Compare commits

..

31 Commits

Author SHA1 Message Date
Florian Bruhin
bad5005dee Release v0.10.1 2017-03-08 06:25:24 +01:00
Florian Bruhin
361e4e93ed Update changelog for v0.10.1 2017-03-08 05:08:54 +01:00
Florian Bruhin
d22125249c pylint: Disable too-many-boolean-expressions
(cherry picked from commit 6bdf8495aa)
2017-03-08 05:07:12 +01:00
pyup-bot
a9871df971 Update pyparsing from 2.1.10 to 2.2.0
(cherry picked from commit a19ebfb6d0)
2017-03-07 22:07:52 +01:00
Florian Bruhin
89eb5224b0 Fix compiled version check
Fixes #2412

(cherry picked from commit 7c9d004bbc)
2017-03-07 22:06:33 +01:00
Florian Bruhin
1f850b8de9 Refactor adblock parsing
(cherry picked from commit 4c3c86081f)
2017-03-07 22:06:29 +01:00
pyup-bot
40d3679073 Update appdirs from 1.4.2 to 1.4.3
(cherry picked from commit c50e652cc7)
2017-03-07 20:38:03 +01:00
Florian Bruhin
4d5dfb5ee0 Stringify py.path.local in adblock tests
(cherry picked from commit ace4006179)
2017-03-07 20:38:00 +01:00
Florian Bruhin
2619d9b83d Handle invalid UTF8 data in hostblock lists
Fixes #2301

(cherry picked from commit c45019f0a0)
2017-03-07 20:29:02 +01:00
Florian Bruhin
2725538155 Reorder initialization
marcos.init() really belongs into _init_modules, and we need to do _process_args
after everything has been initialized.

Fixes #2408.

(cherry picked from commit d42dff67f2)
2017-03-07 20:28:56 +01:00
Florian Bruhin
b1f3c19fc6 Stabilize some tests
(cherry picked from commit 5fb6d26465)
2017-03-07 20:28:52 +01:00
Florian Bruhin
88f2f9dfb5 Only call QApplication::sync() with QtWebEngine
(cherry picked from commit a1c7d179e3)
2017-03-07 20:26:51 +01:00
Florian Bruhin
57c424accd Stabilize session tests
(cherry picked from commit fa89fff668)
2017-03-07 20:26:49 +01:00
Florian Bruhin
2ab6fdab47 Remove unneeded deleted attribute for FakeSocket
(cherry picked from commit 199a2ffe27)
2017-03-07 20:26:44 +01:00
Florian Bruhin
10c64a9e52 Update changelog
(cherry picked from commit 27edc89d88)
2017-03-05 15:52:21 +01:00
Daniel Schadt
63954af9a7 return fast from DownloadItem.open_file
Fixes #2296

By using a singleshot timer, we return fast from DownloadItem.open_file,
which in turn closes the prompt fast, which in turn doesn't allow a
second Ctrl-x to be registered, which in turn doesn't want to set the
filename twice.

(cherry picked from commit 90f12a1d5a)
2017-03-05 15:52:13 +01:00
Florian Bruhin
6b423e15ae Make sure to process history after the rest of init is done
Otherwise, with 5ccafd62d4 the history starts
processing before the webview opened, and opening it is delayed until the whole
history is read.

Instead, call _process_args directly (I'm not even sure why it was using a 0ms
QTimer...) and schedule _init_late_modules after everything is really done.

(cherry picked from commit c422897abb)
2017-03-04 18:26:44 +01:00
Florian Bruhin
50c5a425c0 Fix initial keyboard focus with QtWebEngine
Fixes #2321.

(cherry picked from commit fa90668814c8fa448eb74fad171ce9878bcb5374)
2017-03-04 18:10:57 +01:00
Florian Bruhin
3de4a942e1 ipc: Delay deleting of QLocalSocket on disconnect
Fixes #2396.

See https://bugreports.qt.io/browse/QTBUG-59297 and
https://github.com/qutebrowser/qutebrowser/issues/2321#issuecomment-284024213

(cherry picked from commit b16f1952bb4c8b68f54040e7a2c95702054e624b)
2017-03-04 18:10:54 +01:00
Florian Bruhin
ec969e2da2 Set tab as parent for print dialogs
Fixes #2366

(cherry picked from commit 477539f4bab39122dbee82cfc77b583f871793fd)
2017-03-02 18:42:34 +01:00
Florian Bruhin
a5c3c49e8c Fix WebKitElement._move_text_cursor with old PyQt 2017-03-02 10:10:36 +01:00
Florian Bruhin
ac1095405c Fix lint 2017-03-02 10:10:36 +01:00
Florian Bruhin
18e008d9c0 Stabilize existing text test
(cherry picked from commit 65a701a180)
2017-03-02 08:38:30 +01:00
Florian Bruhin
68775a8cf6 Strip QtWebEngine download suffixes properly
Fixes #2386

(cherry picked from commit 81a36ffd7d)
2017-03-01 23:44:03 +01:00
Florian Bruhin
ca62acb219 Relax validation of QssColor values
Fixes #2370

(cherry picked from commit 1f12b4c1c1)
2017-03-01 18:14:59 +01:00
Florian Bruhin
dbdb48ff71 Move cursor to end with input elements on QtWebEngine
(cherry picked from commit bc0a9cd94d)
2017-03-01 17:55:30 +01:00
Florian Bruhin
b2088e711c Fix selecting text fields with QtWebKit
Using focus() in JS there means that existing text in the field gets selected.
Move the cursor to the end after focusing it to prevent that.

Fixes #2359

(cherry picked from commit 1e1ba34b60)
2017-03-01 17:55:24 +01:00
Florian Bruhin
4730c6fd6b Update changelog
(cherry picked from commit f9697f1ebe)
2017-03-01 14:37:58 +01:00
Florian Bruhin
bc19f138fb Don't strip info when loading PAC from a file
(cherry picked from commit deb59fc66e)
2017-03-01 14:37:57 +01:00
Florian Bruhin
7502d10fd9 Remove UserInfo and path/query for PAC URLs
(cherry picked from commit 9bb5c9fdab)
2017-03-01 14:37:55 +01:00
Florian Bruhin
0d5489395e Fix lint
(cherry picked from commit 1e42fd1319)
2017-03-01 14:35:54 +01:00
598 changed files with 28606 additions and 48397 deletions

View File

@@ -5,15 +5,13 @@ cache:
build: off
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python36-x64\python.exe
matrix:
- TESTENV: py36-pyqt511
- TESTENV: py34
- TESTENV: unittests-frozen
- 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'
- C:\Python27\python -u scripts\dev\ci\appveyor_install.py
test_script:
- '%PYTHON% -m tox -e %TESTENV%'
- C:\Python34\Scripts\tox -e %TESTENV%

View File

@@ -1,7 +0,0 @@
coverage:
status:
project: off
patch: off
changes: off
comment: off

View File

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

29
.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 '#'
@@ -14,7 +11,6 @@ exclude = .*,__pycache__,resources.py
# (for pytest's __tracebackhide__)
# F401: Unused import
# N802: function name should be lowercase
# N806: variable in function should be lowercase
# P101: format string does contain unindexed parameters
# P102: docstring does contain unindexed parameters
# P103: other string does contain unindexed parameters
@@ -22,33 +18,30 @@ exclude = .*,__pycache__,resources.py
# D103: Missing docstring in public function (will be handled by others)
# D104: Missing docstring in public package (will be handled by others)
# D105: Missing docstring in magic method (will be handled by others)
# D106: Missing docstring in public nested class (will be handled by others)
# D107: Missing docstring in __init__ (will be handled by others)
# D209: Blank line before closing """ (removed from PEP257)
# D211: No blank lines allowed before class docstring
# (PEP257 got changed, but let's stick to the old standard)
# D401: First line should be in imperative mood (okay sometimes)
# D402: First line should not be function's signature (false-positives)
# D403: First word of the first line should be properly capitalized
# (false-positives)
# D413: Missing blank line after last section (not in pep257?)
# A003: Builtin name for class attribute (needed for overridden methods)
ignore =
B001,B008,B305,
E128,E226,E265,E501,E402,E266,E722,E731,
F401,
N802,
P101,P102,P103,
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D413,
A003
D102,D103,D104,D105,D209,D211,D402,D403
min-version = 3.4.0
max-complexity = 12
per-file-ignores =
/tests/**/*.py : D100,D101,D401
/tests/unit/browser/test_history.py : N806
/tests/helpers/fixtures.py : N806
/tests/unit/browser/webkit/http/test_content_disposition.py : D400
/scripts/dev/ci/appveyor_install.py : FI53
putty-auto-ignore = True
putty-ignore =
/# pylint: disable=invalid-name/ : +N801,N806
/# pylint: disable=wildcard-import/ : +F403
/# pragma: no mccabe/ : +C901
tests/*/test_*.py : +D100,D101,D401
tests/unit/browser/webkit/test_history.py : +N806
tests/helpers/fixtures.py : +N806
tests/unit/browser/webkit/http/test_content_disposition.py : +D400
scripts/dev/ci/appveyor_install.py : +FI53
copyright-check = True
copyright-regexp = # Copyright [\d-]+ .*
copyright-min-file-size = 110

12
.github/CODEOWNERS vendored
View File

@@ -1,12 +0,0 @@
qutebrowser/browser/history.py @rcorre
qutebrowser/completion/* @rcorre
qutebrowser/misc/sql.py @rcorre
tests/end2end/features/completion.feature @rcorre
tests/end2end/features/test_completion_bdd.py @rcorre
tests/unit/browser/test_history.py @rcorre
tests/unit/completion/* @rcorre
tests/unit/misc/test_sql.py @rcorre
qutebrowser/config/configdata.yml @mschilli87
qutebrowser/javascript/caret.js @artur-shaik

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 +0,0 @@
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
because of exams coming up. Review of non-trivial pull requests will thus be
delayed until then. If you're reading this note after mid-September, please
open an issue.
- Before you start to work on something, please leave a comment on the relevant
issue (or open one). This makes sure there is no duplicate work done.
- Either run the testsuite locally, or keep an eye on Travis CI / AppVeyor
after pushing changes.
- If you are stuck somewhere or have questions,
https://github.com/qutebrowser/qutebrowser#getting-help[please ask]!
See the full contribution documentation for details and other useful hints:
include::../doc/contributing.asciidoc[]

View File

@@ -1,2 +1,2 @@
<!-- If this is a bug report, please remember to mention your version info from
`:open qute:version` or `qutebrowser --version` -->
the `qute:version` page or `qutebrowser --version` -->

24
.gitignore vendored
View File

@@ -15,8 +15,11 @@ __pycache__
/qutebrowser/3rdparty
/doc/*.html
/README.html
/CHANGELOG.html
/CONTRIBUTING.html
/FAQ.html
/INSTALL.html
/qutebrowser/html/doc/
/qutebrowser/html/*.html
/.venv*
/.coverage
/htmlcov
@@ -25,20 +28,17 @@ __pycache__
/.tox
/testresults.html
/.cache
/.pytest_cache
/.testmondata
/.hypothesis
/.mypy_cache
/prof
/venv
TODO
/scripts/testbrowser/cpp/webkit/Makefile
/scripts/testbrowser/cpp/webkit/main.o
/scripts/testbrowser/cpp/webkit/testbrowser
/scripts/testbrowser/cpp/webkit/.qmake.stash
/scripts/testbrowser/cpp/webengine/Makefile
/scripts/testbrowser/cpp/webengine/main.o
/scripts/testbrowser/cpp/webengine/testbrowser
/scripts/testbrowser/cpp/webengine/.qmake.stash
/scripts/testbrowser_cpp/webkit/Makefile
/scripts/testbrowser_cpp/webkit/main.o
/scripts/testbrowser_cpp/webkit/testbrowser
/scripts/testbrowser_cpp/webkit/.qmake.stash
/scripts/testbrowser_cpp/webengine/Makefile
/scripts/testbrowser_cpp/webengine/main.o
/scripts/testbrowser_cpp/webengine/testbrowser
/scripts/testbrowser_cpp/webengine/.qmake.stash
/scripts/dev/pylint_checkers/qute_pylint.egg-info
/misc/file_version_info.txt

View File

@@ -13,40 +13,39 @@ 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,
file-ignored,
wrong-import-order,
ungrouped-imports,
redefined-variable-type,
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,
too-many-boolean-expressions
[BASIC]
function-rgx=[a-z_][a-z0-9_]{2,50}$
const-rgx=[A-Za-z_][A-Za-z0-9_]{0,30}$
method-rgx=[a-z_][A-Za-z0-9_]{1,50}$
attr-rgx=[a-z_][a-z0-9_]{0,30}$
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
argument-rgx=[a-z_][a-z0-9_]{0,30}$
variable-rgx=[a-z_][a-z0-9_]{0,30}$
docstring-min-length=3
@@ -54,9 +53,12 @@ no-docstring-rgx=(^_|^main$)
[FORMAT]
max-line-length=79
ignore-long-lines=(<?https?://|^# Copyright 201\d)
ignore-long-lines=(<?https?://|^# Copyright 201\d|# (pylint|flake8): disable=)
expected-line-ending-format=LF
[SIMILARITIES]
min-similarity-lines=8
[VARIABLES]
dummy-variables-rgx=_.*
@@ -67,11 +69,12 @@ max-args=10
valid-metaclass-classmethod-first-arg=cls
[TYPECHECK]
ignored-modules=PyQt5,PyQt5.QtWebKit
[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
# WORKAROUND for https://github.com/PyCQA/astroid/pull/357
ignored-modules=pytest
# MsgType added as WORKAROUND for
# https://bitbucket.org/logilab/pylint/issues/690/
# UnsetObject because pylint infers any objreg.get(...) as UnsetObject.
ignored-classes=qutebrowser.utils.objreg.UnsetObject,
qutebrowser.browser.webkit.webelem.WebElementWrapper,
scripts.dev.check_coverage.MsgType,
qutebrowser.browser.downloads.UnsupportedAttribute

View File

@@ -1 +0,0 @@
schedule: "every week on monday"

View File

@@ -1,11 +1,14 @@
sudo: false
sudo: required
dist: trusty
language: python
group: edge
python: 3.6
language: generic
matrix:
include:
- os: linux
env: TESTENV=py34-cov
- os: linux
env: DOCKER=debian-jessie
services: docker
- os: linux
env: DOCKER=archlinux
services: docker
@@ -13,40 +16,36 @@ matrix:
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
services: docker
- os: linux
env: DOCKER=archlinux-ng
services: docker
- os: linux
env: DOCKER=ubuntu-xenial
services: docker
- os: linux
language: python
python: 3.6
env: TESTENV=py36-pyqt571
- os: linux
language: python
python: 3.5
env: TESTENV=py35-pyqt571
env: TESTENV=py35-pyqt58
- 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
language: python
python: 3.6
env: TESTENV=py36-pyqt58
- os: osx
env: TESTENV=py37 OSX=sierra
osx_image: xcode9.2
language: generic
env: TESTENV=py36 OSX=elcapitan
osx_image: xcode7.3
# https://github.com/qutebrowser/qutebrowser/issues/2013
# - os: osx
# env: TESTENV=py35 OSX=yosemite
# osx_image: xcode6.4
- os: linux
env: TESTENV=pylint PYTHON=python3.6
env: TESTENV=pylint
- os: linux
env: TESTENV=flake8
- os: linux
env: TESTENV=docs
addons:
apt:
packages:
- asciidoc
- os: linux
env: TESTENV=vulture
- os: linux
@@ -57,27 +56,23 @@ matrix:
env: TESTENV=check-manifest
- os: linux
env: TESTENV=eslint
language: node_js
python: null
node_js: "lts/*"
- os: linux
language: generic
env: TESTENV=shellcheck
services: docker
fast_finish: true
allow_failures:
# https://github.com/qutebrowser/qutebrowser/issues/4055
- os: linux
env: TESTENV=py36-pyqt510
- os: osx
env: TESTENV=py36 OSX=elcapitan
osx_image: xcode7.3
fast_finish: true
cache:
directories:
- $HOME/.cache/pip
- $HOME/build/qutebrowser/qutebrowser/.cache
before_install:
# We need to do this so we pick up the system-wide python properly
- 'export PATH="/usr/bin:$PATH"'
install:
- bash scripts/dev/ci/travis_install.sh
- ulimit -c unlimited
script:
- bash scripts/dev/ci/travis_run.sh
@@ -85,9 +80,6 @@ script:
after_success:
- '[[ $TESTENV == *-cov ]] && codecov -e TESTENV -X gcov'
after_failure:
- bash scripts/dev/ci/travis_backtrace.sh
notifications:
webhooks:
- https://buildtimetrend.herokuapp.com/travis

File diff suppressed because it is too large Load Diff

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
@@ -44,13 +39,8 @@ pointers:
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
be easy to solve]
* https://github.com/qutebrowser/qutebrowser/labels/component%3A%20docs[Documentation issues which require little/no coding]
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/labels/not%20code[Issues which
require little/no coding]
There are also some things to do if you don't want to write code:
@@ -90,16 +80,6 @@ git format-patch origin/master <1>
<1> Replace `master` by the branch your work was based on, e.g.,
`origin/develop`.
Running qutebrowser
-------------------
After link:install.asciidoc#tox[installing qutebrowser via tox], you can run
`.venv/bin/qutebrowser --debug --temp-basedir` to test your changes with debug
logging enabled and without affecting existing running instances.
Alternatively, you can install qutebrowser's dependencies system-wide and run
`python3 -m qutebrowser --debug --temp-basedir`.
Useful utilities
----------------
@@ -112,18 +92,25 @@ unittests and several linters/checkers.
Currently, the following tox environments are available:
* Tests using https://www.pytest.org[pytest]:
- `py35`, `py36`: Run pytest for python 3.5/3.6 with the system-wide PyQt.
- `py36-pyqt57`, ..., `py36-pyqt59`: Run pytest with the given PyQt version (`py35-*` also works).
- `py36-pyqt59-cov`: Run with coverage support (other Python/PyQt versions work too).
* `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8].
- `py34`: Run pytest for python-3.4.
- `py35`: Run pytest for python-3.5.
- `py34-cov`: Run pytest for python-3.4 with code coverage report.
- `py35-cov`: Run pytest for python-3.5 with code coverage report.
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
https://pypi.python.org/pypi/pyflakes[pyflakes],
https://pypi.python.org/pypi/pep8[pep8],
https://pypi.python.org/pypi/mccabe[mccabe]
* `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find
unused code portions.
* `pylint`: Run http://pylint.org/[pylint] static code analysis.
* `pydocstyle`: Check
https://www.python.org/dev/peps/pep-0257/[PEP257] compliance with
https://github.com/PyCQA/pydocstyle[pydocstyle]
* `pyroma`: Check packaging practices with
https://pypi.python.org/pypi/pyroma/[pyroma].
https://pypi.python.org/pypi/pyroma/[pyroma]
* `eslint`: Run http://eslint.org/[ESLint] javascript checker.
* `check-manifest`: Check MANIFEST.in completeness with
https://github.com/mgedmin/check-manifest[check-manifest].
https://github.com/mgedmin/check-manifest[check-manifest]
* `mkvenv`: Bootstrap a virtualenv for testing.
* `misc`: Run `scripts/misc_checks.py` to check for:
- untracked git files
@@ -140,7 +127,7 @@ techniques are useful to handle these:
* Use `_foo` for unused parameters, with `foo` being a descriptive name. Using
`_` is discouraged.
* If you think you have a good reason to suppress a message, then add the
* If you think you have a good reason to suppress a message, then add the
following comment:
+
----
@@ -188,7 +175,7 @@ In the _scripts/_ subfolder there's a `run_profile.py` which profiles the code
and shows a graphical representation of what takes how much time.
It uses the built-in Python
https://docs.python.org/3.6/library/profile.html[cProfile] module and can show
https://docs.python.org/3.4/library/profile.html[cProfile] module and can show
the output in four different ways:
* Raw profile file (`--profile-tool=none`)
@@ -203,8 +190,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
@@ -227,7 +214,7 @@ Documentation of used Python libraries:
* http://pygments.org/docs/[pygments]
* http://fdik.org/pyPEG/index.html[pyPEG2]
* http://pythonhosted.org/setuptools/[setuptools]
* http://www.pyinstaller.org/[PyInstaller]
* http://cx-freeze.readthedocs.org/en/latest/overview.html[cx_Freeze]
* https://pypi.python.org/pypi/colorama[colorama]
Related RFCs and standards:
@@ -299,7 +286,7 @@ There are some exceptions to that:
* `QThread` is used instead of Python threads because it provides signals and
slots.
* `QProcess` is used instead of Python's `subprocess`.
* `QProcess` is used instead of Python's `subprocess`
* `QUrl` is used instead of storing URLs as string, see the
<<handling-urls,handling URLs>> section for details.
@@ -320,7 +307,7 @@ carefully be checked.
* Methods of Qt objects have certain maximum values based on their
underlying C++ types.
+
To avoid passing too large of a numeric parameter to a Qt function, all
To avoid passing too large of a numeric parameter to a Qt function, all
numbers should be range-checked using `qutebrowser.qtutils.check_overflow`,
or by other means (e.g. by setting a maximum value for a config object).
@@ -334,7 +321,7 @@ dictionaries which map object names to the actual long-living objects.
There are currently these object registries, also called 'scopes':
* The `global` scope, with objects which are used globally (`config`,
`cookie-jar`, etc.).
`cookie-jar`, etc.)
* The `tab` scope with objects which are per-tab (`hintmanager`, `webview`,
etc.). Passing this scope to `objreg.get()` selects the object in the currently
focused tab by default. A tab can be explicitly selected by passing
@@ -390,7 +377,7 @@ The following logging levels are available for every logger:
|error |There was an issue and some kind of operation was abandoned.
|warning |There was an issue but the operation can continue running.
|info |General informational messages.
|debug |Verbose debugging information.
|debug |Verbose debugging informations.
|=======================================================================
[[commands]]
@@ -443,14 +430,14 @@ def foo(bar: int, baz=True):
----
Possible values:
- A callable (`int`, `float`, etc.): Gets called to validate/convert the value.
- A python enum type: All members of the enum are possible values.
- A `typing.Union` of multiple types above: Any of these types are valid
values, e.g., `typing.Union[str, int]`.
- A callable (`int`, `float`, etc.): Gets called to validate/convert the
value.
- A python enum type: All members of the enum are possible values.
- A `typing.Union` of multiple types above: Any of these types are valid
values, e.g., `typing.Union[str, int]`
You can customize how an argument is handled using the `@cmdutils.argument`
decorator *after* `@cmdutils.register`. This can, for example, be used to
decorator *after* `@cmdutils.register`. This can, for example, be used to
customize the flag an argument should get:
[source,python]
@@ -477,10 +464,10 @@ For `typing.Union` types, the given `choices` are only checked if other types
The following arguments are supported for `@cmdutils.argument`:
- `flag`: Customize the short flag (`-x`) the argument will get.
- `win_id=True`: Mark the argument as special window ID argument.
- `count=True`: Mark the argument as special count argument.
- `completion`: A completion function (see `qutebrowser.completions.models.*`)
to use when completing arguments for the given command.
- `win_id=True`: Mark the argument as special window ID argument
- `count=True`: Mark the argument as special count argument
- `hide=True`: Hide the argument from the documentation
- `completion`: A `usertypes.Completion` member to use as completion.
- `choices`: The allowed string choices for the argument.
The name of an argument will always be the parameter name, with any trailing
@@ -542,12 +529,12 @@ ____
Setting up a Windows Development Environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Install https://www.python.org/downloads/release/python-362/[Python 3.6].
* Install PyQt via `pip install PyQt5`.
* Create a file at `C:\Windows\system32\python3.bat` with the following content (adjust the path as necessary):
`@C:\Python36\python %*`.
This will make the Python 3.6 interpreter available as `python3`, which is used by various development scripts.
* Install git from the https://git-scm.com/download/win[git-scm downloads page].
* Install https://www.python.org/downloads/release/python-344/[Python 3.4]
* Install https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/[PyQt 5.5]
* Create a file at `C:\Windows\system32\python3.bat` with the following content:
`@C:\Python34\python %*`
This will make the Python 3.4 interpreter available as `python3`, which is used by various development scripts.
* Install git from the https://git-scm.com/download/win[git-scm downloads page]
Try not to enable `core.autocrlf`, since that will cause `flake8` to complain a lot. Use an editor that can deal with plain line feeds instead.
* Clone your favourite qutebrowser repository.
* To install tox, open an elevated cmd, enter your working directory and run `pip install -rmisc/requirements/requirements-tox.txt`.
@@ -559,45 +546,6 @@ Rebuilding the website
If you want to rebuild the website, run `./scripts/asciidoc2html.py --website <outputdir>`.
Chrome URLs
~~~~~~~~~~~
With the QtWebEngine backend, qutebrowser supports several chrome:// urls which
can be useful for debugging:
- chrome://appcache-internals/
- chrome://blob-internals/
- chrome://gpu/
- chrome://histograms/
- chrome://indexeddb-internals/
- chrome://media-internals/
- chrome://network-errors/
- chrome://serviceworker-internals/
- chrome://webrtc-internals/
- chrome://crash/ (crashes the current renderer process!)
- chrome://kill/ (kills the current renderer process!)
- chrome://gpucrash/ (crashes qutebrowser!)
- chrome://gpuhang/ (hangs qutebrowser!)
- chrome://gpuclean/ (crashes the current renderer process!)
- chrome://ppapiflashcrash/
- chrome://ppapiflashhang/
- chrome://quota-internals/ (Qt 5.11)
- chrome://taskscheduler-internals/ (Qt 5.11)
- chrome://sandbox/ (Qt 5.11, Linux only)
QtWebEngine internals
~~~~~~~~~~~~~~~~~~~~~
This is mostly useful for qutebrowser maintainers to work around issues in Qt - if you don't understand it, don't worry, just ignore it.
The hierarchy of widgets when QtWebEngine is involved looks like this:
- qutebrowser has a `WebEngineTab` object, which is its abstraction over QtWebKit/QtWebEngine.
- The `WebEngineTab` has a `_widget` attribute, which is the https://doc.qt.io/qt-5/qwebengineview.html[QWebEngineView]
- That view has a https://doc.qt.io/qt-5/qwebenginepage.html[QWebEnginePage] for everything which doesn't require rendering.
- The view also has a layout with exactly one element (which also is its `focusProxy()`)
- That element is the http://code.qt.io/cgit/qt/qtwebengine.git/tree/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp[RenderWidgetHostViewQtDelegateWidget] (it inherits https://doc.qt.io/qt-5/qquickwidget.html[QQuickWidget]) - also often referred to as RWHV or RWHVQDW. It can be obtained via `sip.cast(tab._widget.focusProxy(), QQuickWidget)`.
- Calling `rootObject()` on that gives us the https://doc.qt.io/qt-5/qquickitem.html[QQuickItem] where Chromium renders into (?). With it, we can do things like `.setRotation(20)`.
Style conventions
-----------------
@@ -683,7 +631,7 @@ New Qt release
https://bugreports.qt.io/issues/?jql=reporter%20%3D%20%22The%20Compiler%22%20ORDER%20BY%20fixVersion%20ASC[Qt bugtracker]
and make sure all bugs marked as resolved are actually fixed.
* Update own PKGBUILDs based on upstream Archlinux updates and rebuild.
* Update recommended Qt version in `README`.
* Update recommended Qt version in `README`
* Grep for `WORKAROUND` in the code and test if fixed stuff works without the
workaround.
* Check relevant
@@ -693,32 +641,37 @@ bugs] and check if they're fixed.
New PyQt release
~~~~~~~~~~~~~~~~
* See above.
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
* See above
* Install new PyQt in Windows VM (32- and 64-bit)
* Download new installer and update PyQt installer path in `ci_install.py`.
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions
qutebrowser release
~~~~~~~~~~~~~~~~~~~
* Make sure there are no unstaged changes and the tests are green.
* Make sure all issues with the related milestone are closed.
* Run `x=... y=...` to set the respective shell variables.
* Run `x=... y=...` to set the respective shell variables
* Update changelog (remove *(unreleased)*).
* Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version`
- `python -m qutebrowser --basedir conf :quit`
- `sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.x.y.conf`
- `rm -r conf`
- commit
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Commit.
* Update changelog (remove *(unreleased)*)
* Run tests again
* Commit
* Create annotated git tag (`git tag -s "v1.$x.$y" -m "Release v1.$x.$y"`).
* `git push origin`; `git push origin v1.$x.$y`.
* Create annotated git tag (`git tag -s "v0.$x.$y" -m "Release v0.$x.$y"`)
* `git push origin`; `git push origin v0.$x.$y`
* If committing on minor branch, cherry-pick release commit to master.
* Create release on github.
* Create release on github
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones
as closed.
* Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`.
* Windows: Run `git checkout v1.X.Y; py -3.6 scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
* macOS: Run `pyenv shell 3.6.6 && git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
* On server:
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
- Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser`
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
* Announce to qutebrowser and qutebrowser-announce mailinglist.
* Linux: Run `python3 scripts/dev/build_release.py --upload v0.$x.$y`
* Windows: Run `C:\Python34_x32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v0.X.Y` (replace X/Y by hand)
* OS X: Run `python3 scripts/dev/build_release.py --upload v0.X.Y` (replace X/Y by hand)
* On server: Run `python3 scripts/dev/download_release.py v0.X.Y` (replace X/Y by hand)
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed
* Announce to qutebrowser and qutebrowser-announce mailinglist

View File

173
FAQ.asciidoc Normal file
View File

@@ -0,0 +1,173 @@
Frequently asked questions
==========================
:title: Frequently asked questions
The Compiler <mail@qutebrowser.org>
[qanda]
What is qutebrowser based on?::
qutebrowser uses http://www.python.org/[Python], http://qt.io/[Qt] and
http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
+
The concept of it is largely inspired by http://portix.bitbucket.org/dwb/[dwb]
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
key bindings are similar to dwb.
Why another browser?::
It might be hard to believe, but I didn't find any browser which I was
happy with, so I started to write my own. Also, I needed a project to get
into writing GUI applications with Python and
link:http://qt.io/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
+
Read the next few questions to find out why I was unhappy with existing
software.
What's wrong with link:http://portix.bitbucket.org/dwb/[dwb]/link:http://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/link:http://pwmt.org/projects/jumanji/[jumanji]/... (projects based on WebKitGTK)?::
Most of them are based on the http://webkitgtk.org/[WebKitGTK+]
http://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
which causes a lot of crashes. As the GTK API using WebKit1 is
https://lists.webkit.org/pipermail/webkit-gtk/2014-March/001821.html[deprecated],
these bugs are never going to be fixed.
+
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/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
slower and more bloated with every upgrade, and has some
https://blog.mozilla.org/advancingcontent/2014/02/11/publisher-transformation-with-users-at-the-center/[horrible
ideas] lately.
+
Also, developing addons for it is a nightmare.
What's wrong with http://www.chromium.org/Home[Chromium] and https://vimium.github.io/[Vimium]?::
The Chrome plugin API doesn't seem to allow much freedom for plugin
writers, which results in Vimium not really having all the features you'd
expect from a proper minimal, vim-like browser.
Why Python?::
I enjoy writing Python since 2011, which made it one of the possible
choices. I wanted to use http://qt.io/[Qt] because of
http://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
http://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
like C++ and can't write it very well, so that wasn't an alternative.
But isn't Python too slow for a browser?::
http://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
I believe efficiency while coding is a lot more important than efficiency
while running. Also, most of the heavy lifting of qutebrowser is done by Qt
and WebKit in C++, with the
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
Is there an adblocker?::
There is a host-based adblocker which takes /etc/hosts-like lists. A "real"
adblocker has a
http://www.reddit.com/r/programming/comments/25j41u/adblock_pluss_effect_on_firefoxs_memory_usage/chhpomw[big
impact] on browsing speed and
https://blog.mozilla.org/nnethercote/2014/05/14/adblock-pluss-effect-on-firefoxs-memory-usage/[RAM
usage], so implementing it properly might take some time and won't be done
for v0.1 if at all.
How do I play Youtube videos with mpv?::
You can easily add a key binding to play youtube videos inside a real video
player - optionally even with hinting for links:
+
----
:bind m spawn mpv {url}
:bind M hint links spawn mpv {hint-url}
----
+
Note that you might need an additional package (e.g.
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
Archlinux) to play web videos with mpv.
+
There is a very useful script for mpv, which emulates "unique application"
functionality. This way you can add links to the mpv playlist instead of
playing them all at once.
+
You can find the script here: https://github.com/mpv-player/mpv/blob/master/TOOLS/umpv
+
It also works nicely with rapid hints:
+
----
:bind m spawn umpv {url}
:bind M hint links spawn umpv {hint-url}
:bind ;M hint --rapid links spawn umpv {hint-url}
----
How do I use qutebrowser with mutt?::
Due to a Qt limitation, local files without `.html` extensions are
"downloaded" instead of displayed, see
https://github.com/qutebrowser/qutebrowser/issues/566[#566]. You can work
around this by using this in your `mailcap`:
+
----
text/html; mv %s %s.html && qutebrowser %s.html >/dev/null 2>/dev/null; needsterminal;
----
What is the difference between bookmarks and quickmarks?::
Bookmarks will always use the title of the website as their name, but with quickmarks
you can set your own title.
+
For example, if you bookmark multiple food recipe websites and use `:open`,
you have to type the title or address of the website.
+
When using quickmark, you can give them all names, like
`foodrecipes1`, `foodrecipes2` and so on. When you type
`:open foodrecipes`, you will see a list of all the food recipe sites,
without having to remember the exact website title or address.
== 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 allow-plugins true`
in qutebrowser will enable plugins. Packages for flash should
be provided for your platform or it can be obtained from
http://get.adobe.com/flashplayer/[Adobe].
Experiencing freezing on sites like duckduckgo and youtube.::
This issue could be caused by stale plugin files installed by `mozplugger`
if mozplugger was subsequently removed.
Try exiting qutebrowser and removing `~/.mozilla/plugins/mozplugger*.so`.
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
for more details.
Experiencing segfaults (crashes) on Debian systems.::
For Debian it's highly recommended to install the `gstreamer0.10-plugins-base` package.
This is a workaround for a bug in Qt, it has been fixed upstream in Qt 5.4
More details can be found
https://bugs.webkit.org/show_bug.cgi?id=119951[here].
Segfaults on Facebook, Medium, Amazon, ...::
If you are on a Debian or Ubuntu based system, you might experience some crashes
visiting these sites. This is caused by various bugs in Qt which have been
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
some packages. On Debian Jessie, it's recommended to use the experimental
repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL].
+
Since Ubuntu Trusty (using Qt 5.2.1),
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over
70 important bugs] have been fixed in QtWebKit. For Debian Jessie (using Qt 5.3.2)
it's still
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[nearly
20 important bugs].
My issue is not listed.::
If you experience any segfaults or crashes, you can report the issue in
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
using the `:report` command.
If you are reporting a segfault, make sure you read the
link:doc/stacktrace.asciidoc[guide] on how to report them with all needed
information.

356
INSTALL.asciidoc Normal file
View File

@@ -0,0 +1,356 @@
Installing qutebrowser
======================
On Debian / Ubuntu
------------------
qutebrowser should run on these systems:
* Debian jessie or newer
* Ubuntu Trusty (14.04 LTS) or newer
* Any other distribution based on these (e.g. Linux Mint 17+)
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is
still relatively easy!
You can use packages that are built for every release or build it yourself from git.
Using the packages
~~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
----
# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml
----
Get the qutebrowser package from the
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
Install the packages:
----
# dpkg -i python3-pypeg2_*_all.deb
# dpkg -i qutebrowser_*_all.deb
----
Build it from git
~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
[NOTE]
==========================
On Debian, it's recommended to install the Qt packages from the
https://wiki.debian.org/DebianExperimental[experimental] repository as those
are a much newer version of Qt which is more stable.
Add the following line to your `/etc/apt/sources.list`:
----
deb http://ftp.debian.org/debian experimental main
----
Then install the packages like this:
----
# apt-get update
# apt-get install -t experimental python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-dev
# apt-get install python-tox
----
It's also recommended to pin those packages to receive updates by creating a
file `/etc/apt/preferences.d/qutebrowser` with the following contents:
----
Package: python3-pyqt5* libqt5*
Pin: release a=experimental
Pin-Priority: 800
----
==========================
For distributions other than Debian or if you prefer to not use the
experimental repo:
----
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev
----
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
----
# apt-get install asciidoc source-highlight
$ python3 scripts/asciidoc2html.py
----
If video or sound don't seem to work, try installing the gstreamer plugins:
----
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
----
Then <<tox,install qutebrowser via tox>>.
On Fedora
---------
qutebrowser is available in the official repositories for Fedora 22 and newer.
----
# dnf install qutebrowser
----
On Archlinux
------------
qutebrowser is available in the official [community] repository.
----
# pacman -S qutebrowser
----
There is also a -git version available in the AUR:
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
You can install it using `makepkg` like this:
----
$ git clone https://aur.archlinux.org/qutebrowser-git.git
$ cd qutebrowser-git
$ makepkg -si
$ cd ..
$ rm -r qutebrowser-git
----
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
If video or sound don't seem to work, try installing the gstreamer plugins:
----
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
----
On Gentoo
---------
qutebrowser is available in the main repository and can be installed with:
----
# emerge -av qutebrowser
----
Make sure you have `python3_4` in your `PYTHON_TARGETS`
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
necessary.
If video or sound don't seem to work, try installing the gstreamer plugins:
----
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
----
On Void Linux
-------------
qutebrowser is available in the official repositories and can be installed
with:
----
# xbps-install qutebrowser
----
On NixOS
--------
Nixpkgs collection contains `pkgs.qutebrowser` since June 2015. You can install
it with:
----
$ nix-env -i qutebrowser
----
On openSUSE
-----------
There are prebuilt RPMs available for Tumbleweed and Leap 42.1:
http://software.opensuse.org/download.html?project=home%3Aarpraher&package=qutebrowser[One Click Install]
Or add the repo manually:
----
# zypper addrepo http://download.opensuse.org/repositories/home:arpraher/openSUSE_Tumbleweed/home:arpraher.repo
# zypper refresh
# zypper install qutebrowser
----
On OpenBSD
----------
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
Manual install:
----
# cd /usr/ports/www/qutebrowser
# make install
----
Or alternatively if you're using `-current` (or OpenBSD 6.1 once it's been released):
----
# pkg_add qutebrowser
----
On Windows
----------
There are different ways to install qutebrowser on Windows:
Prebuilt binaries
~~~~~~~~~~~~~~~~~
Prebuilt standalone packages and MSI installers
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
release.
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* PackageManagement PowerShell module
----
PS C:\> Install-Package qutebrowser
----
* Chocolatey's client
----
C:\> choco install qutebrowser
----
Manual install
~~~~~~~~~~~~~~
* Use the installer from http://www.python.org/downloads[python.org] to get
Python 3 (be sure to install pip).
* Use the installer from
http://www.riverbankcomputing.com/software/pyqt/download5[Riverbank computing]
to get Qt and PyQt5.
* Install https://testrun.org/tox/latest/index.html[tox] via
https://pip.pypa.io/en/latest/[pip]:
----
$ pip install tox
----
Then <<tox,install qutebrowser via tox>>.
On OS X
-------
Prebuilt binary
~~~~~~~~~~~~~~~
The easiest way to install qutebrowser on OS X is to use the prebuilt `.app`
files from the
https://github.com/qutebrowser/qutebrowser/releases[release page].
This binary is also available through the
https://caskroom.github.io/[Homebrew Cask] package manager:
----
$ brew cask install qutebrowser
----
Manual Install
~~~~~~~~~~~~~~
Alternatively, you can install the dependencies via a package manager (like
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]) and run
qutebrowser from source.
==== Homebrew
----
$ brew install qt5
$ pip3 install qutebrowser
----
Homebrew's builds of Qt and PyQt no longer include QtWebKit - if you need
QtWebKit support, it is necessary to build from source. The build takes several
hours on an average laptop.
----
$ brew install qt5 --with-qtwebkit
$ brew install -s pyqt5
$ pip3 install qutebrowser
----
Packagers
---------
There are example .desktop and icon files provided. They would go in the
standard location for your distro (`/usr/share/applications` and
`/usr/share/pixmaps` for example).
The normal `setup.py install` doesn't install these files, so you'll have to do
it as part of the packaging process.
[[tox]]
Installing qutebrowser with tox
-------------------------------
First of all, clone the repository using http://git-scm.org/[git] and switch
into the repository folder:
----
$ git clone https://github.com/qutebrowser/qutebrowser.git
$ cd qutebrowser
----
Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
----
$ tox -e mkvenv
----
On Windows, run tox with the 'mkvenv-win' option, however make sure that ONLY Python3 is in your PATH before running tox.
----
$ tox -e mkvenv-win
----
This installs all needed Python dependencies in a `.venv` subfolder. The
system-wide Qt5/PyQt5 installations are symlinked into the virtual environment.
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 "$@"
----
If you are developing on qutebrowser, you may want to redirect it to a local
config:
----
#!/bin/bash
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser -c .qutebrowser-local "$@"
----
Updating
~~~~~~~~
When you updated your local copy of the code (e.g. by pulling the git repo, or
extracting a new version), the virtualenv should automatically use the updated
code. However, if dependencies got added, this won't be reflected in the
virtualenv. Thus it's recommended to run the following command to recreate the
virtualenv:
----
$ tox -r -e mkvenv
----

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,37 +8,42 @@ graft icons
graft doc/img
graft misc/apparmor
graft misc/userscripts
graft misc/requirements
recursive-include scripts *.py *.sh *.js
recursive-include scripts *.py
include qutebrowser/utils/testfile
include qutebrowser/git-commit-id
include LICENSE doc/* README.asciidoc
include misc/qutebrowser.desktop
include misc/qutebrowser.appdata.xml
include misc/Makefile
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
include qutebrowser.desktop
include requirements.txt
include tox.ini
include qutebrowser.py
include misc/cheatsheet.svg
include qutebrowser/config/configdata.yml
prune www
prune scripts/dev
prune scripts/testbrowser/cpp
prune scripts/testbrowser_cpp
prune .github
exclude scripts/asciidoc2html.py
exclude doc/notes
recursive-exclude doc *.asciidoc
include doc/qutebrowser.1.asciidoc
include doc/changelog.asciidoc
prune tests
prune qutebrowser/3rdparty
prune misc/requirements
prune misc/docker
exclude .editorconfig
exclude pytest.ini
exclude qutebrowser.rcc
exclude .coveragerc
exclude .pylintrc
exclude qutebrowser/javascript/.eslintrc.yaml
exclude qutebrowser/javascript/.eslintignore
exclude doc/help
exclude .*
exclude .appveyor.yml
exclude .travis.yml
exclude codecov.yml
exclude .pydocstylerc
exclude misc/appveyor_install.py
exclude misc/qutebrowser.spec
exclude misc/qutebrowser.nsi
exclude misc/qutebrowser.rcc
exclude .flake8
global-exclude __pycache__ *.pyc *.pyo

View File

@@ -9,11 +9,14 @@ qutebrowser
// QUTE_WEB_HIDE
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/COPYING"]
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
image:https://requires.io/github/qutebrowser/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/qutebrowser/qutebrowser/requirements/?branch=master"]
image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"]
image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"]
image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"]
link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/faq.asciidoc[FAQ] | https://www.qutebrowser.org/doc/contributing.html[contributing] | link:https://github.com/qutebrowser/qutebrowser/releases[releases] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc[installing]
link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | link:https://github.com/qutebrowser/qutebrowser/releases[releases]
// QUTE_WEB_HIDE_END
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
@@ -33,8 +36,11 @@ Downloads
---------
See the https://github.com/qutebrowser/qutebrowser/releases[github releases
page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for
detailed instructions on how to get qutebrowser running on various platforms.
page] for available downloads (currently a source archive, and standalone
packages as well as MSI installers for Windows).
See link:INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
qutebrowser running for various platforms.
Documentation
-------------
@@ -42,15 +48,14 @@ Documentation
In addition to the topics mentioned in this README, the following documents are
available:
* https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[Key binding cheatsheet]: +
image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"]
* A https://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]: +
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
* link:doc/quickstart.asciidoc[Quick start guide]
* https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings
* link:doc/faq.asciidoc[Frequently asked questions]
* link:doc/help/configuring.asciidoc[Configuring qutebrowser]
* link:doc/contributing.asciidoc[Contributing to qutebrowser]
* link:doc/install.asciidoc[Installing qutebrowser]
* link:doc/changelog.asciidoc[Change Log]
* A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings.
* link:FAQ.asciidoc[Frequently asked questions]
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
* link:INSTALL.asciidoc[INSTALL]
* link:CHANGELOG.asciidoc[Change Log]
* link:doc/stacktrace.asciidoc[Reporting segfaults]
* link:doc/userscripts.asciidoc[How to write userscripts]
@@ -65,18 +70,14 @@ message to the
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
mailto:qutebrowser@lists.qutebrowser.org[].
There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also
get sent to the general qutebrowser@ list).
If you're a reddit user, there's a
https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there.
There's also a https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
at mailto:qutebrowser-announce@lists.qutebrowser.org[].
Contributions / Bugs
--------------------
You want to contribute to qutebrowser? Awesome! Please read
link:doc/contributing.asciidoc[the contribution guidelines] for details and
link:CONTRIBUTING.asciidoc[the contribution guidelines] for details and
useful hints.
If you found a bug or have a feature request, you can report it in several
@@ -89,43 +90,36 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
mailto:qutebrowser@lists.qutebrowser.org[].
For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID
https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072].
https://www.the-compiler.org/pubkey.asc[0xFD55A072].
Requirements
------------
The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
* http://qt.io/[Qt] 5.7.1 or newer (5.11.1 recommended) with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
- QtOpenGL
- QtWebEngine, or
- QtWebKit - only the
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
supported
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
(5.11.2 recommended) for Python 3
* http://www.python.org/[Python] 3.4 or newer
* http://qt.io/[Qt] 5.2.0 or newer (5.5.1 recommended)
* QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG) or QtWebEngine
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
(5.5.1 recommended) for Python 3
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]
* http://pygments.org/[pygments]
* https://github.com/yaml/pyyaml[PyYAML]
* http://www.attrs.org/[attrs]
* http://pyyaml.org/wiki/PyYAML[PyYAML]
The following libraries are optional:
The following libraries are optional and provide a better user experience:
* http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml`
with QtWebKit).
* On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log
output.
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
command, when using the git repository (rather than a release).
* http://cthedot.de/cssutils/[cssutils]
See link:doc/install.asciidoc[the documentation] for directions on how to
install qutebrowser and its dependencies.
To generate the documentation for the `:help` command, when using the git
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
On Windows, https://pypi.python.org/pypi/colorama/[colorama] is needed to
display colored log output.
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
and its dependencies.
Donating
--------
@@ -146,63 +140,206 @@ get in touch!
Authors
-------
qutebrowser's primary author is Florian Bruhin (The Compiler), but qutebrowser
wouldn't be what it is without the help of
https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contributors]!
Contributors, sorted by the number of commits in descending order:
Additionally, the following people have contributed graphics:
// QUTE_AUTHORS_START
* Florian Bruhin
* Daniel Schadt
* Ryan Roden-Corrent
* Jan Verbeek
* Jakub Klinkovský
* Antoni Boucher
* Lamar Pavel
* Marshall Lochbaum
* Bruno Oliveira
* Alexander Cogneau
* Felix Van der Jeugt
* Daniel Karbach
* Martin Tournoij
* Kevin Velghe
* Raphael Pierzina
* Joel Torstensson
* Patric Schmitz
* Tarcisio Fedrizzi
* Claude
* Corentin Julé
* meles5
* Philipp Hansch
* Imran Sobir
* Panagiotis Ktistakis
* Artur Shaik
* Nathan Isom
* Thorsten Wißmann
* Austin Anderson
* Fritz Reichwald
* Jimmy
* Niklas Haas
* Maciej Wołczyk
* Spreadyy
* Alexey "Averrin" Nabrodov
* nanjekyejoannah
* avk
* ZDarian
* Milan Svoboda
* John ShaggyTwoDope Jenkins
* Clayton Craft
* Peter Vilim
* knaggita
* Oliver Caldwell
* Julian Weigt
* Tomasz Kramkowski
* Sebastian Frysztak
* Nikolay Amiantov
* Julie Engel
* Jonas Schürmann
* error800
* Michael Hoang
* Liam BEGUIN
* Daniel Fiser
* skinnay
* Zach-Button
* Samuel Walladge
* Peter Rice
* Ismail S
* Halfwit
* David Vogt
* Claire Cavanaugh
* rikn00
* kanikaa1234
* haitaka
* Nick Ginther
* Michał Góral
* Michael Ilsaas
* Martin Zimmermann
* Jussi Timperi
* Cosmin Popescu
* Brian Jackson
* thuck
* sbinix
* rsteube
* neeasade
* jnphilipp
* Tobias Patzl
* Stefan Tatschner
* Samuel Loury
* Peter Michely
* Panashe M. Fundira
* Lucas Hoffmann
* Link
* Larry Hynes
* Kirill A. Shutemov
* Johannes Altmanninger
* Jeremy Kaplan
* Ismail
* Edgar Hipp
* Daryl Finlay
* pkill9
* arza
* adam
* Samir Benmendil
* Regina Hug
* Mathias Fussenegger
* Marcelo Santos
* Joel Bradshaw
* Jean-Louis Fuchs
* Franz Fellner
* Eric Drechsel
* zwarag
* xd1le
* rmortens
* oniondreams
* issue
* haxwithaxe
* evan
* dylan araps
* addictedtoflames
* Xitian9
* Tomas Orsava
* Tom Janson
* Tobias Werth
* Tim Harder
* Thiago Barroso Perrotta
* Sorokin Alexei
* Simon Désaulniers
* Rok Mandeljc
* Noah Huesser
* Moez Bouhlel
* Matthias Lisin
* Marcel Schilling
* Lazlow Carmichael
* Kevin Wang
* Ján Kobezda
* Johannes Martinsson
* Jean-Christophe Petkovich
* Jay Kamat
* Helen Sherwood-Taylor
* HalosGhost
* Gregor Pohl
* Eivind Uggedal
* Dietrich Daroch
* Derek Sivers
* Daniel Lu
* Arseniy Seroka
* Andy Balaam
* Andreas Fischer
* Akselmo
// QUTE_AUTHORS_END
The following people have contributed graphics:
* Jad/link:http://yelostudio.com[yelo] (new icon)
* WOFall (original icon)
* regines (key binding cheatsheet)
Also, thanks to everyone who contributed to one of qutebrowser's
link:doc/backers.asciidoc[crowdfunding campaigns]!
Thanks / Similar projects
-------------------------
Similar projects
----------------
Many projects with a similar goal as qutebrowser exist:
* http://portix.bitbucket.org/dwb/[dwb] (C, GTK+ with WebKit1, currently
http://www.reddit.com/r/linux/comments/2huqbc/dwb_abandoned/[unmaintained] -
main inspiration for qutebrowser)
* https://github.com/fanglingsu/vimb[vimb] (C, GTK+ with WebKit1, active)
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
WebKit1, dead)
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1, active)
* https://mason-larobina.github.io/luakit/[luakit] (C/Lua, GTK+ with
WebKit1, not very active)
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1, not very
active)
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2, active)
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko,
active)
* https://github.com/AeroNotix/lispkit[lispkit] (quite new, lisp, GTK+ with
WebKit, active)
* http://www.vimperator.org/[Vimperator] (Firefox addon)
* http://5digits.org/pentadactyl/[Pentadactyl] (Firefox addon)
* https://github.com/akhodakivskiy/VimFx[VimFx] (Firefox addon)
* https://github.com/1995eaton/chromium-vim[cVim] (Chrome/Chromium addon)
* http://vimium.github.io/[vimium] (Chrome/Chromium addon)
* https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome] (Chrome/Chromium addon)
* https://github.com/jinzhu/vrome[Vrome] (Chrome/Chromium addon)
Many projects with a similar goal as qutebrowser exist.
Most of them were inspirations for qutebrowser in some way, thanks for that!
Active
~~~~~~
Thanks as well to the following projects and people for helping me with
problems and helpful hints:
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
* Chrome/Chromium addons:
https://github.com/1995eaton/chromium-vim[cVim],
http://vimium.github.io/[Vimium],
https://github.com/brookhong/Surfingkeys[Surfingkeys],
https://key.saka.io/[Saka Key]
* Firefox addons (based on WebExtensions):
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental),
https://key.saka.io[Saka Key],
https://github.com/ueokande/vim-vixen[Vim Vixen],
https://github.com/shinglyu/QuantumVim[QuantumVim],
https://github.com/cmcaine/tridactyl[Tridactyl] (working
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
keyboard integration in Firefox).
* http://eric-ide.python-projects.org/[eric5] / Detlev Offenbach
* https://code.google.com/p/devicenzo/[devicenzo]
* portix
* seir
* nitroxleecher
Inactive
~~~~~~~~
Also, thanks to:
* https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1,
https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] -
main inspiration for qutebrowser)
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
WebKit1)
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1)
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko)
* Firefox addons (not based on WebExtensions or no recent activity):
http://www.vimperator.org/[Vimperator],
http://5digits.org/pentadactyl/[Pentadactyl],
https://github.com/akhodakivskiy/VimFx[VimFx],
* Chrome/Chromium addons:
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
https://github.com/jinzhu/vrome[Vrome]
* Everyone contributing to the link:doc/backers.asciidoc[crowdfunding].
* Everyone who had the patience to test qutebrowser before v0.1.
* Everyone triaging/fixing my bugs in the
https://bugreports.qt.io/secure/Dashboard.jspa[Qt bugtracker]
* Everyone answering my questions on http://stackoverflow.com/[Stack Overflow]
and in IRC.
* All the projects which were a great help while developing qutebrowser.
License
-------
@@ -218,7 +355,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
pdf.js
------

9
codecov.yml Normal file
View File

@@ -0,0 +1,9 @@
status:
project:
enabled: no
patch:
enabled: no
changes:
enabled: no
comment: off

View File

@@ -1,186 +1,13 @@
Crowdfunding backers
====================
2017
----
Mid-2017, qutebrowser had its
https://www.kickstarter.com/projects/the-compiler/qutebrowser-v10-with-per-domain-settings[second crowdfunding]
with the goal of implementing the new config system and releasing v1.0.
Thanks a lot to the following people who contributed to it:
Gold sponsors
~~~~~~~~~~~~~
- Iggy
- zwitschi
- 2x Anonymous
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
Other sponsors
~~~~~~~~~~~~~~
- 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
- Jonas Schürmann
- Kenichiro Ito
- Kenny Low
- Lars Ivar Igesund
- Leulas
- Lucas Aride Moulin
- Ludovic Chabant
- Lukas Gierth
- Magnus Lindström
- Marulkan
- Matthew Chun-Lum
- Matthew Cronen
- Matthew Quigley
- Michael Schönwälder
- Mika Kutila
- Mitchell Stokes
- Nathan Howell
- Nathan Schlehlein
- Noël Zindel
- Obri
- Patrik Peng
- Peter DiMarco
- Peter Rice
- Philipp Middendorf
- Pkill9
- PluMGMK
- Prescott
- ProXicT
- Ram-Z
- Robotichead
- Roshless
- Ryan Ellis
- Ryan P Deslandes
- Sam Doshi
- Sam Stone
- 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
2016
----
Mid-2016, qutebrowser did run a http://igg.me/at/qutebrowser[crowdfunding] for
QtWebEngine support in qutebrowser.
Thanks a lot to the following people who contributed to it:
Gold sponsors
~~~~~~~~~~~~~
-------------
- Chris Salzberg
- Clayton Craft
@@ -189,7 +16,7 @@ Gold sponsors
- 1 Anonymous
Day sponsors
~~~~~~~~~~~~
------------
- Agent 42
- Iggy Jackson
@@ -201,7 +28,7 @@ Day sponsors
- 4 Anonymous
Other sponsors
~~~~~~~~~~~~~~
--------------
- AP M
- Alessandro Balzano
@@ -263,7 +90,6 @@ Other sponsors
- Julie Engel
- Jörg Behrmann
- Jørgen Skancke
- Kevin Kainan Li
- Kevin Velghe
- Konstantin Shmelkov
- Kyle Frazer

View File

@@ -1,304 +0,0 @@
Frequently asked questions
==========================
:title: Frequently asked questions
The Compiler <mail@qutebrowser.org>
[qanda]
What is qutebrowser based on?::
qutebrowser uses http://www.python.org/[Python], http://qt.io/[Qt] and
http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
+
The concept of it is largely inspired by http://portix.bitbucket.org/dwb/[dwb]
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
key bindings are similar to dwb.
Why another browser?::
It might be hard to believe, but I didn't find any browser which I was
happy with, so I started to write my own. Also, I needed a project to get
into writing GUI applications with Python and
link:http://qt.io/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
+
Read the next few questions to find out why I was unhappy with existing
software.
What's wrong with link:http://portix.bitbucket.org/dwb/[dwb]/link:http://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/link:http://pwmt.org/projects/jumanji/[jumanji]/... (projects based on WebKitGTK)?::
Most of them are based on the http://webkitgtk.org/[WebKitGTK+]
http://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
which causes a lot of crashes. As the GTK API using WebKit1 is
https://lists.webkit.org/pipermail/webkit-gtk/2014-March/001821.html[deprecated],
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]).
+
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.
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
slower and more bloated with every upgrade, and has some
https://blog.mozilla.org/advancingcontent/2014/02/11/publisher-transformation-with-users-at-the-center/[horrible
ideas] lately.
+
Also, developing addons for it is a nightmare.
What's wrong with http://www.chromium.org/Home[Chromium] and https://vimium.github.io/[Vimium]?::
The Chrome plugin API doesn't seem to allow much freedom for plugin
writers, which results in Vimium not really having all the features you'd
expect from a proper minimal, vim-like browser.
Why Python?::
I enjoy writing Python since 2011, which made it one of the possible
choices. I wanted to use http://qt.io/[Qt] because of
http://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
http://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
like C++ and can't write it very well, so that wasn't an alternative.
But isn't Python too slow for a browser?::
http://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
I believe efficiency while coding is a lot more important than efficiency
while running. Also, most of the heavy lifting of qutebrowser is done by Qt
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
http://www.reddit.com/r/programming/comments/25j41u/adblock_pluss_effect_on_firefoxs_memory_usage/chhpomw[big
impact] on browsing speed and
https://blog.mozilla.org/nnethercote/2014/05/14/adblock-pluss-effect-on-firefoxs-memory-usage/[RAM
usage], so implementing support for AdBlockPlus-like lists is currently not
a priority.
How do I play Youtube videos with mpv?::
You can easily add a key binding to play youtube videos inside a real video
player - optionally even with hinting for links:
+
----
:bind m spawn mpv {url}
:bind M hint links spawn mpv {hint-url}
----
+
Note that you might need an additional package (e.g.
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
Archlinux) to play web videos with mpv.
+
There is a very useful script for mpv, which emulates "unique application"
functionality. This way you can add links to the mpv playlist instead of
playing them all at once.
+
You can find the script here: https://github.com/mpv-player/mpv/blob/master/TOOLS/umpv
+
It also works nicely with rapid hints:
+
----
:bind m spawn umpv {url}
:bind M hint links spawn umpv {hint-url}
:bind ;M hint --rapid links spawn umpv {hint-url}
----
How do I use qutebrowser with mutt?::
Due to a Qt limitation, local files without `.html` extensions are
"downloaded" instead of displayed, see
https://github.com/qutebrowser/qutebrowser/issues/566[#566]. You can work
around this by using this in your `mailcap`:
+
----
text/html; mv %s %s.html && qutebrowser %s.html >/dev/null 2>/dev/null; needsterminal;
----
What is the difference between bookmarks and quickmarks?::
Bookmarks will always use the title of the website as their name, but with quickmarks
you can set your own title.
+
For example, if you bookmark multiple food recipe websites and use `:open`,
you have to type the title or address of the website.
+
When using quickmark, you can give them all names, like
`foodrecipes1`, `foodrecipes2` and so on. When you type
`:open foodrecipes`, you will see a list of all the food recipe sites,
without having to remember the exact website title or address.
How do I use spell checking?::
Configuring spell checking in qutebrowser depends on the backend in use
(see https://github.com/qutebrowser/qutebrowser/issues/700[#700] for
a more detailed discussion).
+
For QtWebKit:
. Install https://github.com/QupZilla/qtwebkit-plugins[qtwebkit-plugins].
. Note: with QtWebKit reloaded you may experience some issues. See
https://github.com/QupZilla/qtwebkit-plugins/issues/10[#10].
. The dictionary to use is taken from the `DICTIONARY` environment variable.
The default is `en_US`. For example to use Dutch spell check set `DICTIONARY`
to `nl_NL`; you can't use multiple dictionaries or change them at runtime at
the moment.
(also see the README file for `qtwebkit-plugins`).
. Remember to install the hunspell dictionaries if you don't have them already
(most distros should have packages for this).
+
For QtWebEngine:
. Make sure your versions of PyQt and Qt are 5.8 or higher.
. Use `dictcli.py` script to install dictionaries.
Run the script with `-h` for the parameter description.
. Set `spellcheck.languages` to the desired list of languages, e.g.:
`:set spellcheck.languages "['en-US', 'pl-PL']"`
How do I use Tor with qutebrowser?::
Start tor on your machine, and do `:set content.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.
Why does J move to the next (right) tab, and K to the previous (left) one?::
One reason is because https://bitbucket.org/portix/dwb[dwb] did it that way,
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`).
What's the difference between insert and passthrough mode?::
They are quite similar, but insert mode has some bindings (like `Ctrl-e` to
open an editor) while passthrough mode only has escape bound. It might also
be useful to rebind escape to something else in passthrough mode only, to be
able to send an escape keypress to the website.
Why takes it longer to open an URL in qutebrowser than in chromium?::
When opening an URL in an existing instance the normal qutebrowser
Python script is started and a few PyQt libraries need to be
loaded until it is detected that there is an instance running
where the URL is then passed to. This takes some time.
One workaround is to use this
https://github.com/qutebrowser/qutebrowser/blob/master/scripts/open_url_in_instance.sh[script]
and place it in your $PATH with the name "qutebrowser". This
script passes the URL via an unix socket to qutebrowser (if its
running already) using socat which is much faster and starts a new
qutebrowser if it is not running already. Also check if you want
to use webengine as backend in line 17 and change it to your
needs.
How do I make qutebrowser use greasemonkey scripts?::
There is currently no UI elements to handle managing greasemonkey scripts.
All management of what scripts are installed or disabled is done in the
filesystem by you. qutebrowser reads all files that have an extension of
`.js` from the `<data>/greasemonkey/` folder and attempts to load them.
Where `<data>` is the qutebrowser data directory shown in the `Paths`
section of the page displayed by `:version`. If you want to disable a
script just rename it, for example, to have `.disabled` on the end, after
the `.js` extension. To reload scripts from that directory run the command
`:greasemonkey-reload`.
+
Troubleshooting: to check that your script is being loaded when
`:greasemonkey-reload` runs you can start qutebrowser with the arguments
`--debug --logfilter greasemonkey,js` and check the messages on the
program's standard output for errors parsing or loading your script.
You may also see javascript errors if your script is expecting an environment
that we fail to provide.
+
Note that there are some missing features which you may run into:
. Some scripts expect `GM_xmlhttpRequest` to ignore Cross Origin Resource
Sharing restrictions, this is currently not supported, so scripts making
requests to third party sites will often fail to function correctly.
. If your backend is a QtWebEngine version 5.8, 5.9 or 5.10 then regular
expressions are not supported in `@include` or `@exclude` rules. If your
script uses them you can re-write them to use glob expressions or convert
them to `@match` rules.
See https://wiki.greasespot.net/Metadata_Block[the wiki] for more info.
. Any greasemonkey API function to do with adding UI elements is not currently
supported. That means context menu extentensions and background pages.
== Troubleshooting
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`
in qutebrowser will enable plugins. Packages for flash should
be provided for your platform or it can be obtained from
http://get.adobe.com/flashplayer/[Adobe].
Experiencing freezing on sites like duckduckgo and youtube.::
This issue could be caused by stale plugin files installed by `mozplugger`
if mozplugger was subsequently removed.
Try exiting qutebrowser and removing `~/.mozilla/plugins/mozplugger*.so`.
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
for more details.
When using QtWebEngine, qutebrowser reports "Render Process Crashed" and the console prints a traceback on Gentoo Linux or another Source-Based Distro::
As stated in https://gcc.gnu.org/gcc-6/changes.html[GCC's Website] GCC 6 has introduced some optimizations that could break non-conforming codebases, like QtWebEngine. +
As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +
On gentoo, you just need to add it into your make.conf, like this: +
CFLAGS="... -fno-delete-null-pointer-checks"
CXXFLAGS="... -fno-delete-null-pointer-checks"
+
And then re-emerging qtwebengine with: +
emerge -1 qtwebengine
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
information.

File diff suppressed because it is too large Load Diff

View File

@@ -1,458 +0,0 @@
Configuring qutebrowser
=======================
IMPORTANT: qutebrowser's configuration system was completely rewritten in
September 2017. This information is not applicable to older releases, and older
information elsewhere might be outdated.
qutebrowser's config files
--------------------------
qutebrowser releases before v1.0.0 had a `qutebrowser.conf` and `keys.conf`
file. Those are not used anymore since that release - see
<<migrating,"Migrating older configurations">> for information on how to
migrate to the new config.
When using `:set` and `:bind`, changes are saved to an `autoconfig.yml` file
automatically. If you don't want to have a config file which is curated by
hand, you can simply use those - see
<<autoconfig,"Configuring qutebrowser via the user interface">> for details.
For more advanced configuration, you can write a `config.py` file - see
<<configpy,"Configuring qutebrowser via config.py">>. As soon as a `config.py`
exists, the `autoconfig.yml` file **is not read anymore** by default. You need
to <<configpy-autoconfig,load it by hand>> if you want settings done via
`:set`/`:bind` to still persist.
[[autoconfig]]
Configuring qutebrowser via the user interface
----------------------------------------------
The easy (but less flexible) way to configure qutebrowser is using its user
interface or command line. Changes you make this way are immediately active
(with the exception of a few settings, where this is pointed out in the
documentation) and are persisted in an `autoconfig.yml` file.
The `autoconfig.yml` file is located in the "config" folder listed on the
link:qute://version[] page. On macOS, the "auto config" folder is used, which is
different from where hand-written config files are kept.
However, **do not** edit `autoconfig.yml` by hand. Instead, see the next
section.
If you want to customize many settings, you can open the link:qute://settings[]
page by running `:set` without any arguments, where all settings are listed and
customizable.
Using the link:commands.html#set[`:set`] command and command completion, you
can quickly set settings interactively, for example `:set tabs.position left`.
Some settings are also customizable for a given
https://developer.chrome.com/apps/match_patterns[URL pattern] by doing e.g.
`:set --pattern=*://example.com/ content.images false`.
To get more help about a setting, use e.g. `:help tabs.position`.
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
link:commands.html#unbind[`:unbind`] commands:
- Binding the key chain `,v` to the `:spawn mpv {url}` command:
`:bind ,v spawn mpv {url}`
- Unbinding the same key chain: `:unbind ,v`
Key chains starting with a comma are ideal for custom bindings, as the comma key
will never be used in a default keybinding.
See the help pages linked above (or `:help :bind`, `:help :unbind`) for more
information.
Other useful commands for config manipulation are
link:commands.html#config-unset[`:config-unset`] to reset a value to its default,
link:commands.html#config-clear[`:config-clear`] to reset the entire configuration,
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
different values.
[[configpy]]
Configuring qutebrowser via config.py
-------------------------------------
For more powerful configuration possibilities, you can create a `config.py`
file. Since it's a Python file, you have much more flexibility for
configuration. Note that qutebrowser will never touch this file - this means
you'll be responsible for updating it when upgrading to a newer qutebrowser
version.
You can run `:config-edit` inside qutebrowser to open the file in your editor,
`:config-source` to reload the file (`:config-edit` does this automatically), or
`:config-write-py --defaults` to write a template file to work with.
The file should be located in the "config" location listed on
link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
Linux, `~/.qutebrowser/config.py` on macOS, and
`%APPDATA%/qutebrowser/config.py` on Windows.
Two global objects are pre-defined when running `config.py`: `c` and `config`.
Changing settings
~~~~~~~~~~~~~~~~~
While you can set settings using the `config.set()` method (which is explained
in the next section), it's easier to use the `c` shorthand object to easily set
settings like this:
.config.py:
[source,python]
----
c.tabs.position = "left"
c.completion.shrink = True
----
Note that qutebrowser does some Python magic so it's able to warn you about
mistyped config settings. As an example, if you do `c.tabs.possition = "left"`,
you'll get an error when starting.
See the link:settings.html[settings help page] for all available settings. The
accepted values depend on the type of the option. Commonly used are:
- Strings: `c.tabs.position = "left"`
- Booleans: `c.completion.shrink = True`
- Integers: `c.messages.timeout = 5000`
- Dictionaries:
* `c.headers.custom = {'X-Hello': 'World', 'X-Awesome': 'yes'}` to override
any other values in the dictionary.
* `c.aliases['foo'] = 'message-info foo'` to add a single value.
- Lists:
* `c.url.start_pages = ["https://www.qutebrowser.org/"]` to override any
previous elements.
* `c.url.start_pages.append("https://www.python.org/")` to add a new value.
Any other config types (e.g. a color) are specified as a string. The only
exception is the `Regex` type, which can take either a string (with an `r`
prefix to preserve backslashes) or a Python regex object:
- `c.hints.next_regexes.append(r'\bvor\b')`
- `c.hints.prev_regexes.append(re.compile(r'\bzurück\b'))`
If you want to read a setting, you can use the `c` object to do so as well:
`c.colors.tabs.even.bg = c.colors.tabs.odd.bg`.
Using strings for setting names
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to set settings based on their name as a string, use the
`config.set` method:
.config.py:
[source,python]
----
# Equivalent to:
# c.content.javascript.enabled = False
config.set('content.javascript.enabled', False)
----
To read a setting, use the `config.get` method:
[source,python]
----
# Equivalent to:
# color = c.colors.completion.fg
color = config.get('colors.completion.fg')
----
Per-domain settings
~~~~~~~~~~~~~~~~~~~
Using `config.set`, some settings are also customizable for a given
https://developer.chrome.com/apps/match_patterns[URL pattern]:
[source,python]
----
config.set('content.images', False, '*://example.com/')
----
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
similar to `c.` which is scoped to the given domain:
[source,python]
----
with config.pattern('*://example.com/') as p:
p.content.images = False
----
Binding keys
~~~~~~~~~~~~
While it's possible to change the `bindings.commands` setting to bind keys, it's
preferred to use the `config.bind` command. Doing so ensures the commands are
valid and normalizes different expressions which map to the same key.
For details on how to specify keys and the available modes, see the
link:settings.html#bindings.commands[documentation] for the `bindings.commands`
setting.
To bind a key:
.config.py:
[source,python]
----
config.bind('<Ctrl-v>', 'spawn mpv {url}')
----
To bind a key in a mode other than `'normal'`, add a `mode` argument:
[source,python]
----
config.bind('<Ctrl-y>', 'prompt-yes', mode='prompt')
----
To unbind a key (either a key which has been bound before, or a default binding):
[source,python]
----
config.unbind('<Ctrl-v>', mode='normal')
----
To bind keys without modifiers, specify a key chain to bind as a string. Key
chains starting with a comma are ideal for custom bindings, as the comma key
will never be used in a default keybinding.
[source,python]
----
config.bind(',v', 'spawn mpv {url}')
----
To suppress loading of any default keybindings, you can set
`c.bindings.default = {}`.
[[configpy-autoconfig]]
Loading `autoconfig.yml`
~~~~~~~~~~~~~~~~~~~~~~~~
All customization done via the UI (`:set`, `:bind` and `:unbind`) is
stored in the `autoconfig.yml` file, which is not loaded automatically as soon
as a `config.py` exists. If you want those settings to be loaded, you'll need to
explicitly load the `autoconfig.yml` file in your `config.py` by doing:
.config.py:
[source,python]
----
config.load_autoconfig()
----
If you do so at the top of your file, your `config.py` settings will take
precedence as they overwrite the settings done in `autoconfig.yml`.
Importing other modules
~~~~~~~~~~~~~~~~~~~~~~~
You can import any module from the
https://docs.python.org/3/library/index.html[Python standard library] (e.g.
`import os.path`), as well as any module installed in the environment
qutebrowser is run with.
If you have an `utils.py` file in your qutebrowser config folder, you can import
that via `import utils` as well.
While it's in some cases possible to import code from the qutebrowser
installation, doing so is unsupported and discouraged.
To read config data from a different file with `c` and `config` available, you
can use `config.source('otherfile.py')` in your `config.py`.
Getting the config directory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to get the qutebrowser config directory, you can do so by reading
`config.configdir`. Similarly, you can get the qutebrowser data directory via
`config.datadir`.
This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path`
object], on which you can use `/` to add more directory parts, or `str(...)` to
get a string:
.config.py:
[source,python]
----
print(str(config.configdir / 'config.py'))
----
Handling errors
~~~~~~~~~~~~~~~
If there are errors in your `config.py`, qutebrowser will try to apply as much
of it as possible, and show an error dialog before starting.
qutebrowser tries to display errors which are easy to understand even for people
who are not used to writing Python. If you see a config error which you find
confusing or you think qutebrowser could handle better, please
https://github.com/qutebrowser/qutebrowser/issues[open an issue]!
Recipes
~~~~~~~
Reading a YAML file
^^^^^^^^^^^^^^^^^^^
To read a YAML config like this:
.config.yml:
----
tabs.position: left
tabs.show: switching
----
You can use:
.config.py:
[source,python]
----
import yaml
with (config.configdir / 'config.yml').open() as f:
yaml_data = yaml.load(f)
for k, v in yaml_data.items():
config.set(k, v)
----
Reading a nested YAML file
^^^^^^^^^^^^^^^^^^^^^^^^^^
To read a YAML file with nested values like this:
.colors.yml:
----
colors:
statusbar:
normal:
bg: lime
fg: black
url:
fg: red
----
You can use:
.config.py:
[source,python]
----
import yaml
with (config.configdir / 'colors.yml').open() as f:
yaml_data = yaml.load(f)
def dict_attrs(obj, path=''):
if isinstance(obj, dict):
for k, v in obj.items():
yield from dict_attrs(v, '{}.{}'.format(path, k) if path else k)
else:
yield path, obj
for k, v in dict_attrs(yaml_data):
config.set(k, v)
----
Note that this won't work for values which are dictionaries.
Binding chained commands
^^^^^^^^^^^^^^^^^^^^^^^^
If you have a lot of chained commands you want to bind, you can write a helper
to do so:
[source,python]
----
def bind_chained(key, *commands):
config.bind(key, ' ;; '.join(commands))
bind_chained('<Escape>', 'clear-keychain', 'search')
----
Reading colors from Xresources
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can use something like this to read colors from an `~/.Xresources` file:
[source,python]
----
import subprocess
def read_xresources(prefix):
props = {}
x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE)
lines = x.stdout.decode().split('\n')
for line in filter(lambda l : l.startswith(prefix), lines):
prop, _, value = line.partition(':\t')
props[prop] = value
return props
xresources = read_xresources('*')
c.colors.statusbar.normal.bg = xresources['*.background']
----
Pre-built colorschemes
^^^^^^^^^^^^^^^^^^^^^^
- A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager].
- Two implementations of the https://github.com/arcticicestudio/nord[Nord] colorscheme for qutebrowser exist: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon]
Avoiding flake8 errors
^^^^^^^^^^^^^^^^^^^^^^
If you use an editor with flake8 and pylint integration, it may have some
complaints about invalid names, undefined variables, or missing docstrings.
You can silence those with:
[source,python]
----
# pylint: disable=C0111
c = c # noqa: F821 pylint: disable=E0602,C0103
config = config # noqa: F821 pylint: disable=E0602,C0103
----
For type annotation support (note that those imports aren't guaranteed to be
stable across qutebrowser versions):
[source,python]
----
# pylint: disable=C0111
from qutebrowser.config.configfiles import ConfigAPI # noqa: F401
from qutebrowser.config.config import ConfigContainer # noqa: F401
config = config # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
c = c # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103
----
[[migrating]]
Migrating older configurations
------------------------------
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff/old[configdiff] page
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
did in your old configuration, compared to the old defaults.
Other changes in default settings:
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
if no text was entered yet.
With v1.0.x, they always navigate through command history instead of selecting
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
instead.
You can get back the old behavior by doing:
+
----
:bind -m command <Up> completion-item-focus prev
:bind -m command <Down> completion-item-focus next
----
+
or always navigate through command history with
+
----
:bind -m command <Up> command-history-prev
:bind -m command <Down> command-history-next
----
- The default for `completion.web_history_max_items` is now set to `-1`, showing
an unlimited number of items in the completion for `:open` as the new
sqlite-based completion is much faster. If the `:open` completion is too slow
on your machine, set an appropriate limit again.

View File

@@ -7,13 +7,12 @@ Documentation
The following help pages are currently available:
* link:../quickstart.html[Quick start guide]
* link:../faq.html[Frequently asked questions]
* link:../changelog.html[Change Log]
* link:../../FAQ.html[Frequently asked questions]
* link:../../CHANGELOG.html[Change Log]
* link:commands.html[Documentation of commands]
* link:configuring.html[Configuring qutebrowser]
* link:settings.html[Documentation of settings]
* link:../userscripts.html[How to write userscripts]
* link:../contributing.html[Contributing to qutebrowser]
* link:../../CONTRIBUTING.html[Contributing to qutebrowser]
Getting help
------------

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,471 +0,0 @@
Installing qutebrowser
======================
toc::[]
NOTE: qutebrowser recently had some bigger dependency changes for v1.0.0, which
means those instructions might be out of date in some places.
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[Please help]
updating them if you notice something being broken!
On Debian / Ubuntu
------------------
How to install qutebrowser depends a lot on the version of Debian/Ubuntu you're
running.
Debian Jessie / Ubuntu 14.04 LTS / Linux Mint < 18
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Those distributions only have Python 3.4 and a too old Qt version available,
while qutebrowser requires Python 3.5 and Qt 5.7.1 or newer.
It should be possible to install Python 3.5 e.g. from the
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
If you get qutebrowser running on those distributions, please
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[contribute]
to update this documentation!
Ubuntu 16.04 LTS / Linux Mint 18
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
QtWebEngine). However, it comes with Python 3.5, so you can
<<tox,install qutebrowser via tox>>.
You'll need some basic libraries to use the tox-installed PyQt:
----
# apt install libglib2.0-0 libgl1 libfontconfig1 libx11-xcb1 libxi6 libxrender1 libdbus-1-3
----
Debian Stretch / Ubuntu 17.04 and 17.10
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Those versions come with QtWebEngine in the repositories. This makes it possible
to install qutebrowser via the Debian package.
You'll need to download three packages:
- https://packages.debian.org/sid/all/python3-pypeg2/download[PyPEG2] (a library
used by qutebrowser which is not in the earlier repositories)
- https://packages.debian.org/sid/all/qutebrowser/download[qutebrowser] itself
- Either https://packages.debian.org/sid/all/qutebrowser-qtwebengine/download[qutebrowser-qtwebengine]
or https://packages.debian.org/sid/all/qutebrowser-qtwebkit/download[qutebrowser-qtwebkit]
(or both) depending on the backend you want to use. QtWebEngine is the
default/recommended choice.
After downloading, install the packages (make sure to install all the
downloaded qutebrowser deb files in one apt command):
----
# apt install ./python3-pypeg2_*_all.deb
# apt install ./qutebrowser*.deb
----
For an update after the initial install, you only need to download/install the
qutebrowser package.
Debian Testing / Ubuntu 18.04
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On Debian Testing, qutebrowser is in the official repositories, and you can
install it with apt:
----
# apt install qutebrowser
----
Additional hints
~~~~~~~~~~~~~~~~
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
QtWebEngine version.
- If running from git, run the following to generate the documentation for the
`:help` command:
+
----
# apt install --no-install-recommends asciidoc source-highlight
$ python3 scripts/asciidoc2html.py
----
- If you prefer using QtWebKit, there's an up-to-date version available in
https://packages.debian.org/buster/libqt5webkit5[Debian Testing].
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
+
----
# apt install gstreamer1.0-plugins-{bad,base,good,ugly}
----
On Fedora
---------
NOTE: Fedora's packages used to be outdated for a long time, but are
now (November 2017) maintained and up-to-date again.
qutebrowser is available in the official repositories:
-----
# dnf install qutebrowser
-----
However, note that Fedora 25/26 won't be updated to qutebrowser v1.0, so you
might want to <<tox,install qutebrowser via tox>> instead there.
Additional hints
~~~~~~~~~~~~~~~~
Fedora only ships free software in the repositories.
To be able to play videos with proprietary codecs with QtWebEngine, you will
need to install an additional package from the RPM Fusion Free repository.
For more information see https://rpmfusion.org/Configuration.
-----
# dnf install qt5-qtwebengine-freeworld
-----
On Archlinux
------------
qutebrowser is available in the official [community] repository.
----
# pacman -S qutebrowser
----
There is also a -git version available in the AUR:
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
You can install it using `makepkg` like this:
----
$ git clone https://aur.archlinux.org/qutebrowser-git.git
$ cd qutebrowser-git
$ makepkg -si
$ cd ..
$ rm -r qutebrowser-git
----
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
----
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
----
On Gentoo
---------
NOTE: Gentoo's packages used to be severely outdated for a long time, but are
now (October 2017) maintained and up-to-date again.
qutebrowser is available in the main repository and can be installed with:
----
# emerge -av qutebrowser
----
To use QtWebKit instead of QtWebEngine, you'll need a newer QtWebKit using
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
If video or sound don't work with QtWebKit, try installing the gstreamer
plugins:
----
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
----
To be able to play videos with proprietary codecs with QtWebEngine, you will
need to turn off the `bindist` flag for `dev-qt/qtwebengine`.
See the https://wiki.gentoo.org/wiki/Qutebrowser#USE_flags[Gentoo Wiki] for
more information.
On Void Linux
-------------
qutebrowser is available in the official repositories and can be installed
with:
----
# xbps-install qutebrowser
----
It's currently recommended to install `python3-PyQt5-webengine` and
`python3-PyQt5-opengl`, then start with `--backend webengine` to use the new
backend.
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
On NixOS
--------
Nixpkgs collection contains `pkgs.qutebrowser` since June 2015. You can install
it with:
----
$ nix-env -i qutebrowser
----
It's recommended to install `qt5.qtwebengine` and start with
`--backend webengine` to use the new backend.
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
On openSUSE
-----------
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
On OpenBSD
----------
WARNING: OpenBSD only packages a legacy unmaintained version of QtWebKit (for
which support was dropped in qutebrowser v1.0). It's advised to not use
qutebrowser from OpenBSD ports for untrusted websites.
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
Install the package:
----
# pkg_add qutebrowser
----
Or alternatively, use the ports system :
----
# cd /usr/ports/www/qutebrowser
# make install
----
On FreeBSD
----------
qutebrowser is in https://www.freshports.org/www/qutebrowser/[FreeBSD ports].
It can be installed with:
----
# cd /usr/ports/www/qutebrowser
# make install clean
----
At present, precompiled packages are not available for this port,
and QtWebEngine backend is also not available.
On Windows
----------
There are different ways to install qutebrowser on Windows:
Prebuilt binaries
~~~~~~~~~~~~~~~~~
Prebuilt standalone packages and installers
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
release.
Note that you'll need to upgrade to new versions manually (subscribe to the
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce
mailinglist] to get notified on new releases). You can install a newer version
without uninstalling the older one.
The binary release ships with a QtWebEngine built without proprietary codec
support. To get support for e.g. h264/h265 videos, you'll need to build
QtWebEngine from source yourself with support for that enabled.
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* PackageManagement PowerShell module
----
PS C:\> Install-Package qutebrowser
----
* Chocolatey's client
----
C:\> choco install qutebrowser
----
* Scoop's client
----
C:\> scoop bucket add extras
C:\> scoop install qutebrowser
----
Manual install
~~~~~~~~~~~~~~
* Use the installer from http://www.python.org/downloads[python.org] to get
Python 3 (be sure to install pip).
* Install https://testrun.org/tox/latest/index.html[tox] via
https://pip.pypa.io/en/latest/[pip]:
----
$ pip install tox
----
Then <<tox,install qutebrowser via tox>>.
On macOS
--------
Prebuilt binary
~~~~~~~~~~~~~~~
The easiest way to install qutebrowser on macOS is to use the prebuilt `.app`
files from the
https://github.com/qutebrowser/qutebrowser/releases[release page].
Note that you'll need to upgrade to new versions manually (subscribe to the
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrowser-announce
mailinglist] to get notified on new releases).
The binary release ships with a QtWebEngine built without proprietary codec
support. To get support for e.g. h264/h265 videos, you'll need to build
QtWebEngine from source yourself with support for that enabled.
This binary is also available through the
https://caskroom.github.io/[Homebrew Cask] package manager:
----
$ brew cask install qutebrowser
----
Manual Install
~~~~~~~~~~~~~~
Alternatively, you can install the dependencies via a package manager (like
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]) and run
qutebrowser from source.
==== Homebrew
----
$ brew install qt5
$ pip3 install qutebrowser
----
Since the v1.0 release, qutebrowser uses QtWebEngine by default.
Homebrew's builds of Qt and PyQt don't come with QtWebKit (and `--with-qtwebkit`
uses an old version of QtWebKit which qutebrowser doesn't support anymore). If
you want QtWebKit support, you'll need to build an up-to-date QtWebKit
https://github.com/annulen/webkit/wiki/Building-QtWebKit-on-OS-X[manually].
Packagers
---------
There are example .desktop and icon files provided. They would go in the
standard location for your distro (`/usr/share/applications` and
`/usr/share/pixmaps` for example).
The normal `setup.py install` doesn't install these files, so you'll have to do
it as part of the packaging process.
[[tox]]
Installing qutebrowser with tox
-------------------------------
Getting the repository
~~~~~~~~~~~~~~~~~~~~~~
First of all, clone the repository using http://git-scm.org/[git] and switch
into the repository folder:
----
$ git clone https://github.com/qutebrowser/qutebrowser.git
$ cd qutebrowser
----
Installing dependencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
----
$ tox -e mkvenv-pypi
----
If your system comes with Python 3.5.3 or older (such as Ubuntu 16.04 LTS), use
`tox -e mkvenv-pypi-old` instead. This installs an older Qt version (5.10) due
to bugs in newer versions.
This installs all needed Python dependencies in a `.venv` subfolder.
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few
caveats:
- Make sure your `python3` is Python 3.5 or newer, otherwise you'll get a "No
matching distribution found" error. Note that qutebrowser itself also requires
this.
- It only works on 64-bit x86 systems, with other architectures you'll get the
same error.
- If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux),
you'll need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
qutebrowser if you want SSL to work in certain downloads (e.g. for
`:adblock-update` or `:download`).
- It comes with a QtWebEngine compiled without proprietary codec support (such
as h.264).
See the next section for an alternative.
Installing dependencies (system-wide Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
local Qt install instead of installing PyQt in the virtualenv. However, unless
you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It
also typically means you'll be using an older release of QtWebEngine.
On Windows, run `set PYTHON=C:\path\to\python.exe` (CMD) or ``$Env:PYTHON =
"..."` (Powershell) first.
Creating a wrapper script
~~~~~~~~~~~~~~~~~~~~~~~~~
Running `tox` does not install a system-wide `qutebrowser` script. You can
launch qutebrowser by doing:
----
.venv/bin/python3 -m qutebrowser
----
You can create a simple wrapper script to start qutebrowser somewhere in your
`$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
----
#!/bin/bash
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
----
Building the docs
~~~~~~~~~~~~~~~~~
To build the documentation, install `asciidoc` (note that LaTeX which comes as
optional/recommended dependency with some distributions is not required).
Then, run:
----
$ python3 scripts/asciidoc2html.py
----
Updating
~~~~~~~~
When you updated your local copy of the code (e.g. by pulling the git repo, or
extracting a new version), the virtualenv should automatically use the updated
code. However, if dependencies got added, this won't be reflected in the
virtualenv. Thus it's recommended to run the following command to recreate the
virtualenv:
----
$ tox -r -e mkvenv-pypi
----

196
doc/notes Normal file
View File

@@ -0,0 +1,196 @@
henk's thoughts
===============
1. Power to the user! Protect privacy!
Things the browser should only do with explicit consent from the user, if
applicable the user should be able to choose which protocol/host/port triplets
to white/blacklist:
- load/run executable code, like js, flash, java applets, ... (think NoScript)
- requests to other domains, ports or using a different protocol than what the
user requested (think RequestPolicy)
- accept cookies
- storing/saving/caching things, e.g. open tabs ("session"), cookies, page
contents, browsing/download history, form data, ...
- send referrer
- disclose any (presence, type, version, settings, capabilities, etc.)
information about OS, browser, installed fonts, plugins, addons, etc.
2. Be efficient!
I tend to leave a lot of tabs open and nobody can deny that some websites
simply suck, so the browser should, unless told otherwise by the user:
- load tabs only when needed
- run code in tabs only when needed, i.e. when the tab is currently being
used/viewed (background tabs doing some JS magic even when they are not being
used can create a lot of unnecessary load on the machine)
- finish requests to the domain the user requested (e.g. www.example.org)
before doing any requests to other subdomains (e.g. images.example.org) and
finish those before doing requests to thirdparty domains (e.g. example.com)
3. Be stable!
- one site should not make the complete browser crash, only that site's tab
Upstream Bugs
=============
- Web inspector is blank unless .hide()/.show() is called.
Asked on SO: http://stackoverflow.com/q/23499159/2085149
TODO: Report to PyQt/Qt
- Report some other crashes
/u/angelic_sedition's thoughts
==============================
Well support for greasemonkey scripts and bookmarklets/js (which was mentioned
in the arch forum post) would be a big addition. What I've usually missed when
using other vim-like browsers is things that allow for different settings and
key bindings for different contexts. With that implemented I think I could
switch to a lightweight browser (and believe me, I'd like to) for the most part
and only use firefox when I needed downthemall or something.
For example, I have different bindings based on tab position that are reloaded
with a pentadactyl autocmd so that <space><homerow keys> will take me to tab
1-10 if I'm in that range or 2-20 if I'm in that range. I have an autocmd that
will run on completed downloads that passes the file path to a script that will
open ranger in a floating window with that file cut (this is basically like
using ranger to save files instead of the crappy gui popup).
I also have a few bindings based on tabgroups. Tabgroups are a firefox feature,
but I find them very useful for sorting things by topic so that only the tabs
I'm interested at the moment are visible.
Pentadactyl has a feature it calls groups. You can create a group that will
activate for sites/urls that match a pattern with some regex support. This
allows me, for example, to set up different (more convenient) bindings for
zooming only on images. I'll never need use the equivalent of vim n (next text
search match), so I can bind that to zoom. This allows setting up custom
quickmarks/gotos using the same keys for different websites. For example, on
reddit I have different g(some key) bindings to go to different subreddits.
This can also be used to pass certain keys directly to the site (e.g. for use
with RES). For sites that don't have modifiable bindings, I can use this with
pentadactyl's feedkeys or xdotool to create my own custom bindings. I even have
a binding that will call out to bash script with different arguments depending
on the site to download an image or an image gallery depending on the site (in
some cases passing the url to some cli program).
I've also noticed the lack of completion. For example, on "o" pentadactyl will
show sites (e.g. from history) that can be completed. I think I've been spoiled
by pentadactyl having completion for just about everything.
suckless surf ML post
=====================
From: Ben Woolley <tautolog_AT_gmail.com>
Date: Wed, 7 Jan 2015 18:29:25 -0800
Hi all,
This patch is a bit of a beast for surf. It is intended to be applied after
the disk cache patch. It breaks some internal interfaces, so it could
conflict with other patches.
I have been wanting a browser to implement a complete same-origin policy,
and have been investigating how to do this in various browsers for many
months. When I saw how surf opened new windows in a separate process, and
was so simple, I knew I could do it quickly. Over the last two weeks, I
have been developing this implementation on surf.
The basic idea is to prevent browser-based tracking as you browse from site
to site, or origin to origin. By "origin" domain, I mean the "first-party"
domain, the domain normally in the location bar (of the typical browser
interface). Each origin domain effectively gets its own browser profile,
and a browser process only ever deals with one origin domain at a time.
This isolates origins vertically, preventing cookies, disk cache, memory
cache, and window.name vulnerabilities. Basically, all known
vulnerabilities that google and Mozilla cite as counter-examples when they
explain why they haven't disabled third-party cookies yet.
When you are on msnbc.com, the tracking pixels will be stored in a cookie
file for msnbc.com. When you go to cnn.com, the tracking pixels will be
stored in a cookie file for cnn.com. You will not be tracked between them.
However, third-party cookies, and the caching of third party resources will
still work, but they will be isolated between origin domains. Instead of
blocking cookies and cache entries, they are "double-keyed", or *also*
keyed by origin.
There is a unidirectional communication channel, however, from one origin
to the next, through navigation from one origin to the next. That is, the
query string is passed from one origin to the next, and may embed
identifiers. One example is an affiliate link that identifies where the
lead came from. I have implemented what I call "horizontal isolation", in
the form of an "Origin Crossing Gate".
Whenever you follow a link to a new domain, or even are just redirected to
a new domain, a new window/tab is opened, and passed the referring origin
via -R. The page passed to -O, for example -O originprompt.html, is an HTML
page that is loaded in the new origin's context. That page tells you the
origin you were on, the new origin, and the full link, and you can decide
to go just to the new origin, or go to the full URL, after reviewing it for
tracking data.
Also, you may click links that store your trust of that relationship with
various expiration times, the same way you would trust geolocation requests
for a particular origin for a period of time. The database used is actually
the new origin's cookie file. Since the origin prompt is loaded in the new
origin's context, I can set a cookie on behalf of the new origin. The
expiration time of the trust is the expiration time of the cookie. The
cookie implementation in webkit automatically expires the trust as part of
how cookies work. Each time you cross an origin, the origin crossing page
checks the cookie to see if trust is still established. If so, it will use
window.location.replace() to continue on automatically. The initial page
renders blank until the trust is invalidated, in which case the content of
the gate is made visible.
However, the new origin is technically able to mess with those cookies, so
a website could set trust for an origin crossing. I have addressed that by
hashing the key with a salt, and setting the real expiration time as the
value, along with an HMAC to verify the contents of the value. If the
cookie is messed with in any way, the trust will be disabled, and the
prompt will appear again. So it has a fail-safe function.
I know it seems a bit convoluted, but it just started out as a nice little
rabbit hole, and I just wanted to get something workable. At first I
thought using the cookie expiration time was convenient, but then when I
realized that I needed to protect the cookie, things got a bit hairy. But
it works.
Each profile is, by default, stored in ~/.surf/origins/$origin/
The interesting side effect is that if there is a problem where a website
relies on the cross-site cookie vulnerability to make a connection, you can
simply make a symbolic link from one origin folder to another, and they
will share the same profile. And if you want to delete cookies and/or cache
for a particular origin, you just rm -rf the origin's profile folder, and
don't have to interfere with your other sites that are working just fine.
One thing I don't handle are cross-origins POSTs. They just end up as GET
requests right now. I intend to do something about that, but I haven't
figured that out yet.
I have only been using this functionality for a few days myself, so I have
absolutely no feedback yet. I wanted to provide the first implementation of
the management of identity as a system resource the same way that things
like geolocation, camera, and microphone resources are managed in browsers
and mobile apps.
Currently, Mozilla and Tor have are working on third-party tracking issues
in Firefox.
https://blog.mozilla.org/privacy/2014/11/10/introducing-polaris-privacy-initiative-to-accelerate-user-focused-privacy-online/
Up to this point, Tor has provided a patch that double-keys cookies with
the origin domain, but no other progress is visible. I have seen no
discussion of how horizontal isolation is supposed to happen, and I wanted
to show people that it can be done, and this is one way it can be done, and
to compel the other browser makers to catch up, and hopefully the community
can work toward a standard *without* the tracking loopholes, by showing
people what a *complete* solution looks like.
Thank you,
Ben Woolley
Patch: http://lists.suckless.org/dev/att-25070/0005-same-origin-policy.patch

View File

@@ -22,16 +22,16 @@ Basic keybindings to get you started
What to do now
--------------
* View the link:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[key binding cheatsheet]
* View the link:http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
to make yourself familiar with the key bindings: +
image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"]
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
* There's also a https://www.shortcutfoo.com/app/dojos/qutebrowser[free training
course] on shortcutfoo for the keybindings - note that you need to be in
insert mode (i) for it to work.
* Run `:adblock-update` to download adblock lists and activate adblocking.
* If you just cloned the repository, you'll need to run
`scripts/asciidoc2html.py` to generate the documentation.
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it.
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. (Currently not available with the QtWebEngine backend and on the OS X build - use the `:set` command instead)
* Subscribe to
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist].

View File

@@ -17,7 +17,7 @@ qutebrowser - a keyboard-driven, vim-like browser based on PyQt5.
== DESCRIPTION
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
on Python and Qt5 and is free software, licensed under the GPL.
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
@@ -38,13 +38,13 @@ show it.
*-h*, *--help*::
show this help message and exit
*-B* 'BASEDIR', *--basedir* 'BASEDIR'::
*--basedir* 'BASEDIR'::
Base directory for all storage.
*-V*, *--version*::
Show version and quit.
*-s* 'OPTION' 'VALUE', *--set* 'OPTION' 'VALUE'::
*-s* 'SECTION' 'OPTION' 'VALUE', *--set* 'SECTION' 'OPTION' 'VALUE'::
Set a temporary setting for this session.
*-r* 'SESSION', *--restore* 'SESSION'::
@@ -57,10 +57,10 @@ show it.
How URLs should be opened if there is already a qutebrowser instance running.
*--backend* '{webkit,webengine}'::
Which backend to use.
Which backend to use (webengine backend is EXPERIMENTAL!).
*--enable-webengine-inspector*::
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details. This is not needed anymore since Qt 5.11 where the inspector is always enabled and secure.
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
=== debug arguments
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
@@ -72,7 +72,7 @@ show it.
*--loglines* 'LOGLINES'::
How many lines of the debug log to keep in RAM (-1: unlimited).
*-d*, *--debug*::
*--debug*::
Turn on debugging options.
*--json-logging*::
@@ -84,10 +84,22 @@ show it.
*--force-color*::
Force colored logging
*--harfbuzz* '{old,new,system,auto}'::
HarfBuzz engine version to use. Default: auto.
*--relaxed-config*::
Silently remove unknown config options.
*--nowindow*::
Don't show the main window.
*-T*, *--temp-basedir*::
*--debug-exit*::
Turn on debugging of late exit.
*--pdb-postmortem*::
Drop into pdb on exceptions.
*--temp-basedir*::
Use a temporary basedir.
*--no-err-windows*::
@@ -98,16 +110,13 @@ show it.
*--qt-flag* 'QT_FLAG'::
Pass an argument to Qt as flag.
*-D* 'DEBUG_FLAGS', *--debug-flag* 'DEBUG_FLAGS'::
Pass name of debugging feature to be turned on.
// QUTE_OPTIONS_END
== FILES
- '~/.config/qutebrowser/config.py': Configuration file.
- '~/.config/qutebrowser/autoconfig.yml': Configuration done via the GUI.
- '~/.config/qutebrowser/qutebrowser.conf': Main config file.
- '~/.config/qutebrowser/quickmarks': Saved quickmarks.
- '~/.config/qutebrowser/keys.conf': Defined key bindings.
- '~/.local/share/qutebrowser/': Various state information.
- '~/.cache/qutebrowser/': Temporary data.

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:
@@ -58,7 +60,7 @@ Sending commands
Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be
executed.
On Unix/macOS, this is a named pipe and commands written to it will get executed
On Unix/OS X, this is a named pipe and commands written to it will get executed
immediately.
On Windows, this is a regular file, and the commands in it will be executed as
@@ -76,11 +78,3 @@ Opening the currently selected word on http://www.dict.cc/[dict.cc]:
echo "open -t http://www.dict.cc/?s=$QUTE_SELECTED_TEXT" >> "$QUTE_FIFO"
----
Libraries
---------
Some third-party libraries are available to make writing userscripts easier:
- Python: https://github.com/hiway/python-qutescript[python-qutescript]
- Node.js: https://www.npmjs.com/package/qutejs[qutejs]

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,77 +0,0 @@
Name "qutebrowser"
Unicode true
RequestExecutionLevel admin
SetCompressor /solid lzma
!ifdef X64
OutFile "..\dist\qutebrowser-${VERSION}-amd64.exe"
InstallDir "$ProgramFiles64\qutebrowser"
!else
OutFile "..\dist\qutebrowser-${VERSION}-win32.exe"
InstallDir "$ProgramFiles\qutebrowser"
!endif
;Default installation folder
!include "MUI2.nsh"
;!include "MultiUser.nsh"
!define MUI_ABORTWARNING
;!define MULTIUSER_MUI
;!define MULTIUSER_INSTALLMODE_COMMANDLINE
!define MUI_ICON "../icons/qutebrowser.ico"
!define MUI_UNICON "../icons/qutebrowser.ico"
!insertmacro MUI_PAGE_LICENSE "..\LICENSE"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
; depends on admin status
;SetShellVarContext current
Section "Install"
; Uninstall old versions
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{633F41F9-FE9B-42D1-9CC4-718CBD01EE11}'
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{9331D947-AC86-4542-A755-A833429C6E69}'
SetOutPath "$INSTDIR"
!ifdef X64
file /r "..\dist\qutebrowser-${VERSION}-x64\*.*"
!else
file /r "..\dist\qutebrowser-${VERSION}-x86\*.*"
!endif
SetShellVarContext all
CreateShortCut "$SMPROGRAMS\qutebrowser.lnk" "$INSTDIR\qutebrowser.exe"
;Create uninstaller
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser" "DisplayName" "qutebrowser"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser" "UninstallString" '"$INSTDIR\uninst.exe"'
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser" "QuietUninstallString" '"$INSTDIR\uninst.exe" /S'
SectionEnd
;--------------------------------
;Uninstaller Section
Section "Uninstall"
SetShellVarContext all
Delete "$SMPROGRAMS\qutebrowser.lnk"
RMDir /r "$INSTDIR\*.*"
RMDir "$INSTDIR"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser"
SectionEnd

View File

@@ -15,14 +15,13 @@ def get_data_files():
('../qutebrowser/img', 'img'),
('../qutebrowser/javascript', 'javascript'),
('../qutebrowser/html/doc', 'html/doc'),
('../qutebrowser/git-commit-id', '.'),
('../qutebrowser/config/configdata.yml', 'config'),
('../qutebrowser/git-commit-id', '')
]
# if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
# data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
# else:
# print("Warning: excluding pdfjs as it's not present!")
if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
else:
print("Warning: excluding pdfjs as it's not present!")
return data_files
@@ -42,10 +41,10 @@ a = Analysis(['../qutebrowser/__main__.py'],
pathex=['misc'],
binaries=None,
datas=get_data_files(),
hiddenimports=['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0'],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=['tkinter'],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
@@ -58,19 +57,22 @@ exe = EXE(pyz,
icon=icon,
debug=False,
strip=False,
upx=False,
console=False,
version='misc/file_version_info.txt')
upx=True,
console=False )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx=True,
name='qutebrowser')
app = BUNDLE(coll,
name='qutebrowser.app',
icon=icon,
info_plist={
'NSHighResolutionCapable': 'True',
'NSSupportsAutomaticGraphicsSwitching': 'True',
},
# https://github.com/pyinstaller/pyinstaller/blob/b78bfe530cdc2904f65ce098bdf2de08c9037abb/PyInstaller/hooks/hook-PyQt5.QtWebEngineWidgets.py#L24
bundle_identifier='org.qt-project.Qt.QtWebEngineCore')

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,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
certifi==2018.4.16
chardet==3.0.4
codecov==2.0.15
coverage==4.5.1
idna==2.7
requests==2.19.1
urllib3==1.22
codecov==2.0.5
coverage==4.3.4
requests==2.13.0

View File

@@ -1,4 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.11.2
PyQt5-sip==4.19.11
cx-Freeze==4.3.4 # rq.filter: < 5.0.0

View File

@@ -0,0 +1,5 @@
cx-Freeze < 5.0.0
# We'll probably switch to PyInstaller soon, and 5.x doesn't install without a
# compiler?
#@ filter: cx-Freeze < 5.0.0

View File

@@ -1,27 +1,20 @@
# 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.1
flake8-docstrings==1.0.3
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
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
flake8-tidy-imports==1.0.5
flake8-tuple==0.2.12
mccabe==0.6.1
pathmatch==0.2.1
pep8-naming==0.7.0
pycodestyle==2.3.1 # rq.filter: < 2.4.0
pydocstyle==2.1.1
pyflakes==2.0.0
six==1.11.0
snowballstemmer==1.2.1
typing==3.6.4
pep8-naming==0.4.1
pycodestyle==2.3.1
pydocstyle==1.1.1
pyflakes==1.5.0

View File

@@ -1,14 +1,12 @@
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-future-import
flake8-mock
flake8-per-file-ignores
flake8-pep3101
flake8-putty
flake8-string-format
flake8-tidy-imports
flake8-tuple
@@ -16,8 +14,13 @@ pep8-naming
pydocstyle
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
# 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
six==1.11.0
wheel==0.31.1
setuptools==34.3.1
six==1.10.0
wheel==0.29.0

View File

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

View File

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

View File

@@ -1,18 +1,11 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
certifi==2018.4.16
chardet==3.0.4
github3.py==1.1.0
idna==2.7
isort==4.3.4
lazy-object-proxy==1.3.1
editdistance==0.3.1
isort==4.2.5
lazy-object-proxy==1.2.2
mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
python-dateutil==2.7.3
./scripts/dev/pylint_checkers
requests==2.19.1
six==1.11.0
uritemplate==3.0.0
urllib3==1.22
wrapt==1.10.11
requests==2.13.0
wrapt==1.10.8

View File

@@ -2,7 +2,6 @@
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
./scripts/dev/pylint_checkers
requests
github3.py
# remove @commit-id for scm installs
#@ replace: @.*# #

View File

@@ -1,18 +1,13 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==1.6.5
certifi==2018.4.16
chardet==3.0.4
github3.py==1.1.0
idna==2.7
isort==4.3.4
lazy-object-proxy==1.3.1
astroid==1.4.9
github3.py==0.9.6
isort==4.2.5
lazy-object-proxy==1.2.2
mccabe==0.6.1
pylint==1.9.2
python-dateutil==2.7.3
pylint==1.6.5
./scripts/dev/pylint_checkers
requests==2.19.1
six==1.11.0
requests==2.13.0
uritemplate==3.0.0
urllib3==1.22
wrapt==1.10.11
uritemplate.py==3.0.2
wrapt==1.10.8

View File

@@ -1 +0,0 @@
PyQt5

View File

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

View File

@@ -4,4 +4,3 @@ pyPEG2
PyYAML
colorama
cssutils
attrs

View File

@@ -4,6 +4,7 @@ hg+https://bitbucket.org/ned/coveragepy
git+https://github.com/micheles/decorator.git
git+https://github.com/pallets/flask.git
git+https://github.com/miracle2k/python-glob2.git
git+https://github.com/Runscope/httpbin.git
git+https://github.com/HypothesisWorks/hypothesis-python.git
git+https://github.com/pallets/itsdangerous.git
git+https://bitbucket.org/zzzeek/mako.git
@@ -12,6 +13,12 @@ git+https://github.com/jenisys/parse_type.git
hg+https://bitbucket.org/pytest-dev/py
git+https://github.com/pytest-dev/pytest.git@features
git+https://github.com/pytest-dev/pytest-bdd.git
# This is broken at the moment because logfail tries to access
# LogCaptureHandler
# git+https://github.com/eisensheng/pytest-catchlog.git
pytest-catchlog==1.2.2
git+https://github.com/pytest-dev/pytest-cov.git
git+https://github.com/pytest-dev/pytest-faulthandler.git
git+https://github.com/pytest-dev/pytest-instafail.git
@@ -20,6 +27,7 @@ git+https://github.com/pytest-dev/pytest-qt.git
git+https://github.com/pytest-dev/pytest-repeat.git
git+https://github.com/pytest-dev/pytest-rerunfailures.git
git+https://github.com/abusalimov/pytest-travis-fold.git
git+https://github.com/fschulze/pytest-warnings.git
git+https://github.com/The-Compiler/pytest-xvfb.git
hg+https://bitbucket.org/gutworth/six
hg+https://bitbucket.org/jendrikseipp/vulture
@@ -34,5 +42,8 @@ git+https://github.com/pallets/jinja.git
git+https://github.com/pallets/markupsafe.git
hg+http://bitbucket.org/birkenfeld/pygments-main
hg+https://bitbucket.org/fdik/pypeg
git+https://github.com/python-attrs/attrs.git
git+https://github.com/yaml/pyyaml.git
# Fails to build:
# gcc: error: ext/_yaml.c: No such file or directory
# hg+https://bitbucket.org/xi/pyyaml
PyYAML==3.12

View File

@@ -1,40 +1,36 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==18.1.0
beautifulsoup4==4.6.0
cheroot==6.3.2.post0
beautifulsoup4==4.5.3
cheroot==5.1.0
click==6.7
# colorama==0.3.9
coverage==4.5.1
coverage==4.3.4
decorator==4.0.11
EasyProcess==0.2.3
fields==5.0.0
Flask==1.0.2
glob2==0.6
hunter==2.0.2
hypothesis==3.66.1
Flask==0.12
glob2==0.5
httpbin==0.5.0
hypothesis==3.6.1
itsdangerous==0.24
# Jinja2==2.10
Mako==1.0.7
# MarkupSafe==1.0
more-itertools==4.2.0
parse==1.8.4
parse-type==0.4.2
pluggy==0.6.0
py==1.5.4
py-cpuinfo==4.0.0
pytest==3.6.3
pytest-bdd==2.21.0
pytest-benchmark==3.1.1
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
# Jinja2==2.9.5
Mako==1.0.6
# MarkupSafe==0.23
parse==1.6.6
parse-type==0.3.4
py==1.4.32
pytest==3.0.6
pytest-bdd==2.18.1
pytest-benchmark==3.0.0
pytest-catchlog==1.2.2
pytest-cov==2.4.0
pytest-faulthandler==1.3.1
pytest-instafail==0.3.0
pytest-mock==1.5.0
pytest-qt==2.1.0
pytest-repeat==0.4.1
pytest-rerunfailures==4.1
pytest-travis-fold==1.3.0
pytest-xvfb==1.1.0
pytest-rerunfailures==2.1.0
pytest-travis-fold==1.2.0
pytest-warnings==0.2.0
pytest-xvfb==1.0.0
PyVirtualDisplay==0.2.1
six==1.11.0
vulture==0.28
Werkzeug==0.14.1
vulture==0.12
Werkzeug==0.11.15

View File

@@ -2,11 +2,12 @@ beautifulsoup4
cheroot
coverage
Flask
hunter
httpbin
hypothesis
pytest
pytest-bdd
pytest-benchmark
pytest-catchlog
pytest-cov
pytest-faulthandler
pytest-instafail
@@ -15,7 +16,8 @@ pytest-qt
pytest-repeat
pytest-rerunfailures
pytest-travis-fold
pytest-warnings
pytest-xvfb
vulture
#@ ignore: Jinja2, MarkupSafe, colorama
#@ ignore: Jinja2, MarkupSafe

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.4.0
py==1.4.32
tox==2.6.0
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.12

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

@@ -1,7 +1,6 @@
#!/usr/bin/env bash
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -23,7 +22,7 @@
# If run from qutebrowser as a userscript, it runs :open on the URL
# If not, it opens a new qutebrowser window at the URL
#
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
# :bind o spawn --userscript dmenu_qutebrowser
#
# Use the hotkey to open in new tab/window, press 'o' to open URL in current tab/window
@@ -41,7 +40,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,42 +0,0 @@
#!/bin/sh
set -euo pipefail
#
# Behavior:
# Userscript for qutebrowser which will take the raw JSON text of the current
# page, format it using `jq`, will add syntax highlighting using `pygments`,
# and open the syntax highlighted pretty printed html in a new tab. If the file
# is larger than 10MB then this script will only indent the json and will forego
# syntax highlighting using pygments.
#
# In order to use this script, just start it using `spawn --userscript` from
# qutebrowser. I recommend using an alias, e.g. put this in the
# [alias]-section of qutebrowser.conf:
#
# json = spawn --userscript /path/to/json_format
#
# Note that the color style defaults to monokai, but a different pygments style
# can be passed as the first parameter to the script. A full list of the pygments
# styles can be found at: https://help.farbox.com/pygments.html
#
# Bryan Gilbert, 2017
# do not run pygmentize on files larger than this amount of bytes
MAX_SIZE_PRETTIFY=10485760 # 10 MB
# default style to monokai if none is provided
STYLE=${1:-monokai}
TEMP_FILE="$(mktemp)"
jq . "$QUTE_TEXT" >"$TEMP_FILE"
# try GNU stat first and then OSX stat if the former fails
FILE_SIZE=$(
stat --printf="%s" "$TEMP_FILE" 2>/dev/null ||
stat -f%z "$TEMP_FILE" 2>/dev/null
)
if [ "$FILE_SIZE" -lt "$MAX_SIZE_PRETTIFY" ]; then
pygmentize -l json -f html -O full,style="$STYLE" <"$TEMP_FILE" >"${TEMP_FILE}_"
mv -f "${TEMP_FILE}_" "$TEMP_FILE"
fi
# send the command to qutebrowser to open the new file containing the formatted json
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"

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

@@ -12,7 +12,7 @@
# - rofi (in a recent version)
# - xdg-open and xdg-mime
# - You should configure qutebrowser to download files to a single directory
# - It comes in handy if you enable downloads.remove_finished. If you want to
# - It comes in handy if you enable remove-finished-downloads. If you want to
# see the recent downloads, just press "sd".
#
# Thorsten Wißmann, 2015 (thorsten` on freenode)
@@ -52,7 +52,7 @@ die() {
if ! [ -d "$DOWNLOAD_DIR" ] ; then
die "Download directory »$DOWNLOAD_DIR« not found!"
fi
if ! command -v "${ROFI_CMD}" > /dev/null ; then
if ! which "${ROFI_CMD}" > /dev/null ; then
die "Rofi command »${ROFI_CMD}« not found in PATH!"
fi
@@ -76,7 +76,6 @@ crop-first-column() {
ls-files() {
# add the slash at the end of the download dir enforces to follow the
# symlink, if the DOWNLOAD_DIR itself is a symlink
# shellcheck disable=SC2010
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
| grep '^[-]' \
| cut -d' ' -f3- \
@@ -92,10 +91,10 @@ if [ "${#entries[@]}" -eq 0 ] ; then
die "Download directory »${DOWNLOAD_DIR}« empty"
fi
line=$(printf '%s\n' "${entries[@]}" \
line=$(printf "%s\n" "${entries[@]}" \
| crop-first-column 55 \
| column -s $'\t' -t \
| $ROFI_CMD "${rofi_default_args[@]}" "$ROFI_ARGS") || true
| $ROFI_CMD "${rofi_default_args[@]}" $ROFI_ARGS) || true
if [ -z "$line" ]; then
exit 0
fi

View File

@@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
# Copyright 2015 jnphilipp <me@jnphilipp.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -21,7 +20,7 @@
# Opens all links to feeds defined in the head of a site
#
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
# :bind gF spawn --userscript openfeeds
#
# Use the hotkey to open the feeds in new tab/window, press 'gF' to open

View File

@@ -9,7 +9,7 @@ directly ask me via IRC (nickname thorsten\`) in #qutebrowser on freenode.
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
WARNING: the passwords are stored in qutebrowser's
debug log reachable via the url qute://log
debug log reachable via the url qute:log
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
Usage: run as a userscript form qutebrowser, e.g.:
@@ -64,7 +64,7 @@ die() {
javascript_escape() {
# print the first argument in an escaped way, such that it can safely
# be used within javascripts double quotes
sed "s,[\\\\'\"],\\\\&,g" <<< "$1"
sed "s,[\\\'\"],\\\&,g" <<< "$1"
}
# ======================================================= #
@@ -178,7 +178,7 @@ choose_entry_menu() {
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
file="${files[0]}"
else
file=$( printf '%s\n' "${files[@]}" | "${MENU_COMMAND[@]}" )
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
fi
}
@@ -189,7 +189,7 @@ choose_entry_rofi() {
}
choose_entry_zenity() {
MENU_COMMAND=( zenity --list --title "qutebrowser password fill"
MENU_COMMAND=( zenity --list --title "Qutebrowser password fill"
--text "Pick the password entry:"
--column "Name" )
choose_entry_menu || true
@@ -199,7 +199,7 @@ choose_entry_zenity_radio() {
zenity_helper() {
awk '{ print $0 ; print $0 }' \
| zenity --list --radiolist \
--title "qutebrowser password fill" \
--title "Qutebrowser password fill" \
--text "Pick the password entry:" \
--column " " --column "Name"
}
@@ -220,7 +220,7 @@ user_pattern='^(user|username|login): '
GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
GPG="gpg"
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
command -v gpg2 &>/dev/null && GPG="gpg2"
which gpg2 &>/dev/null && GPG="gpg2"
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
pass_backend() {
@@ -236,7 +236,7 @@ pass_backend() {
if ((match_line)) ; then
# add entries with matching URL-tag
while read -r -d "" passfile ; do
if $GPG "${GPG_OPTS[@]}" -d "$passfile" \
if $GPG "${GPG_OPTS}" -d "$passfile" \
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
then
passfile="${passfile#$PREFIX}"
@@ -269,7 +269,7 @@ pass_backend() {
break
fi
fi
done < <($GPG "${GPG_OPTS[@]}" -d "$path" )
done < <($GPG "${GPG_OPTS}" -d "$path" )
}
}
# =======================================================
@@ -283,8 +283,8 @@ secret_backend() {
query_entries() {
local domain="$1"
while read -r line ; do
if [[ "$line" == "attribute.username = "* ]] ; then
files+=("$domain ${line:21}")
if [[ "$line" =~ "attribute.username = " ]] ; then
files+=("$domain ${line#${BASH_REMATCH[0]}}")
fi
done < <( secret-tool search --unlock --all domain "$domain" 2>&1 )
}
@@ -303,7 +303,6 @@ pass_backend
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
if [ -f "$PWFILL_CONFIG" ] ; then
# shellcheck source=/dev/null
source "$PWFILL_CONFIG"
fi
init
@@ -312,7 +311,7 @@ simplify_url "$QUTE_URL"
query_entries "${simple_url}"
no_entries_found
# remove duplicates
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort | uniq )
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
choose_entry
if [ -z "$file" ] ; then
# choose_entry didn't want any of these entries
@@ -328,17 +327,6 @@ open_entry "$file"
js() {
cat <<EOF
function isVisible(elem) {
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
if (style.getPropertyValue("visibility") !== "visible" ||
style.getPropertyValue("display") === "none" ||
style.getPropertyValue("opacity") === "0") {
return false;
}
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
};
function hasPasswordField(form) {
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
@@ -353,7 +341,7 @@ cat <<EOF
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
var input = inputs[j];
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
if (input.type == "text" || input.type == "email") {
input.focus();
input.value = "$(javascript_escape "${username}")";
input.blur();

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

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Copyright 2015 Zach-Button <zachrey.button@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 script fetches the unprocessed HTML source for a page and opens it in vim.
# :bind gf spawn --userscript qutebrowser_viewsource
#
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
#
path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html)
curl "$QUTE_URL" > "$path"
urxvt -e vim "$path"
rm "$path"

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

@@ -1,37 +1,21 @@
#!/usr/bin/env python
#!/usr/bin/env python2
#
# Executes python-readability on current page and opens the summary as new tab.
#
# Depends on the python-readability package, or its fork:
#
# - https://github.com/buriy/python-readability
# - https://github.com/bookieio/breadability
#
# Usage:
# :spawn --userscript readability
#
from __future__ import absolute_import
import codecs, os
from readability.readability import Document
tmpfile = os.path.join(
os.environ.get('QUTE_DATA_DIR',
os.path.expanduser('~/.local/share/qutebrowser')),
'userscripts/readability.html')
tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html')
if not os.path.exists(os.path.dirname(tmpfile)):
os.makedirs(os.path.dirname(tmpfile))
with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source:
data = source.read()
try:
from breadability.readable import Article as reader
doc = reader(data)
content = doc.readable
except ImportError:
from readability import Document
doc = Document(data)
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
doc = Document(source.read())
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
with codecs.open(tmpfile, 'w', 'utf-8') as target:
target.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2
#
# Adds DuckDuckGo bang as searchengine.
#
@@ -8,21 +8,14 @@
# Example:
# :spawn --userscript ripbang amazon maps
#
from __future__ import print_function
import os, re, requests, sys
try:
from urllib.parse import unquote
except ImportError:
from urllib import unquote
import os, re, requests, sys, urllib
for argument in sys.argv[1:]:
bang = '!' + argument
r = requests.get('https://duckduckgo.com/',
params={'q': bang + ' SEARCHTEXT'})
searchengine = unquote(re.search("url=[^']+", r.text).group(0))
searchengine = urllib.unquote(re.search("url=[^']+", r.text).group(0))
searchengine = searchengine.replace('url=', '')
searchengine = searchengine.replace('/l/?kh=-1&uddg=', '')
searchengine = searchengine.replace('SEARCHTEXT', '{}')
@@ -31,4 +24,4 @@ for argument in sys.argv[1:]:
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
fifo.write('set searchengines %s %s' % (bang, searchengine))
else:
print('%s %s' % (bang, searchengine))
print '%s %s' % (bang, searchengine)

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 }
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
js() {
cat <<EOF
@@ -140,4 +140,4 @@ printjs() {
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
msg info "Opening $QUTE_URL with mpv"
"${video_command[@]}" "$@" "$QUTE_URL"
"${video_command[@]}" "$QUTE_URL"

View File

@@ -1,14 +1,12 @@
[pytest]
log_level = NOTSET
addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
testpaths = tests
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail --pythonwarnings error
markers =
gui: Tests using the GUI (e.g. spawning widgets)
posix: Tests which only can run on a POSIX OS.
windows: Tests which only can run on Windows.
linux: Tests which only can run on Linux.
mac: Tests which only can run on macOS.
not_mac: Tests which can not run on macOS.
osx: Tests which only can run on OS X.
not_osx: Tests which can not run on OS X.
not_frozen: Tests which can't be run if sys.frozen is True.
no_xvfb: Tests which can't be run with Xvfb.
frozen: Tests which can only be run if sys.frozen is True.
@@ -16,26 +14,21 @@ markers =
end2end: End to end tests which run qutebrowser as subprocess
xfail_norun: xfail the test with out running it
ci: Tests which should only run on CI.
no_ci: Tests which should not run on CI.
qtwebengine_todo: Features still missing with QtWebEngine
qtwebengine_skip: Tests not applicable with QtWebEngine
qtwebkit_skip: Tests not applicable with QtWebKit
qtwebkit_ng_xfail: Tests failing with QtWebKit-NG
qtwebkit_ng_skip: Tests skipped with QtWebKit-NG
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine
js_prompt: Tests needing to display a javascript prompt
this: Used to mark tests during development
no_invalid_lines: Don't fail on unparseable lines in end2end tests
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
issue3572: Tests which are broken with QtWebEngine and Qt 5.10, https://github.com/qutebrowser/qutebrowser/issues/3572
qtbug60673: Tests which are broken if the conversion from orange selection to real selection is flaky
fake_os: Fake utils.is_* to a fake operating system
unicode_locale: Tests which need an unicode locale to work
qtwebkit6021_skip: Tests which would fail on WebKit version 602.1
qt_log_level_fail = WARNING
qt_log_ignore =
^SpellCheck: .*
^SetProcessDpiAwareness failed: .*
^QWindowsWindow::setGeometry(Dp)?: Unable to set geometry .*
^QWindowsWindow::setGeometryDp: Unable to set geometry .*
^QProcess: Destroyed while process .* is still running\.
^"Method "GetAll" with signature "s" on interface "org\.freedesktop\.DBus\.Properties" doesn't exist
^"Method \\"GetAll\\" with signature \\"s\\" on interface \\"org\.freedesktop\.DBus\.Properties\\" doesn't exist\\n"
@@ -49,22 +42,13 @@ qt_log_ignore =
^QWaitCondition: Destroyed while threads are still waiting
^QXcbXSettings::QXcbXSettings\(QXcbScreen\*\) Failed to get selection owner for XSETTINGS_S atom
^QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to .*
^QGeoclueMaster error creating GeoclueMasterClient\.
^Geoclue error: Process org\.freedesktop\.Geoclue\.Master exited with status 127
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
^QXcbClipboard: Cannot transfer data, no data available
^load glyph failed
^Error when parsing the netrc file
^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=
^QPainter::end: Painter ended with \d+ saved states
^QSslSocket: cannot resolve .*
^QSslSocket: cannot call unresolved function .*
^Incompatible version of OpenSSL
^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
^QSslSocket: cannot resolve SSLv[23]_(client|server)_method
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

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

@@ -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-2016 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-2016 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (1, 4, 0)
__version_info__ = (0, 10, 1)
__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-2016 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-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -17,29 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Initialization of qutebrowser and application-wide things.
The run() function will get called once early initialization (in
qutebrowser.py/earlyinit.py) is done. See the qutebrowser.py docstring for
details about early initialization.
As we need to access the config before the QApplication is created, we
initialize everything the config needs before the QApplication is created, and
then leave it in a partially initialized state (no saving, no config errors
shown yet).
We then set up the QApplication object and initialize a few more low-level
things.
After that, init() and _init_modules() take over and initialize the rest.
After all initialization is done, the qt_mainloop() function is called, which
blocks and spins the Qt mainloop.
"""
"""Initialization of qutebrowser and application-wide things."""
import os
import sys
import subprocess
import configparser
import functools
import json
import shutil
@@ -51,7 +34,7 @@ import tokenize
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QWindow
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
QObject, QEvent, pyqtSignal, Qt)
QObject, QEvent, pyqtSignal)
try:
import hunter
except ImportError:
@@ -59,27 +42,22 @@ except ImportError:
import qutebrowser
import qutebrowser.resources
from qutebrowser.completion import completiondelegate
from qutebrowser.completion.models import miscmodels
from qutebrowser.completion.models import instances as completionmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import config, websettings, configfiles, configinit
from qutebrowser.config import style, config, websettings, configexc
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
qtnetworkdownloads, downloads, greasemonkey)
downloads)
from qutebrowser.browser.network import proxy
from qutebrowser.browser.webkit import cookies, cache
from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.keyinput import macros
from qutebrowser.mainwindow import mainwindow, prompt
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
crashsignal, earlyinit, sql, cmdhistory,
backendproblem)
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
usertypes, standarddir, error)
# pylint: disable=unused-import
# We import those to run the cmdutils.register decorators.
from qutebrowser.mainwindow.statusbar import command
from qutebrowser.misc import utilcmds
# pylint: enable=unused-import
crashsignal, earlyinit)
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
objreg, usertypes, standarddir, error, debug)
# We import utilcmds to run the cmdutils.register decorators.
qApp = None
@@ -93,13 +71,6 @@ def run(args):
quitter = Quitter(args)
objreg.register('quitter', quitter)
log.init.debug("Initializing directories...")
standarddir.init(args)
utils.preload_resources()
log.init.debug("Initializing config...")
configinit.early_init(args)
global qApp
qApp = Application(args)
qApp.setOrganizationName("qutebrowser")
@@ -107,6 +78,9 @@ def run(args):
qApp.setApplicationVersion(qutebrowser.__version__)
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
log.init.debug("Initializing directories...")
standarddir.init(args)
if args.version:
print(version.version())
sys.exit(usertypes.Exit.ok)
@@ -159,10 +133,11 @@ def init(args, crash_handler):
log.init.debug("Starting init...")
qApp.setQuitOnLastWindowClosed(False)
_init_icon()
utils.actute_warning()
try:
_init_modules(args, crash_handler)
except (OSError, UnicodeDecodeError, browsertab.WebTabError) as e:
except (OSError, UnicodeDecodeError) as e:
error.handle_fatal_exc(e, args, "Error while initializing!",
pre_text="Error while initializing")
sys.exit(usertypes.Exit.err_init)
@@ -173,6 +148,8 @@ def init(args, crash_handler):
objreg.register('event-filter', event_filter)
log.init.debug("Connecting signals...")
config_obj = objreg.get('config')
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
qApp.focusChanged.connect(on_focus_changed)
_process_args(args)
@@ -181,7 +158,7 @@ def init(args, crash_handler):
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
objreg.get('web-history').import_txt()
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
log.init.debug("Init done!")
crash_handler.raise_crashdlg()
@@ -194,32 +171,36 @@ def _init_icon():
for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]:
filename = ':/icons/qutebrowser-{}x{}.png'.format(size, size)
pixmap = QPixmap(filename)
if pixmap.isNull():
log.init.warning("Failed to load {}".format(filename))
else:
fallback_icon.addPixmap(pixmap)
qtutils.ensure_not_null(pixmap)
fallback_icon.addPixmap(pixmap)
qtutils.ensure_not_null(fallback_icon)
icon = QIcon.fromTheme('qutebrowser', fallback_icon)
if icon.isNull():
log.init.warning("Failed to load icon")
else:
qApp.setWindowIcon(icon)
qtutils.ensure_not_null(icon)
qApp.setWindowIcon(icon)
def _process_args(args):
"""Open startpage etc. and process commandline args."""
config_obj = objreg.get('config')
for sect, opt, val in args.temp_settings:
try:
config_obj.set('temp', sect, opt, val)
except (configexc.Error, configparser.Error) as e:
message.error("set: {} - {}".format(e.__class__.__name__, e))
if not args.override_restore:
_load_session(args.session)
session_manager = objreg.get('session-manager')
if not session_manager.did_load:
log.init.debug("Initializing main window...")
window = mainwindow.MainWindow(private=None)
window = mainwindow.MainWindow()
if not args.nowindow:
window.show()
qApp.setActiveWindow(window)
process_pos_args(args.command)
_open_startpage()
_open_special_pages(args)
_open_quickstart(args)
delta = datetime.datetime.now() - earlyinit.START_TIME
log.init.debug("Init finished after {}s".format(delta.total_seconds()))
@@ -231,12 +212,13 @@ def _load_session(name):
Args:
name: The name of the session to load, or None to read state file.
"""
state_config = objreg.get('state-config')
session_manager = objreg.get('session-manager')
if name is None and session_manager.exists('_autosave'):
name = '_autosave'
elif name is None:
try:
name = configfiles.state['general']['session']
name = state_config['general']['session']
except KeyError:
# No session given as argument and none in the session file ->
# start without loading a session
@@ -249,7 +231,7 @@ def _load_session(name):
except sessions.SessionError as e:
message.error("Failed to load session {}: {}".format(name, e))
try:
del configfiles.state['general']['session']
del state_config['general']['session']
except KeyError:
pass
# If this was a _restart session, delete it.
@@ -281,7 +263,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
win_id = mainwindow.get_window(via_ipc, force_tab=True)
log.init.debug("Startup cmd {!r}".format(cmd))
commandrunner = runners.CommandRunner(win_id)
commandrunner.run_safely(cmd[1:])
commandrunner.run_safely_init(cmd[1:])
elif not cmd:
log.init.debug("Empty argument")
win_id = mainwindow.get_window(via_ipc, force_window=True)
@@ -289,7 +271,11 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
if via_ipc and target_arg and target_arg != 'auto':
open_target = target_arg
else:
open_target = None
open_target = config.get('general', 'new-instance-open-target')
win_id = mainwindow.get_window(via_ipc, force_target=open_target)
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
log.init.debug("Startup URL {}".format(cmd))
if not cwd: # could also be an empty string due to the PyQt signal
cwd = None
try:
@@ -298,30 +284,9 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
message.error("Error in startup argument '{}': {}".format(
cmd, e))
else:
win_id = open_url(url, target=open_target, via_ipc=via_ipc)
def open_url(url, target=None, no_raise=False, via_ipc=True):
"""Open an URL in new window/tab.
Args:
url: An URL to open.
target: same as new_instance_open_target (used as a default).
no_raise: suppress target window raising.
via_ipc: Whether the arguments were transmitted over IPC.
Return:
ID of a window that was used to open URL
"""
target = target or config.val.new_instance_open_target
background = target in {'tab-bg', 'tab-bg-silent'}
win_id = mainwindow.get_window(via_ipc, force_target=target,
no_raise=no_raise)
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
log.init.debug("About to open URL: {}".format(url.toDisplayString()))
tabbed_browser.tabopen(url, background=background, related=False)
return win_id
background = open_target in ['tab-bg', 'tab-bg-silent']
tabbed_browser.tabopen(url, background=background,
explicit=True)
def _open_startpage(win_id=None):
@@ -337,52 +302,47 @@ def _open_startpage(win_id=None):
window_ids = [win_id]
else:
window_ids = objreg.window_registry
for cur_win_id in list(window_ids): # Copying as the dict could change
for cur_win_id in window_ids:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=cur_win_id)
if tabbed_browser.widget.count() == 0:
log.init.debug("Opening start pages")
for url in config.val.url.start_pages:
tabbed_browser.tabopen(url)
if tabbed_browser.count() == 0:
log.init.debug("Opening startpage")
for urlstr in config.get('general', 'startpage'):
try:
url = urlutils.fuzzy_url(urlstr, do_search=False)
except urlutils.InvalidUrlError as e:
message.error("Error when opening startpage: {}".format(e))
tabbed_browser.tabopen(QUrl('about:blank'))
else:
tabbed_browser.tabopen(url)
def _open_special_pages(args):
"""Open special notification pages which are only shown once.
Currently this is:
- Quickstart page if it's the first start.
- Legacy QtWebKit warning if needed.
def _open_quickstart(args):
"""Open quickstart if it's the first start.
Args:
args: The argparse namespace.
"""
if args.basedir is not None:
# With --basedir given, don't open anything.
# With --basedir given, don't open quickstart.
return
general_sect = configfiles.state['general']
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
# Quickstart page
quickstart_done = general_sect.get('quickstart-done') == '1'
state_config = objreg.get('state-config')
try:
quickstart_done = state_config['general']['quickstart-done'] == '1'
except KeyError:
quickstart_done = False
if not quickstart_done:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
tabbed_browser.tabopen(
QUrl('https://www.qutebrowser.org/quickstart.html'))
general_sect['quickstart-done'] = '1'
state_config['general']['quickstart-done'] = '1'
# Setting migration page
needs_migration = os.path.exists(
os.path.join(standarddir.config(), 'qutebrowser.conf'))
migration_shown = general_sect.get('config-migration-shown') == '1'
if needs_migration and not migration_shown:
tabbed_browser.tabopen(QUrl('qute://help/configuring.html'),
background=False)
general_sect['config-migration-shown'] = '1'
def _save_version():
"""Save the current version to the state config."""
state_config = objreg.get('state-config')
state_config['general']['version'] = qutebrowser.__version__
def on_focus_changed(_old, new):
@@ -418,46 +378,33 @@ def _init_modules(args, crash_handler):
args: The argparse namespace.
crash_handler: The CrashHandler instance.
"""
# pylint: disable=too-many-statements
log.init.debug("Initializing prompts...")
prompt.init()
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(qApp)
objreg.register('save-manager', save_manager)
configinit.late_init(save_manager)
log.init.debug("Checking backend requirements...")
backendproblem.init()
log.init.debug("Initializing prompts...")
prompt.init()
save_manager.add_saveable('version', _save_version)
log.init.debug("Initializing network...")
networkmanager.init()
log.init.debug("Initializing proxy...")
proxy.init()
if qtutils.version_check('5.8'):
# Otherwise we can only initialize it for QtWebKit because of crashes
log.init.debug("Initializing proxy...")
proxy.init()
log.init.debug("Initializing readline-bridge...")
readline_bridge = readline.ReadlineBridge()
objreg.register('readline-bridge', readline_bridge)
try:
log.init.debug("Initializing sql...")
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
log.init.debug("Initializing config...")
config.init(qApp)
save_manager.init_autosave()
log.init.debug("Initializing web history...")
history.init(qApp)
except sql.SqlError as e:
if e.environmental:
error.handle_fatal_exc(e, args, 'Error initializing SQL',
pre_text='Error initializing SQL')
sys.exit(usertypes.Exit.err_init)
else:
raise
log.init.debug("Initializing completion...")
completiondelegate.init()
log.init.debug("Initializing command history...")
cmdhistory.init()
log.init.debug("Initializing web history...")
history.init(qApp)
log.init.debug("Initializing crashlog...")
if not args.no_err_windows:
@@ -492,19 +439,36 @@ def _init_modules(args, crash_handler):
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
objreg.register('cache', diskcache)
log.init.debug("Initializing downloads...")
download_manager = qtnetworkdownloads.DownloadManager(parent=qApp)
objreg.register('qtnetwork-download-manager', download_manager)
log.init.debug("Initializing Greasemonkey...")
greasemonkey.init()
log.init.debug("Initializing completions...")
completionmodels.init()
log.init.debug("Misc initialization...")
if config.get('ui', 'hide-wayland-decoration'):
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
else:
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
macros.init()
# Init backend-specific stuff
browsertab.init()
def _init_late_modules(args):
"""Initialize modules which can be inited after the window is shown."""
log.init.debug("Reading web history...")
reader = objreg.get('web-history').async_read()
with debug.log_time(log.init, 'Reading history'):
while True:
QApplication.processEvents()
try:
next(reader)
except StopIteration:
break
except (OSError, UnicodeDecodeError) as e:
error.handle_fatal_exc(e, args, "Error while initializing!",
pre_text="Error while initializing")
sys.exit(usertypes.Exit.err_init)
class Quitter:
"""Utility class to quit/restart the QApplication.
@@ -547,13 +511,12 @@ class Quitter:
with tokenize.open(os.path.join(dirpath, fn)) as f:
compile(f.read(), fn, 'exec')
def _get_restart_args(self, pages=(), session=None, override_args=None):
def _get_restart_args(self, pages=(), session=None):
"""Get the current working directory and args to relaunch qutebrowser.
Args:
pages: The pages to re-open.
session: The session to load, or None.
override_args: Argument overrides as a dict.
Return:
An (args, cwd) tuple.
@@ -569,8 +532,8 @@ class Quitter:
cwd = os.path.abspath(os.path.dirname(sys.executable))
else:
args = [sys.executable, '-m', 'qutebrowser']
cwd = os.path.join(
os.path.abspath(os.path.dirname(qutebrowser.__file__)), '..')
cwd = os.path.join(os.path.abspath(os.path.dirname(
qutebrowser.__file__)), '..')
if not os.path.isdir(cwd):
# Probably running from a python egg. Let's fallback to
# cwd=None and see if that works out.
@@ -604,9 +567,6 @@ class Quitter:
argdict['temp_basedir'] = False
argdict['temp_basedir_restarted'] = True
if override_args is not None:
argdict.update(override_args)
# Dump the data
data = json.dumps(argdict)
args += ['--json-args', data]
@@ -631,7 +591,7 @@ class Quitter:
if ok:
self.shutdown(restart=True)
def restart(self, pages=(), session=None, override_args=None):
def restart(self, pages=(), session=None):
"""Inner logic to restart qutebrowser.
The "better" way to restart is to pass a session (_restart usually) as
@@ -644,7 +604,6 @@ class Quitter:
Args:
pages: A list of URLs to open.
session: The session to load, or None.
override_args: Argument overrides as a dict.
Return:
True if the restart succeeded, False otherwise.
@@ -654,19 +613,13 @@ class Quitter:
log.destroy.debug("sys.path: {}".format(sys.path))
log.destroy.debug("sys.argv: {}".format(sys.argv))
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
# Save the session if one is given.
if session is not None:
session_manager = objreg.get('session-manager')
session_manager.save(session, with_private=True)
# Make sure we're not accepting a connection from the new process
# before we fully exited.
ipc.server.shutdown()
session_manager.save(session)
# Open a new process and immediately shutdown the existing one
try:
args, cwd = self._get_restart_args(pages, session, override_args)
args, cwd = self._get_restart_args(pages, session)
if cwd is None:
subprocess.Popen(args)
else:
@@ -677,24 +630,8 @@ class Quitter:
else:
return True
@cmdutils.register(instance='quitter', name='quit')
@cmdutils.argument('session', completion=miscmodels.session)
def quit(self, save=False, session=None):
"""Quit qutebrowser.
Args:
save: When given, save the open windows even if auto_save.session
is turned off.
session: The name of the session to save.
"""
if session is not None and not save:
raise cmdexc.CommandError("Session name given without --save!")
if save:
if session is None:
session = sessions.default
self.shutdown(session=session)
else:
self.shutdown()
@cmdutils.register(instance='quitter', name=['quit', 'q'],
ignore_args=True)
def shutdown(self, status=0, session=None, last_window=False,
restart=False):
"""Quit qutebrowser.
@@ -711,14 +648,14 @@ class Quitter:
self._shutting_down = True
log.destroy.debug("Shutting down with status {}, session {}...".format(
status, session))
session_manager = objreg.get('session-manager', None)
if session_manager is not None:
if session is not None:
session_manager.save(session, last_window=last_window,
load_next_time=True)
elif config.val.auto_save.session:
session_manager.save(sessions.default, last_window=last_window,
load_next_time=True)
session_manager = objreg.get('session-manager')
if session is not None:
session_manager.save(session, last_window=last_window,
load_next_time=True)
elif config.get('general', 'save-session'):
session_manager.save(sessions.default, last_window=last_window,
load_next_time=True)
if prompt.prompt_queue.shutdown():
# If shutdown was called while we were asking a question, we're in
@@ -735,7 +672,7 @@ class Quitter:
# event loop, so we can shut down immediately.
self._shutdown(status, restart=restart)
def _shutdown(self, status, restart): # noqa
def _shutdown(self, status, restart):
"""Second stage of shutdown."""
log.destroy.debug("Stage 2 of shutting down...")
if qApp is None:
@@ -744,16 +681,14 @@ class Quitter:
# Remove eventfilter
try:
log.destroy.debug("Removing eventfilter...")
event_filter = objreg.get('event-filter', None)
if event_filter is not None:
qApp.removeEventFilter(event_filter)
qApp.removeEventFilter(objreg.get('event-filter'))
except AttributeError:
pass
# Close all windows
QApplication.closeAllWindows()
# Shut down IPC
try:
ipc.server.shutdown()
objreg.get('ipc-server').shutdown()
except KeyError:
pass
# Save everything
@@ -772,8 +707,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()
@@ -790,13 +723,21 @@ class Quitter:
# Now we can hopefully quit without segfaults
log.destroy.debug("Deferring QApplication::exit...")
objreg.get('signal-handler').deactivate()
session_manager = objreg.get('session-manager', None)
if session_manager is not None:
session_manager.delete_autosave()
objreg.get('session-manager').delete_autosave()
# We use a singleshot timer to exit here to minimize the likelihood of
# segfaults.
QTimer.singleShot(0, functools.partial(qApp.exit, status))
@cmdutils.register(instance='quitter', name='wq')
@cmdutils.argument('name', completion=usertypes.Completion.sessions)
def save_and_quit(self, name=sessions.default):
"""Save open pages and quit.
Args:
name: The name of the session.
"""
self.shutdown(session=name)
class Application(QApplication):
@@ -817,7 +758,7 @@ class Application(QApplication):
"""
self._last_focus_object = None
qt_args = configinit.qt_args(args)
qt_args = qtutils.get_args(args)
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
super().__init__(qt_args)
@@ -829,7 +770,6 @@ class Application(QApplication):
self.launch_time = datetime.datetime.now()
self.focusObjectChanged.connect(self.on_focus_object_changed)
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
@pyqtSlot(QObject)
def on_focus_object_changed(self, obj):
@@ -839,26 +779,13 @@ class Application(QApplication):
log.misc.debug("Focus object changed: {}".format(output))
self._last_focus_object = output
def event(self, e):
"""Handle macOS FileOpen events."""
if e.type() == QEvent.FileOpen:
url = e.url()
if url.isValid():
open_url(url, no_raise=True)
else:
message.error("Invalid URL: {}".format(url.errorString()))
else:
return super().event(e)
return True
def __repr__(self):
return utils.get_repr(self)
def exit(self, status):
"""Extend QApplication::exit to log the event."""
log.destroy.debug("Now calling QApplication::exit.")
if 'debug-exit' in self._args.debug_flags:
if self._args.debug_exit:
if hunter is None:
print("Not logging late shutdown because hunter could not be "
"imported!", file=sys.stderr)
@@ -882,9 +809,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 +832,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 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-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -67,7 +67,11 @@ def is_whitelisted_host(host):
Args:
host: The host of the request as string.
"""
for pattern in config.val.content.host_blocking.whitelist:
whitelist = config.get('content', 'host-blocking-whitelist')
if whitelist is None:
return False
for pattern in whitelist:
if fnmatch.fnmatch(host, pattern.lower()):
return True
return False
@@ -94,8 +98,14 @@ class HostBlocker:
_done_count: How many files have been read successfully.
_local_hosts_file: The path to the blocked-hosts file.
_config_hosts_file: The path to a blocked-hosts in ~/.config
Class attributes:
WHITELISTED: Hosts which never should be blocked.
"""
WHITELISTED = ('localhost', 'localhost.localdomain', 'broadcasthost',
'local')
def __init__(self):
self._blocked_hosts = set()
self._config_blocked_hosts = set()
@@ -104,16 +114,16 @@ class HostBlocker:
data_dir = standarddir.data()
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
self._update_files()
self.on_config_changed()
config_dir = standarddir.config()
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
config.instance.changed.connect(self._update_files)
objreg.get('config').changed.connect(self.on_config_changed)
def is_blocked(self, url):
"""Check if the given URL (as QUrl) is blocked."""
if not config.val.content.host_blocking.enabled:
if not config.get('content', 'host-blocking-enabled'):
return False
host = url.host()
return ((host in self._blocked_hosts or
@@ -154,9 +164,8 @@ class HostBlocker:
if not found:
args = objreg.get('args')
if (config.val.content.host_blocking.lists and
args.basedir is None and
config.val.content.host_blocking.enabled):
if (config.get('content', 'host-block-lists') is not None and
args.basedir is None):
message.info("Run :adblock-update to get adblock lists.")
@cmdutils.register(instance='host-blocker')
@@ -170,15 +179,18 @@ class HostBlocker:
self._config_blocked_hosts)
self._blocked_hosts = set()
self._done_count = 0
download_manager = objreg.get('qtnetwork-download-manager')
for url in config.val.content.host_blocking.lists:
urls = config.get('content', 'host-block-lists')
download_manager = objreg.get('qtnetwork-download-manager',
scope='window', window='last-focused')
if urls is None:
return
for url in urls:
if url.scheme() == 'file':
filename = url.toLocalFile()
try:
fileobj = open(filename, 'rb')
fileobj = open(url.path(), 'rb')
except OSError as e:
message.error("adblock: Error while reading {}: {}".format(
filename, e.strerror))
url.path(), e.strerror))
continue
download = FakeDownload(fileobj)
self._in_progress.append(download)
@@ -228,14 +240,16 @@ class HostBlocker:
parts = line.split()
if len(parts) == 1:
# "one host per line" format
hosts = [parts[0]]
else:
host = parts[0]
elif len(parts) == 2:
# /etc/hosts format
hosts = parts[1:]
host = parts[1]
else:
log.misc.error("Failed to parse: {!r}".format(line))
return False
for host in hosts:
if '.' in host and not host.endswith('.localdomain'):
self._blocked_hosts.add(host)
if host not in self.WHITELISTED:
self._blocked_hosts.add(host)
return True
@@ -277,10 +291,11 @@ class HostBlocker:
message.info("adblock: Read {} hosts from {} sources.".format(
len(self._blocked_hosts), self._done_count))
@config.change_filter('content.host_blocking.lists')
def _update_files(self):
@config.change_filter('content', 'host-block-lists')
def on_config_changed(self):
"""Update files when the config changed."""
if not config.val.content.host_blocking.lists:
urls = config.get('content', 'host-block-lists')
if urls is None:
try:
os.remove(self._local_hosts_file)
except FileNotFoundError:

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 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,36 +19,27 @@
"""Base class for a wrapper over QWebView/QWebEngineView."""
import enum
import itertools
import attr
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF
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)
def create(win_id, private, parent=None):
def create(win_id, parent=None):
"""Get a QtWebKit/QtWebEngine tab object.
Args:
win_id: The window ID where the tab will be shown.
private: Whether the tab is a private/off the record tab.
parent: The Qt parent to set.
"""
# Importing modules here so we don't depend on QtWebEngine without the
@@ -60,8 +51,7 @@ def create(win_id, private, parent=None):
else:
from qutebrowser.browser.webkit import webkittab
tab_class = webkittab.WebKitTab
return tab_class(win_id=win_id, mode_manager=mode_manager, private=private,
parent=parent)
return tab_class(win_id=win_id, mode_manager=mode_manager, parent=parent)
def init():
@@ -69,6 +59,9 @@ def init():
if objects.backend == usertypes.Backend.QtWebEngine:
from qutebrowser.browser.webengine import webenginetab
webenginetab.init()
else:
from qutebrowser.browser.webkit import webkittab
webkittab.init()
class WebTabError(Exception):
@@ -81,7 +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
@@ -90,7 +83,6 @@ TerminationStatus = enum.Enum('TerminationStatus', [
])
@attr.s
class TabData:
"""A simple namespace with a fixed set of attributes.
@@ -100,47 +92,23 @@ class TabData:
load.
inspector: The QWebInspector used for this webview.
viewing_source: Set if we're currently showing a source view.
Only used when sources are shown via pygments.
open_target: Where to open the next link.
Only used for QtWebKit.
override_target: Override for open_target for fake clicks (like hints).
Only used for QtWebKit.
pinned: Flag to pin the tab.
fullscreen: Whether the tab has a video shown fullscreen currently.
netrc_used: Whether netrc authentication was performed.
input_mode: current input mode for the tab.
"""
keep_icon = attr.ib(False)
viewing_source = attr.ib(False)
inspector = attr.ib(None)
open_target = attr.ib(usertypes.ClickTarget.normal)
override_target = attr.ib(None)
pinned = attr.ib(False)
fullscreen = attr.ib(False)
netrc_used = attr.ib(False)
input_mode = attr.ib(usertypes.KeyMode.normal)
def should_show_icon(self):
return (config.val.tabs.favicons.show == 'always' or
config.val.tabs.favicons.show == 'pinned' and self.pinned)
def __init__(self):
self.keep_icon = False
self.viewing_source = False
self.inspector = None
self.override_target = None
class AbstractAction:
"""Attribute of AbstractTab for Qt WebActions.
"""Attribute of AbstractTab for Qt WebActions."""
Class attributes (overridden by subclasses):
action_class: The class actions are defined on (QWeb{Engine,}Page)
action_base: The type of the actions (QWeb{Engine,}Page.WebAction)
"""
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."""
@@ -150,38 +118,6 @@ class AbstractAction:
"""Save the current page."""
raise NotImplementedError
def run_string(self, name):
"""Run a webaction based on its name."""
member = getattr(self.action_class, name, None)
if not isinstance(member, self.action_base):
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:
@@ -219,8 +155,6 @@ class AbstractSearch(QObject):
Attributes:
text: The last thing this view was searched for.
search_displayed: Whether we're currently displaying search results in
this view.
_flags: The flags of the last search (needs to be set by subclasses).
_widget: The underlying WebView widget.
"""
@@ -229,30 +163,14 @@ class AbstractSearch(QObject):
super().__init__(parent)
self._widget = None
self.text = None
self.search_displayed = False
def _is_case_sensitive(self, ignore_case):
"""Check if case-sensitivity should be used.
This assumes self.text is already set properly.
Arguments:
ignore_case: The ignore_case value from the config.
"""
mapping = {
'smart': not self.text.islower(),
'never': True,
'always': False,
}
return mapping[ignore_case]
def search(self, text, *, ignore_case='never', reverse=False,
def search(self, text, *, ignore_case=False, reverse=False,
result_cb=None):
"""Find the given text on the page.
Args:
text: The text to search for.
ignore_case: Search case-insensitively. ('always'/'never/'smart')
ignore_case: Search case-insensitively. (True/False/'smart')
reverse: Reverse search direction.
result_cb: Called with a bool indicating whether a match was found.
"""
@@ -288,14 +206,13 @@ class AbstractZoom(QObject):
_default_zoom_changed: Whether the zoom was changed from the default.
"""
def __init__(self, tab, parent=None):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._tab = tab
self._widget = None
self._win_id = win_id
self._default_zoom_changed = False
self._init_neighborlist()
config.instance.changed.connect(self._on_config_changed)
self._zoom_factor = float(config.val.zoom.default) / 100
objreg.get('config').changed.connect(self._on_config_changed)
# # FIXME:qtwebengine is this needed?
# # For some reason, this signal doesn't get disconnected automatically
@@ -304,20 +221,21 @@ class AbstractZoom(QObject):
# self.destroyed.connect(functools.partial(
# cfg.changed.disconnect, self.init_neighborlist))
@pyqtSlot(str)
def _on_config_changed(self, option):
if option in ['zoom.levels', 'zoom.default']:
@pyqtSlot(str, str)
def _on_config_changed(self, section, option):
if section == 'ui' and option in ['zoom-levels', 'default-zoom']:
if not self._default_zoom_changed:
factor = float(config.val.zoom.default) / 100
self.set_factor(factor)
factor = float(config.get('ui', 'default-zoom')) / 100
self._set_factor_internal(factor)
self._default_zoom_changed = False
self._init_neighborlist()
def _init_neighborlist(self):
"""Initialize self._neighborlist."""
levels = config.val.zoom.levels
levels = config.get('ui', 'zoom-levels')
self._neighborlist = usertypes.NeighborList(
levels, mode=usertypes.NeighborList.Modes.edge)
self._neighborlist.fuzzyval = config.val.zoom.default
self._neighborlist.fuzzyval = config.get('ui', 'default-zoom')
def offset(self, offset):
"""Increase/Decrease the zoom level by the given offset.
@@ -346,37 +264,25 @@ class AbstractZoom(QObject):
self._neighborlist.fuzzyval = int(factor * 100)
if factor < 0:
raise ValueError("Can't zoom to factor {}!".format(factor))
default_zoom_factor = float(config.val.zoom.default) / 100
self._default_zoom_changed = (factor != default_zoom_factor)
self._zoom_factor = factor
self._default_zoom_changed = True
self._set_factor_internal(factor)
def factor(self):
return self._zoom_factor
raise NotImplementedError
def set_default(self):
self._set_factor_internal(float(config.val.zoom.default) / 100)
def set_current(self):
self._set_factor_internal(self._zoom_factor)
default_zoom = config.get('ui', 'default-zoom')
self._set_factor_internal(float(default_zoom) / 100)
class AbstractCaret(QObject):
"""Attribute of AbstractTab for caret browsing.
"""Attribute of AbstractTab for caret browsing."""
Signals:
selection_toggled: Emitted when the selection was toggled.
arg: Whether the selection is now active.
"""
selection_toggled = pyqtSignal(bool)
def __init__(self, tab, mode_manager, parent=None):
def __init__(self, win_id, tab, mode_manager, parent=None):
super().__init__(parent)
self._tab = tab
self._win_id = win_id
self._widget = None
self.selection_enabled = False
mode_manager.entered.connect(self._on_mode_entered)
@@ -385,7 +291,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 +345,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 +387,6 @@ class AbstractScroller(QObject):
def to_point(self, point):
raise NotImplementedError
def to_anchor(self, name):
raise NotImplementedError
def delta(self, x=0, y=0):
raise NotImplementedError
@@ -542,23 +441,11 @@ class AbstractHistory:
def current_idx(self):
raise NotImplementedError
def back(self, count=1):
"""Go back in the tab's history."""
idx = self.current_idx() - count
if idx >= 0:
self._go_to_item(self._item_at(idx))
else:
self._go_to_item(self._item_at(0))
raise WebTabError("At beginning of history.")
def back(self):
raise NotImplementedError
def forward(self, count=1):
"""Go forward in the tab's history."""
idx = self.current_idx() + count
if idx < len(self):
self._go_to_item(self._item_at(idx))
else:
self._go_to_item(self._item_at(len(self) - 1))
raise WebTabError("At end of history.")
def forward(self):
raise NotImplementedError
def can_go_back(self):
raise NotImplementedError
@@ -566,12 +453,6 @@ class AbstractHistory:
def can_go_forward(self):
raise NotImplementedError
def _item_at(self, i):
raise NotImplementedError
def _go_to_item(self, item):
raise NotImplementedError
def serialize(self):
"""Serialize into an opaque format understood by self.deserialize."""
raise NotImplementedError
@@ -634,33 +515,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.
@@ -670,7 +524,6 @@ class AbstractTab(QWidget):
Attributes:
history: The AbstractHistory for the current tab.
registry: The ObjectRegistry associated with this tab.
private: Whether private browsing is turned on for this tab.
_load_status: loading status of this page
Accessible via load_status() method.
@@ -692,7 +545,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,10 +562,8 @@ 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
def __init__(self, win_id, mode_manager, parent=None):
self.win_id = win_id
self.tab_id = next(tab_id_gen)
super().__init__(parent)
@@ -724,6 +574,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 +601,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 +613,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 +641,12 @@ class AbstractTab(QWidget):
# This only gives us some mild protection against re-using events, but
# it's certainly better than a segfault.
if getattr(evt, 'posted', False):
raise utils.Unreachable("Can't re-use an event which was already "
"posted!")
raise AssertionError("Can't re-use an event which was already "
"posted!")
recipient = self.event_target()
if recipient is None:
# https://github.com/qutebrowser/qutebrowser/issues/3888
log.webview.warning("Unable to find event target!")
return
evt.posted = True
QApplication.postEvent(recipient, evt)
@pyqtSlot(QUrl)
def _on_predicted_navigation(self, url):
"""Adjust the title if we are going to visit an URL soon."""
qtutils.ensure_valid(url)
url_string = url.toDisplayString()
log.webview.debug("Predicted navigation: {}".format(url_string))
self.title_changed.emit(url_string)
@pyqtSlot(QUrl)
def _on_url_changed(self, url):
"""Update title when URL has changed and no title is available."""
@@ -820,26 +662,9 @@ class AbstractTab(QWidget):
self._set_load_status(usertypes.LoadStatus.loading)
self.load_started.emit()
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
"""Handle common acceptNavigationRequest code."""
url = utils.elide(navigation.url.toDisplayString(), 100)
log.webview.debug("navigation request: url {}, type {}, is_main_frame "
"{}".format(url,
navigation.navigation_type,
navigation.is_main_frame))
if (navigation.navigation_type == navigation.Type.link_clicked and
not navigation.url.isValid()):
msg = urlutils.get_errstring(navigation.url,
"Invalid link clicked")
message.error(msg)
self.data.open_target = usertypes.ClickTarget.normal
navigation.accepted = False
def handle_auto_insert_mode(self, ok):
"""Handle `input.insert_mode.auto_load` after loading finished."""
if not config.val.input.insert_mode.auto_load or not ok:
def _handle_auto_insert_mode(self, ok):
"""Handle auto-insert-mode after loading finished."""
if not config.get('input', 'auto-insert-mode') or not ok:
return
cur_mode = self._mode_manager.mode
@@ -859,10 +684,6 @@ class AbstractTab(QWidget):
@pyqtSlot(bool)
def _on_load_finished(self, ok):
if sip.isdeleted(self._widget):
# https://github.com/qutebrowser/qutebrowser/issues/3498
return
sess_manager = objreg.get('session-manager')
sess_manager.save_autosave()
@@ -875,13 +696,10 @@ class AbstractTab(QWidget):
self._set_load_status(usertypes.LoadStatus.warn)
else:
self._set_load_status(usertypes.LoadStatus.error)
self.load_finished.emit(ok)
if not self.title():
self.title_changed.emit(self.url().toDisplayString())
self.zoom.set_current()
self._handle_auto_insert_mode(ok)
@pyqtSlot()
def _on_history_trigger(self):
@@ -893,6 +711,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 +724,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):
@@ -919,12 +740,8 @@ class AbstractTab(QWidget):
def clear_ssl_errors(self):
raise NotImplementedError
def key_press(self, key, modifier=Qt.NoModifier):
"""Send a fake key event to this tab."""
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.
@@ -954,7 +771,7 @@ class AbstractTab(QWidget):
def icon(self):
raise NotImplementedError
def set_html(self, html, base_url=QUrl()):
def set_html(self, html, base_url):
raise NotImplementedError
def networkaccessmanager(self):
@@ -977,9 +794,6 @@ class AbstractTab(QWidget):
try:
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),
100)
except (AttributeError, RuntimeError) as exc:
url = '<{}>'.format(exc.__class__.__name__)
except AttributeError:
url = '<AttributeError>'
return utils.get_repr(self, tab_id=self.tab_id, url=url)
def is_deleted(self):
return sip.isdeleted(self._widget)

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-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,27 +19,25 @@
"""Shared QtWebKit/QtWebEngine code for downloads."""
import re
import sys
import html
import os.path
import collections
import functools
import pathlib
import tempfile
import enum
import sip
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
QTimer, QAbstractListModel, QUrl)
QTimer, QAbstractListModel)
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config import config
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
qtutils)
from qutebrowser.qt import sip
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
is_int=True)
# Remember the last used directory
@@ -69,22 +67,15 @@ class UnsupportedOperationError(Exception):
def download_dir():
"""Get the download directory to use."""
directory = config.val.downloads.location.directory
remember_dir = config.val.downloads.location.remember
directory = config.get('storage', 'download-directory')
remember_dir = config.get('storage', 'remember-download-directory')
if remember_dir and last_used_directory is not None:
ddir = last_used_directory
return last_used_directory
elif directory is None:
ddir = standarddir.download()
return standarddir.download()
else:
ddir = directory
try:
os.makedirs(ddir)
except FileExistsError:
pass
return ddir
return directory
def immediate_download_path(prompt_download_directory=None):
@@ -95,16 +86,15 @@ def immediate_download_path(prompt_download_directory=None):
Args:
prompt_download_directory: If this is something else than None, it
will overwrite the
downloads.location.prompt setting.
storage->prompt-download-directory setting.
"""
if prompt_download_directory is None:
prompt_download_directory = config.val.downloads.location.prompt
prompt_download_directory = config.get('storage',
'prompt-download-directory')
if not prompt_download_directory:
return download_dir()
return None
def _path_suggestion(filename):
"""Get the suggested file path.
@@ -112,7 +102,7 @@ def _path_suggestion(filename):
Args:
filename: The filename to use if included in the suggestion.
"""
suggestion = config.val.downloads.location.suggestion
suggestion = config.get('completion', 'download-path-suggestion')
if suggestion == 'path':
# add trailing '/' if not present
return os.path.join(download_dir(), '')
@@ -139,8 +129,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,54 +155,12 @@ 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)
return q
def transform_path(path):
r"""Do platform-specific transformations, like changing E: to E:\.
Returns None if the path is invalid on the current platform.
"""
if not utils.is_windows:
return path
path = utils.expand_windows_drive(path)
# Drive dependent working directories are not supported, e.g.
# E:filename is invalid
if re.search(r'^[A-Z]:[^\\]', path, re.IGNORECASE):
return None
# Paths like COM1, ...
# See https://github.com/qutebrowser/qutebrowser/issues/82
if pathlib.Path(path).is_reserved():
return None
return path
def suggested_fn_from_title(url_path, title=None):
"""Suggest a filename depending on the URL extension and page title.
Args:
url_path: a string with the URL path
title: the page title string
Return:
The download filename based on the title, or None if the extension is
not found in the whitelist (or if there is no page title).
"""
ext_whitelist = [".html", ".htm", ".php", ""]
_, ext = os.path.splitext(url_path)
if ext.lower() in ext_whitelist and title:
suggested_fn = utils.sanitize_filename(title)
if not suggested_fn.lower().endswith((".html", ".htm")):
suggested_fn += ".html"
else:
suggested_fn = None
return suggested_fn
class NoFilenameError(Exception):
"""Raised when we can't find out a filename in DownloadTarget."""
@@ -238,14 +185,11 @@ class FileDownloadTarget(_DownloadTarget):
Attributes:
filename: Filename where the download should be saved.
force_overwrite: Whether to overwrite the target without
prompting the user.
"""
def __init__(self, filename, force_overwrite=False):
def __init__(self, filename):
# pylint: disable=super-init-not-called
self.filename = filename
self.force_overwrite = force_overwrite
def suggested_filename(self):
return os.path.basename(self.filename)
@@ -507,13 +451,13 @@ class AbstractDownloadItem(QObject):
Args:
position: The color type requested, can be 'fg' or 'bg'.
"""
# pylint: disable=bad-config-call
# WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104/
assert position in ["fg", "bg"]
# pylint: disable=bad-config-option
start = getattr(config.val.colors.downloads.start, position)
stop = getattr(config.val.colors.downloads.stop, position)
system = getattr(config.val.colors.downloads.system, position)
error = getattr(config.val.colors.downloads.error, position)
# pylint: enable=bad-config-option
start = config.get('colors', 'downloads.{}.start'.format(position))
stop = config.get('colors', 'downloads.{}.stop'.format(position))
system = config.get('colors', 'downloads.{}.system'.format(position))
error = config.get('colors', 'downloads.{}.error'.format(position))
if self.error_msg is not None:
assert not self.successful
return error
@@ -563,14 +507,6 @@ class AbstractDownloadItem(QObject):
"""Retry a failed download."""
raise NotImplementedError
@pyqtSlot()
def try_retry(self):
"""Try to retry a download and show an error if it's unsupported."""
try:
self.retry()
except UnsupportedOperationError as e:
message.error(str(e))
def _get_open_filename(self):
"""Get the filename to open a download.
@@ -585,7 +521,7 @@ class AbstractDownloadItem(QObject):
Args:
cmdline: The command to use as string. A `{}` is expanded to the
filename. None means to use the system's default
application or `downloads.open_dispatcher` if set. If no
application or `default-open-dispatcher` if set. If no
`{}` is found, the filename is appended to the cmdline.
"""
assert self.successful
@@ -611,11 +547,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 +573,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 +600,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 +643,7 @@ class AbstractDownloadItem(QObject):
if isinstance(target, FileObjDownloadTarget):
self._set_fileobj(target.fileobj, autoclose=False)
elif isinstance(target, FileDownloadTarget):
self._set_filename(
target.filename, force_overwrite=target.force_overwrite)
self._set_filename(target.filename)
elif isinstance(target, OpenFileDownloadTarget):
try:
fobj = temp_download_manager.get_tmpfile(self.basename)
@@ -805,7 +706,7 @@ class AbstractDownloadManager(QObject):
download.remove_requested.connect(functools.partial(
self._remove_item, download))
delay = config.val.downloads.remove_finished
delay = config.get('ui', 'remove-finished-downloads')
if delay > -1:
download.finished.connect(
lambda: QTimer.singleShot(delay, download.remove))
@@ -997,7 +898,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')
@@ -1022,7 +923,7 @@ class DownloadModel(QAbstractListModel):
@cmdutils.register(instance='download-model', scope='window', maxsplit=0)
@cmdutils.argument('count', count=True)
def download_open(self, cmdline: str = None, count=0):
def download_open(self, cmdline: str=None, count=0):
"""Open the last/[count]th download.
If no specific command is given, this will use the system's default
@@ -1067,7 +968,7 @@ class DownloadModel(QAbstractListModel):
raise cmdexc.CommandError("No failed downloads!")
else:
download = to_retry[0]
download.try_retry()
download.retry()
def can_clear(self):
"""Check if there are finished downloads to clear."""

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-2016 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 PyQt5.QtWidgets import QListView, QSizePolicy, QMenu
from qutebrowser.browser import downloads
from qutebrowser.config import config
from qutebrowser.config import style
from qutebrowser.utils import qtutils, utils, objreg
from qutebrowser.qt import sip
def update_geometry(obj):
@@ -64,8 +64,8 @@ class DownloadView(QListView):
STYLESHEET = """
QListView {
background-color: {{ conf.colors.downloads.bar.bg }};
font: {{ conf.fonts.downloads }};
background-color: {{ color['downloads.bg.bar'] }};
font: {{ font['downloads'] }};
}
QListView::item {
@@ -75,8 +75,7 @@ class DownloadView(QListView):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self.setStyle(QStyleFactory.create('Fusion'))
config.set_register_stylesheet(self)
style.set_register_stylesheet(self)
self.setResizeMode(QListView.Adjust)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
@@ -135,7 +134,7 @@ class DownloadView(QListView):
if item.successful:
actions.append(("Open", item.open_file))
else:
actions.append(("Retry", item.try_retry))
actions.append(("Retry", item.retry))
actions.append(("Remove", item.remove))
else:
actions.append(("Cancel", item.cancel))

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-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -22,26 +22,24 @@
import collections
import functools
import math
import os
import re
import html
import enum
from string import ascii_lowercase
import attr
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
from PyQt5.QtWidgets import QLabel
from qutebrowser.config import config
from qutebrowser.config import config, style
from qutebrowser.keyinput import modeman, modeparsers
from qutebrowser.browser import webelem
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
from qutebrowser.utils import usertypes, log, qtutils, message, objreg, utils
Target = enum.Enum('Target', ['normal', 'current', 'tab', 'tab_fg', 'tab_bg',
'window', 'yank', 'yank_primary', 'run', 'fill',
'hover', 'download', 'userscript', 'spawn'])
Target = usertypes.enum('Target', ['normal', 'current', 'tab', 'tab_fg',
'tab_bg', 'window', 'yank', 'yank_primary',
'run', 'fill', 'hover', 'download',
'userscript', 'spawn'])
class HintingError(Exception):
@@ -67,10 +65,10 @@ class HintLabel(QLabel):
STYLESHEET = """
QLabel {
background-color: {{ conf.colors.hints.bg }};
color: {{ conf.colors.hints.fg }};
font: {{ conf.fonts.hints }};
border: {{ conf.hints.border }};
background-color: {{ color['hints.bg'] }};
color: {{ color['hints.fg'] }};
font: {{ font['hints'] }};
border: {{ config.get('hints', 'border') }};
padding-left: -3px;
padding-right: -3px;
}
@@ -82,7 +80,7 @@ class HintLabel(QLabel):
self.elem = elem
self.setAttribute(Qt.WA_StyledBackground, True)
config.set_register_stylesheet(self)
style.set_register_stylesheet(self)
self._context.tab.contents_size_changed.connect(self._move_to_elem)
self._move_to_elem()
@@ -102,7 +100,7 @@ class HintLabel(QLabel):
matched: The part of the text which was typed.
unmatched: The part of the text which was not typed yet.
"""
if (config.val.hints.uppercase and
if (config.get('hints', 'uppercase') and
self._context.hint_mode in ['letter', 'word']):
matched = html.escape(matched.upper())
unmatched = html.escape(unmatched.upper())
@@ -110,7 +108,7 @@ class HintLabel(QLabel):
matched = html.escape(matched)
unmatched = html.escape(unmatched)
match_color = html.escape(config.val.colors.hints.match.fg)
match_color = html.escape(config.get('colors', 'hints.fg.match'))
self.setText('<font color="{}">{}</font>{}'.format(
match_color, matched, unmatched))
self.adjustSize()
@@ -123,7 +121,7 @@ class HintLabel(QLabel):
log.hints.debug("Frame for {!r} vanished!".format(self))
self.hide()
return
no_js = config.val.hints.find_implementation != 'javascript'
no_js = config.get('hints', 'find-implementation') != 'javascript'
rect = self.elem.rect_on_view(no_js=no_js)
self.move(rect.x(), rect.y())
@@ -133,7 +131,6 @@ class HintLabel(QLabel):
self.deleteLater()
@attr.s
class HintContext:
"""Context namespace used for hinting.
@@ -155,27 +152,25 @@ class HintContext:
to_follow: The link to follow when enter is pressed.
args: Custom arguments for userscript/spawn
rapid: Whether to do rapid hinting.
first_run: Whether the action is run for the 1st time in rapid hinting.
add_history: Whether to add yanked or spawned link to the history.
filterstr: Used to save the filter string for restoring in rapid mode.
tab: The WebTab object we started hinting in.
group: The group of web elements to hint.
"""
all_labels = attr.ib(attr.Factory(list))
labels = attr.ib(attr.Factory(dict))
target = attr.ib(None)
baseurl = attr.ib(None)
to_follow = attr.ib(None)
rapid = attr.ib(False)
first_run = attr.ib(True)
add_history = attr.ib(False)
filterstr = attr.ib(None)
args = attr.ib(attr.Factory(list))
tab = attr.ib(None)
group = attr.ib(None)
hint_mode = attr.ib(None)
first = attr.ib(False)
def __init__(self):
self.all_labels = []
self.labels = {}
self.target = None
self.baseurl = None
self.to_follow = None
self.rapid = False
self.add_history = False
self.filterstr = None
self.args = []
self.tab = None
self.group = None
self.hint_mode = None
def get_args(self, urlstr):
"""Get the arguments, with {hint-url} replaced by the given URL."""
@@ -208,7 +203,7 @@ class HintActions:
Target.window: usertypes.ClickTarget.window,
Target.hover: usertypes.ClickTarget.normal,
}
if config.val.tabs.background:
if config.get('tabs', 'background-tabs'):
target_mapping[Target.tab] = usertypes.ClickTarget.tab_bg
else:
target_mapping[Target.tab] = usertypes.ClickTarget.tab
@@ -244,18 +239,7 @@ class HintActions:
if url.scheme() == 'mailto':
flags |= QUrl.RemoveScheme
urlstr = url.toString(flags)
new_content = urlstr
# only second and consecutive yanks are to append to the clipboard
if context.rapid and not context.first_run:
try:
old_content = utils.get_clipboard(selection=sel)
except utils.ClipboardEmptyError:
pass
else:
new_content = os.linesep.join([old_content, new_content])
utils.set_clipboard(new_content, selection=sel)
utils.set_clipboard(urlstr, selection=sel)
msg = "Yanked URL to {}: {}".format(
"primary selection" if sel else "clipboard",
@@ -306,7 +290,8 @@ class HintActions:
user_agent = context.tab.user_agent()
# FIXME:qtwebengine do this with QtWebEngine downloads?
download_manager = objreg.get('qtnetwork-download-manager')
download_manager = objreg.get('qtnetwork-download-manager',
scope='window', window=self._win_id)
download_manager.get(url, qnam=qnam, user_agent=user_agent,
prompt_download_directory=prompt)
@@ -436,9 +421,9 @@ class HintManager(QObject):
if hint_mode == 'number':
chars = '0123456789'
else:
chars = config.val.hints.chars
min_chars = config.val.hints.min_chars
if config.val.hints.scatter and hint_mode != 'number':
chars = config.get('hints', 'chars')
min_chars = config.get('hints', 'min-chars')
if config.get('hints', 'scatter') and hint_mode != 'number':
return self._hint_scattered(min_chars, chars, elems)
else:
return self._hint_linear(min_chars, chars, elems)
@@ -458,17 +443,8 @@ class HintManager(QObject):
# Short hints are the number of hints we can possibly show which are
# (needed - 1) digits in length.
if needed > min_chars:
total_space = len(chars) ** needed
# Calculate short_count naively, by finding the avaiable space and
# dividing by the number of spots we would loose by adding a
# short element
short_count = math.floor((total_space - len(elems)) /
short_count = math.floor((len(chars) ** needed - len(elems)) /
len(chars))
# Check if we double counted above to warrant another short_count
# https://github.com/qutebrowser/qutebrowser/issues/3242
if total_space - (short_count * len(chars) +
(len(elems) - short_count)) >= len(chars) - 1:
short_count += 1
else:
short_count = 0
@@ -603,10 +579,12 @@ class HintManager(QObject):
if elems is None:
message.error("There was an error while getting hint elements")
return
filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True)
elems = [e for e in elems if filterfunc(e)]
if not elems:
message.error("No elements found.")
return
strings = self._hint_strings(elems)
log.hints.debug("hints: {}".format(', '.join(strings)))
@@ -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
# to make auto-follow == 'always' work
self._handle_auto_follow()
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
star_args_optional=True, maxsplit=2)
@cmdutils.argument('win_id', win_id=True)
def start(self, # pylint: disable=keyword-arg-before-vararg
group=webelem.Group.all, target=Target.normal,
*args, win_id, mode=None, add_history=False, rapid=False,
first=False):
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
*args, win_id, mode=None, add_history=False):
"""Start hinting.
Args:
rapid: Whether to do rapid hinting. With rapid hinting, the hint
mode isn't left after a hint is followed, so you can easily
open multiple links. This is only possible with targets
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
rapid: Whether to do rapid hinting. This is only possible with
targets `tab` (with background-tabs=true), `tab-bg`,
`window`, `run`, `hover`, `userscript` and `spawn`.
add_history: Whether to add the spawned or yanked link to the
browsing history.
first: Click the first hinted element without prompting.
group: The element types to hint.
- `all`: All clickable elements.
@@ -663,7 +633,7 @@ class HintManager(QObject):
- `normal`: Open the link.
- `current`: Open the link in the current tab.
- `tab`: Open the link in a new tab (honoring the
`tabs.background_tabs` setting).
background-tabs setting).
- `tab-fg`: Open the link in a new foreground tab.
- `tab-bg`: Open the link in a new background tab.
- `window`: Open the link in a new window.
@@ -681,7 +651,7 @@ class HintManager(QObject):
mode: The hinting mode to use.
- `number`: Use numeric hints.
- `letter`: Use the chars in the hints.chars setting.
- `letter`: Use the chars in the hints->chars settings.
- `word`: Use hint words based on the html elements and the
extra words.
@@ -702,7 +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,10 +684,10 @@ class HintManager(QObject):
if rapid:
if target in [Target.tab_bg, Target.window, Target.run,
Target.hover, Target.userscript, Target.spawn,
Target.download, Target.normal, Target.current,
Target.yank, Target.yank_primary]:
Target.download, Target.normal, Target.current]:
pass
elif target == Target.tab and config.val.tabs.background:
elif (target == Target.tab and
config.get('tabs', 'background-tabs')):
pass
else:
name = target.name.replace('_', '-')
@@ -725,7 +695,7 @@ class HintManager(QObject):
"target {}!".format(name))
if mode is None:
mode = config.val.hints.mode
mode = config.get('hints', 'mode')
self._check_args(target, *args)
self._context = HintContext()
@@ -734,7 +704,6 @@ class HintManager(QObject):
self._context.rapid = rapid
self._context.hint_mode = mode
self._context.add_history = add_history
self._context.first = first
try:
self._context.baseurl = tabbed_browser.current_url()
except qtutils.QtValueError:
@@ -753,7 +722,7 @@ class HintManager(QObject):
return self._context.hint_mode
def _handle_auto_follow(self, keystr="", filterstr="", visible=None):
"""Handle the auto_follow option."""
"""Handle the auto-follow option."""
if visible is None:
visible = {string: label
for string, label in self._context.labels.items()
@@ -762,7 +731,7 @@ class HintManager(QObject):
if len(visible) != 1:
return
auto_follow = config.val.hints.auto_follow
auto_follow = config.get('hints', 'auto-follow')
if auto_follow == "always":
follow = True
@@ -779,8 +748,8 @@ class HintManager(QObject):
self._context.to_follow = list(visible.keys())[0]
if follow:
# apply auto_follow_timeout
timeout = config.val.hints.auto_follow_timeout
# apply auto-follow-timeout
timeout = config.get('hints', 'auto-follow-timeout')
keyparsers = objreg.get('keyparsers', scope='window',
window=self._win_id)
normal_parser = keyparsers[usertypes.KeyMode.normal]
@@ -804,9 +773,9 @@ class HintManager(QObject):
label.show()
else:
# element doesn't match anymore -> hide it, unless in rapid
# mode and hide_unmatched_rapid_hints is false (see #1799)
# mode and hide-unmatched-rapid-hints is false (see #1799)
if (not self._context.rapid or
config.val.hints.hide_unmatched_rapid_hints):
config.get('hints', 'hide-unmatched-rapid-hints')):
label.hide()
except webelem.Error:
pass
@@ -826,8 +795,6 @@ class HintManager(QObject):
else:
self._context.filterstr = filterstr
log.hints.debug("Filtering hints on {!r}".format(filterstr))
visible = []
for label in self._context.all_labels:
try:
@@ -929,32 +896,22 @@ class HintManager(QObject):
except HintingError as e:
message.error(str(e))
if self._context is not None:
self._context.first_run = False
@cmdutils.register(instance='hintmanager', scope='tab',
@cmdutils.register(instance='hintmanager', scope='tab', hide=True,
modes=[usertypes.KeyMode.hint])
def follow_hint(self, select=False, keystring=None):
def follow_hint(self, keystring=None):
"""Follow a hint.
Args:
select: Only select the given hint, don't necessarily follow it.
keystring: The hint to follow, or None.
"""
if keystring is None:
if self._context.to_follow is None:
raise cmdexc.CommandError("No hint to follow")
elif select:
raise cmdexc.CommandError("Can't use --select without hint.")
else:
keystring = self._context.to_follow
elif keystring not in self._context.labels:
raise cmdexc.CommandError("No hint {}!".format(keystring))
if select:
self.handle_partial_key(keystring)
else:
self._fire(keystring)
self._fire(keystring)
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
@@ -983,7 +940,7 @@ class WordHinter:
def ensure_initialized(self):
"""Generate the used words if yet uninitialized."""
dictionary = config.val.hints.dictionary
dictionary = config.get("hints", "dictionary")
if not self.words or self.dictionary != dictionary:
self.words.clear()
self.dictionary = dictionary

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-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@@ -19,127 +19,215 @@
"""Simple history which gets written to disk."""
import os
import time
import contextlib
import collections
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
debug, standarddir, qtutils)
from qutebrowser.misc import objects, sql
from qutebrowser.commands import cmdutils
from qutebrowser.utils import (utils, objreg, standarddir, log, qtutils,
usertypes, message)
from qutebrowser.config import config
from qutebrowser.misc import lineparser, objects
# increment to indicate that HistoryCompletion must be regenerated
_USER_VERSION = 2
class Entry:
"""A single entry in the web history.
Attributes:
atime: The time the page was accessed.
url: The URL which was accessed as QUrl.
redirect: If True, don't save this entry to disk
"""
def __init__(self, atime, url, title, redirect=False):
self.atime = float(atime)
self.url = url
self.title = title
self.redirect = redirect
qtutils.ensure_valid(url)
def __repr__(self):
return utils.get_repr(self, constructor=True, atime=self.atime,
url=self.url_str(), title=self.title,
redirect=self.redirect)
def __str__(self):
atime = str(int(self.atime))
if self.redirect:
atime += '-r' # redirect flag
elems = [atime, self.url_str()]
if self.title:
elems.append(self.title)
return ' '.join(elems)
def __eq__(self, other):
return (self.atime == other.atime and
self.title == other.title and
self.url == other.url and
self.redirect == other.redirect)
def url_str(self):
"""Get the URL as a lossless string."""
return self.url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
@classmethod
def from_str(cls, line):
"""Parse a history line like '12345 http://example.com title'."""
data = line.split(maxsplit=2)
if len(data) == 2:
atime, url = data
title = ""
elif len(data) == 3:
atime, url, title = data
else:
raise ValueError("2 or 3 fields expected")
url = QUrl(url)
if not url.isValid():
raise ValueError("Invalid URL: {}".format(url.errorString()))
# https://github.com/qutebrowser/qutebrowser/issues/670
atime = atime.lstrip('\0')
if '-' in atime:
atime, flags = atime.split('-')
else:
flags = ''
if not set(flags).issubset('r'):
raise ValueError("Invalid flags {!r}".format(flags))
redirect = 'r' in flags
return cls(atime, url, title, redirect=redirect)
class CompletionHistory(sql.SqlTable):
class WebHistory(QObject):
"""History which only has the newest entry for each URL."""
"""The global history of visited pages.
def __init__(self, parent=None):
super().__init__("CompletionHistory", ['url', 'title', 'last_atime'],
constraints={'url': 'PRIMARY KEY',
'title': 'NOT NULL',
'last_atime': 'NOT NULL'},
parent=parent)
self.create_index('CompletionHistoryAtimeIndex', 'last_atime')
This is a little more complex as you'd expect so the history can be read
from disk async while new history is already arriving.
self.history_dict is the main place where the history is stored, in an
OrderedDict (sorted by time) of URL strings mapped to Entry objects.
class WebHistory(sql.SqlTable):
While reading from disk is still ongoing, the history is saved in
self._temp_history instead, and then appended to self.history_dict once
that's fully populated.
"""The global history of visited pages."""
All history which is new in this session (rather than read from disk from a
previous browsing session) is also stored in self._new_history.
self._saved_count tracks how many of those entries were already written to
disk, so we can always append to the existing data.
# All web history cleared
history_cleared = pyqtSignal()
# one url cleared
url_cleared = pyqtSignal(QUrl)
Attributes:
history_dict: An OrderedDict of URLs read from the on-disk history.
_lineparser: The AppendLineParser used to save the history.
_new_history: A list of Entry items of the current session.
_saved_count: How many HistoryEntries have been written to disk.
_initial_read_started: Whether async_read was called.
_initial_read_done: Whether async_read has completed.
_temp_history: OrderedDict of temporary history entries before
async_read was called.
def __init__(self, parent=None):
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
constraints={'url': 'NOT NULL',
'title': 'NOT NULL',
'atime': 'NOT NULL',
'redirect': 'NOT NULL'},
parent=parent)
self.completion = CompletionHistory(parent=self)
if sql.Query('pragma user_version').run().value() < _USER_VERSION:
self.completion.delete_all()
if not self.completion:
# either the table is out-of-date or the user wiped it manually
self._rebuild_completion()
self.create_index('HistoryIndex', 'url')
self.create_index('HistoryAtimeIndex', 'atime')
self._contains_query = self.contains_query('url')
self._between_query = sql.Query('SELECT * FROM History '
'where not redirect '
'and not url like "qute://%" '
'and atime > :earliest '
'and atime <= :latest '
'ORDER BY atime desc')
Signals:
add_completion_item: Emitted before a new Entry is added.
Used to sync with the completion.
arg: The new Entry.
item_added: Emitted after a new Entry is added.
Used to tell the savemanager that the history is dirty.
arg: The new Entry.
cleared: Emitted after the history is cleared.
"""
self._before_query = sql.Query('SELECT * FROM History '
'where not redirect '
'and not url like "qute://%" '
'and atime <= :latest '
'ORDER BY atime desc '
'limit :limit offset :offset')
add_completion_item = pyqtSignal(Entry)
item_added = pyqtSignal(Entry)
cleared = pyqtSignal()
async_read_done = pyqtSignal()
def __init__(self, hist_dir, hist_name, parent=None):
super().__init__(parent)
self._initial_read_started = False
self._initial_read_done = False
self._lineparser = lineparser.AppendLineParser(hist_dir, hist_name,
parent=self)
self.history_dict = collections.OrderedDict()
self._temp_history = collections.OrderedDict()
self._new_history = []
self._saved_count = 0
objreg.get('save-manager').add_saveable(
'history', self.save, self.item_added)
def __repr__(self):
return utils.get_repr(self, length=len(self))
def __contains__(self, url):
return self._contains_query.run(val=url).value()
def __iter__(self):
return iter(self.history_dict.values())
@contextlib.contextmanager
def _handle_sql_errors(self):
try:
yield
except sql.SqlError as e:
if e.environmental:
message.error("Failed to write history: {}".format(e.text()))
else:
raise
def __len__(self):
return len(self.history_dict)
def _rebuild_completion(self):
data = {'url': [], 'title': [], 'last_atime': []}
# select the latest entry for each url
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
'WHERE NOT redirect and url NOT LIKE "qute://back%" '
'GROUP BY url ORDER BY atime asc')
for entry in q.run():
data['url'].append(self._format_completion_url(QUrl(entry.url)))
data['title'].append(entry.title)
data['last_atime'].append(entry.atime)
self.completion.insert_batch(data, replace=True)
sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
def async_read(self):
"""Read the initial history."""
if self._initial_read_started:
log.init.debug("Ignoring async_read() because reading is started.")
return
self._initial_read_started = True
with self._lineparser.open():
for line in self._lineparser:
yield
line = line.rstrip()
if not line:
continue
try:
entry = Entry.from_str(line)
except ValueError as e:
log.init.warning("Invalid history entry {!r}: {}!".format(
line, e))
continue
# This de-duplicates history entries; only the latest
# entry for each URL is kept. If you want to keep
# information about previous hits change the items in
# old_urls to be lists or change Entry to have a
# list of atimes.
self._add_entry(entry)
self._initial_read_done = True
self.async_read_done.emit()
for entry in self._temp_history.values():
self._add_entry(entry)
self._new_history.append(entry)
if not entry.redirect:
self.add_completion_item.emit(entry)
self._temp_history.clear()
def _add_entry(self, entry, target=None):
"""Add an entry to self.history_dict or another given OrderedDict."""
if target is None:
target = self.history_dict
url_str = entry.url_str()
target[url_str] = entry
target.move_to_end(url_str)
def get_recent(self):
"""Get the most recent history entries."""
return self.select(sort_by='atime', sort_order='desc', limit=100)
old = self._lineparser.get_recent()
return old + [str(e) for e in self._new_history]
def entries_between(self, earliest, latest):
"""Iterate non-redirect, non-qute entries between two timestamps.
Args:
earliest: Omit timestamps earlier than this.
latest: Omit timestamps later than this.
"""
self._between_query.run(earliest=earliest, latest=latest)
return iter(self._between_query)
def entries_before(self, latest, limit, offset):
"""Iterate non-redirect, non-qute entries occurring before a timestamp.
Args:
latest: Omit timestamps more recent than this.
limit: Max number of entries to include.
offset: Number of entries to skip.
"""
self._before_query.run(latest=latest, limit=limit, offset=offset)
return iter(self._before_query)
def save(self):
"""Save the history to disk."""
new = (str(e) for e in self._new_history[self._saved_count:])
self._lineparser.new_data = new
self._lineparser.save()
self._saved_count = len(self._new_history)
@cmdutils.register(name='history-clear', instance='web-history')
def clear(self, force=False):
@@ -155,38 +243,20 @@ class WebHistory(sql.SqlTable):
if force:
self._do_clear()
else:
message.confirm_async(yes_action=self._do_clear,
title="Clear all browsing history?")
message.confirm_async(self._do_clear, title="Clear all browsing "
"history?")
def _do_clear(self):
with self._handle_sql_errors():
self.delete_all()
self.completion.delete_all()
self.history_cleared.emit()
def delete_url(self, url):
"""Remove all history entries with the given url.
Args:
url: URL string to delete.
"""
qurl = QUrl(url)
qtutils.ensure_valid(qurl)
self.delete('url', self._format_url(qurl))
self.completion.delete('url', self._format_completion_url(qurl))
self.url_cleared.emit(qurl)
self._lineparser.clear()
self.history_dict.clear()
self._temp_history.clear()
self._new_history.clear()
self._saved_count = 0
self.cleared.emit()
@pyqtSlot(QUrl, QUrl, str)
def add_from_tab(self, url, requested_url, title):
"""Add a new history entry as slot, called from a BrowserTab."""
if any(url.scheme() in ('data', 'view-source') or
(url.scheme(), url.host()) == ('qute', 'back')
for url in (url, requested_url)):
return
if url.isEmpty():
# things set via setHtml
return
no_formatting = QUrl.UrlFormattingOption(0)
if (requested_url.isValid() and
not requested_url.matches(url, no_formatting)):
@@ -204,154 +274,23 @@ class WebHistory(sql.SqlTable):
(hidden in completion)
atime: Override the atime used to add the entry
"""
if config.get('general', 'private-browsing'):
return
if not url.isValid():
log.misc.warning("Ignoring invalid URL being added to history")
return
if 'no-sql-history' in objreg.get('args').debug_flags:
return
atime = int(atime) if (atime is not None) else int(time.time())
with self._handle_sql_errors():
self.insert({'url': self._format_url(url),
'title': title,
'atime': atime,
'redirect': redirect})
if not redirect:
self.completion.insert({
'url': self._format_completion_url(url),
'title': title,
'last_atime': atime
}, replace=True)
def _parse_entry(self, line):
"""Parse a history line like '12345 http://example.com title'."""
if not line or line.startswith('#'):
return None
data = line.split(maxsplit=2)
if len(data) == 2:
atime, url = data
title = ""
elif len(data) == 3:
atime, url, title = data
if atime is None:
atime = time.time()
entry = Entry(atime, url, title, redirect=redirect)
if self._initial_read_done:
self._add_entry(entry)
self._new_history.append(entry)
self.item_added.emit(entry)
if not entry.redirect:
self.add_completion_item.emit(entry)
else:
raise ValueError("2 or 3 fields expected")
# http://xn--pple-43d.com/ with
# https://bugreports.qt.io/browse/QTBUG-60364
if url in ['http://.com/', 'https://.com/',
'http://www..com/', 'https://www..com/']:
return None
url = QUrl(url)
if not url.isValid():
raise ValueError("Invalid URL: {}".format(url.errorString()))
# https://github.com/qutebrowser/qutebrowser/issues/2646
if url.scheme() == 'data':
return None
# https://github.com/qutebrowser/qutebrowser/issues/670
atime = atime.lstrip('\0')
if '-' in atime:
atime, flags = atime.split('-')
else:
flags = ''
if not set(flags).issubset('r'):
raise ValueError("Invalid flags {!r}".format(flags))
redirect = 'r' in flags
return (url, title, int(atime), redirect)
def import_txt(self):
"""Import a history text file into sqlite if it exists.
In older versions of qutebrowser, history was stored in a text format.
This converts that file into the new sqlite format and moves it to a
backup location.
"""
path = os.path.join(standarddir.data(), 'history')
if not os.path.isfile(path):
return
def action():
"""Actually run the import."""
with debug.log_time(log.init, 'Import old history file to sqlite'):
try:
self._read(path)
except ValueError as ex:
message.error('Failed to import history: {}'.format(ex))
else:
self._write_backup(path)
# delay to give message time to appear before locking down for import
message.info('Converting {} to sqlite...'.format(path))
QTimer.singleShot(100, action)
def _read(self, path):
"""Import a text file into the sql database."""
with open(path, 'r', encoding='utf-8') as f:
data = {'url': [], 'title': [], 'atime': [], 'redirect': []}
completion_data = {'url': [], 'title': [], 'last_atime': []}
for (i, line) in enumerate(f):
try:
parsed = self._parse_entry(line.strip())
if parsed is None:
continue
url, title, atime, redirect = parsed
data['url'].append(self._format_url(url))
data['title'].append(title)
data['atime'].append(atime)
data['redirect'].append(redirect)
if not redirect:
completion_data['url'].append(
self._format_completion_url(url))
completion_data['title'].append(title)
completion_data['last_atime'].append(atime)
except ValueError as ex:
raise ValueError('Failed to parse line #{} of {}: "{}"'
.format(i, path, ex))
self.insert_batch(data)
self.completion.insert_batch(completion_data, replace=True)
def _write_backup(self, path):
bak = path + '.bak'
message.info('History import complete. Appending {} to {}'
.format(path, bak))
with open(path, 'r', encoding='utf-8') as infile:
with open(bak, 'a', encoding='utf-8') as outfile:
for line in infile:
outfile.write('\n' + line)
os.remove(path)
def _format_url(self, url):
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
def _format_completion_url(self, url):
return url.toString(QUrl.RemovePassword)
@cmdutils.register(instance='web-history', debug=True)
def debug_dump_history(self, dest):
"""Dump the history to a file in the old pre-SQL format.
Args:
dest: Where to write the file to.
"""
dest = os.path.expanduser(dest)
lines = ('{}{} {} {}'
.format(int(x.atime), '-r' * x.redirect, x.url, x.title)
for x in self.select(sort_by='atime', sort_order='asc'))
try:
with open(dest, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines))
message.info("Dumped history to {}".format(dest))
except OSError as e:
raise cmdexc.CommandError('Could not write history: {}'.format(e))
self._add_entry(entry, target=self._temp_history)
def init(parent=None):
@@ -360,9 +299,10 @@ def init(parent=None):
Args:
parent: The parent to use for WebHistory.
"""
history = WebHistory(parent=parent)
history = WebHistory(hist_dir=standarddir.data(), hist_name='history',
parent=parent)
objreg.register('web-history', history)
if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover
if objects.backend == usertypes.Backend.QtWebKit:
from qutebrowser.browser.webkit import webkithistory
webkithistory.init(history)

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