Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
113675a0b5 | ||
|
|
c4d8a767f9 | ||
|
|
de3867fe95 | ||
|
|
6ac3940264 | ||
|
|
5e9eafd5a3 | ||
|
|
5d4b9e815c | ||
|
|
3eba7fc314 | ||
|
|
11d7486f97 | ||
|
|
8e7a1d3d97 | ||
|
|
98704c0471 | ||
|
|
c2bf595b79 | ||
|
|
f73f3a2001 | ||
|
|
afde5bbc79 | ||
|
|
66a76a4504 | ||
|
|
feb73f06c5 | ||
|
|
e32fbe9013 | ||
|
|
776a16bf65 | ||
|
|
225c860452 | ||
|
|
f982402526 | ||
|
|
6a6e7ecb38 | ||
|
|
c94ed93f13 | ||
|
|
95d1721f01 | ||
|
|
410be07f54 | ||
|
|
01de52c23a | ||
|
|
a84807ed05 | ||
|
|
2795ae9478 |
@@ -5,15 +5,13 @@ cache:
|
||||
build: off
|
||||
environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python36\python.exe
|
||||
matrix:
|
||||
- TESTENV: py36-pyqt59
|
||||
- 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=%PATH%;C:\Python36'
|
||||
- C:\Python27\python -u scripts\dev\ci\appveyor_install.py
|
||||
|
||||
test_script:
|
||||
- '%PYTHON% -m tox -e %TESTENV%'
|
||||
- C:\Python34\Scripts\tox -e %TESTENV%
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
coverage:
|
||||
status:
|
||||
project: off
|
||||
patch: off
|
||||
changes: off
|
||||
|
||||
comment: off
|
||||
1
.eslintignore
Normal file
@@ -0,0 +1 @@
|
||||
qutebrowser/3rdparty/pdfjs/*
|
||||
49
.eslintrc
Normal file
@@ -0,0 +1,49 @@
|
||||
# vim: ft=yaml
|
||||
|
||||
env:
|
||||
browser: true
|
||||
|
||||
rules:
|
||||
block-scoped-var: 2
|
||||
dot-location: 2
|
||||
default-case: 2
|
||||
guard-for-in: 2
|
||||
no-div-regex: 2
|
||||
no-param-reassign: 2
|
||||
no-eq-null: 2
|
||||
no-floating-decimal: 2
|
||||
no-self-compare: 2
|
||||
no-throw-literal: 2
|
||||
no-void: 2
|
||||
radix: 2
|
||||
wrap-iife: [2, "inside"]
|
||||
brace-style: [2, "1tbs", {"allowSingleLine": true}]
|
||||
comma-style: [2, "last"]
|
||||
consistent-this: [2, "self"]
|
||||
func-style: [2, "declaration"]
|
||||
indent: [2, 4, {"SwitchCase": 1}]
|
||||
linebreak-style: [2, "unix"]
|
||||
max-nested-callbacks: [2, 3]
|
||||
no-lonely-if: 2
|
||||
no-multiple-empty-lines: [2, {"max": 2}]
|
||||
no-nested-ternary: 2
|
||||
no-unneeded-ternary: 2
|
||||
operator-assignment: [2, "always"]
|
||||
operator-linebreak: [2, "after"]
|
||||
keyword-spacing: 2
|
||||
space-before-blocks: [2, "always"]
|
||||
space-before-function-paren: [2, {"anonymous": "never", "named": "never"}]
|
||||
object-curly-spacing: [2, "never"]
|
||||
array-bracket-spacing: [2, "never"]
|
||||
computed-property-spacing: [2, "never"]
|
||||
space-in-parens: [2, "never"]
|
||||
space-unary-ops: [2, {"words": true, "nonwords": false}]
|
||||
spaced-comment: [2, "always"]
|
||||
max-depth: [2, 5]
|
||||
max-len: [2, 79, 4]
|
||||
max-params: [2, 5]
|
||||
max-statements: [2, 30]
|
||||
no-bitwise: 2
|
||||
quote-props: [2, "always"]
|
||||
global-strict: 0
|
||||
quotes: 0
|
||||
20
.flake8
@@ -6,12 +6,8 @@ exclude = .*,__pycache__,resources.py
|
||||
# E501: Line too long
|
||||
# E402: module level import not at top of file
|
||||
# E266: too many leading '#' for block comment
|
||||
# E722: do not use bare except
|
||||
# E731: do not assign a lambda expression, use a def
|
||||
# (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
|
||||
@@ -25,26 +21,28 @@ exclude = .*,__pycache__,resources.py
|
||||
# D402: First line should not be function's signature (false-positives)
|
||||
# D403: First word of the first line should be properly capitalized
|
||||
# (false-positives)
|
||||
# H101: Use TODO(NAME)
|
||||
# H201: bare except
|
||||
# H238: Use new-stule classes
|
||||
# H301: one import per line
|
||||
# H306: imports not in alphabetical order
|
||||
ignore =
|
||||
E128,E226,E265,E501,E402,E266,E722,E731,
|
||||
E128,E226,E265,E501,E402,E266,
|
||||
F401,
|
||||
N802,
|
||||
P101,P102,P103,
|
||||
D102,D103,D104,D105,D209,D211,D402,D403
|
||||
D102,D103,D104,D105,D209,D211,D402,D403,
|
||||
H101,H201,H238,H301,H306
|
||||
min-version = 3.4.0
|
||||
max-complexity = 12
|
||||
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/conftest.py : +F403
|
||||
tests/unit/browser/test_history.py : +N806
|
||||
tests/helpers/fixtures.py : +N806
|
||||
tests/unit/browser/webkit/http/test_content_disposition.py : +D400
|
||||
scripts/dev/ci/appveyor_install.py : +FI53
|
||||
# FIXME:conf
|
||||
tests/unit/completion/test_models.py : +F821
|
||||
copyright-check = True
|
||||
copyright-regexp = # Copyright [\d-]+ .*
|
||||
copyright-min-file-size = 110
|
||||
|
||||
8
.github/CODEOWNERS
vendored
@@ -1,8 +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
|
||||
9
.github/CONTRIBUTING.asciidoc
vendored
@@ -1,9 +0,0 @@
|
||||
- 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.
|
||||
|
||||
See the full contribution docs for details:
|
||||
|
||||
include::../doc/contributing.asciidoc[]
|
||||
6
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,2 +1,4 @@
|
||||
<!-- If this is a bug report, please remember to mention your version info from
|
||||
`:open qute:version` or `qutebrowser --version` -->
|
||||
Please remember to mention your version info (qutebrowser, Qt, PyQt,
|
||||
OS/distribution) from the `qute:version` page or `qutebrowser --version`
|
||||
|
||||
---
|
||||
|
||||
18
.gitignore
vendored
@@ -15,9 +15,12 @@ __pycache__
|
||||
/qutebrowser/3rdparty
|
||||
/doc/*.html
|
||||
/README.html
|
||||
/CHANGELOG.html
|
||||
/CONTRIBUTING.html
|
||||
/FAQ.html
|
||||
/INSTALL.html
|
||||
/qutebrowser/html/doc/
|
||||
/qutebrowser/html/*.html
|
||||
/.venv*
|
||||
/.venv
|
||||
/.coverage
|
||||
/htmlcov
|
||||
/coverage.xml
|
||||
@@ -30,12 +33,7 @@ __pycache__
|
||||
/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/Makefile
|
||||
/scripts/testbrowser_cpp/main.o
|
||||
/scripts/testbrowser_cpp/testbrowser
|
||||
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
||||
|
||||
30
.pylintrc
@@ -31,23 +31,20 @@ disable=no-self-use,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used,
|
||||
file-ignored,
|
||||
wrong-import-order,
|
||||
ungrouped-imports,
|
||||
redefined-variable-type,
|
||||
suppressed-message,
|
||||
too-many-return-statements,
|
||||
duplicate-code,
|
||||
wrong-import-position,
|
||||
no-else-return,
|
||||
# https://github.com/PyCQA/pylint/issues/1698
|
||||
unsupported-membership-test,
|
||||
unsupported-assignment-operation,
|
||||
unsubscriptable-object
|
||||
wrong-import-position
|
||||
|
||||
[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
|
||||
@@ -55,9 +52,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=_.*
|
||||
|
||||
@@ -68,11 +68,9 @@ max-args=10
|
||||
valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-modules=PyQt5,PyQt5.QtWebKit
|
||||
ignored-classes=_CountingAttr
|
||||
|
||||
[IMPORTS]
|
||||
# WORKAROUND
|
||||
# For some reason, pylint doesn't know about some Python 3 modules on
|
||||
# AppVeyor...
|
||||
known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types
|
||||
# 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
|
||||
|
||||
53
.travis.yml
@@ -1,44 +1,28 @@
|
||||
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
|
||||
- os: linux
|
||||
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
|
||||
env: DOCKER=ubuntu-xenial
|
||||
services: docker
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt571
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt58
|
||||
- os: linux
|
||||
python: 3.5
|
||||
env: TESTENV=py35-pyqt59
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt59-cov
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=sierra
|
||||
osx_image: xcode8.3
|
||||
language: generic
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||
# - os: osx
|
||||
# env: TESTENV=py35 OSX=yosemite
|
||||
# osx_image: xcode6.4
|
||||
env: TESTENV=py35
|
||||
- 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
|
||||
@@ -49,19 +33,21 @@ matrix:
|
||||
env: TESTENV=check-manifest
|
||||
- os: linux
|
||||
env: TESTENV=eslint
|
||||
language: node_js
|
||||
python: null
|
||||
node_js: "lts/*"
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- os: osx
|
||||
env: TESTENV=py35
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/build/qutebrowser/qutebrowser/.cache
|
||||
- $HOME/build/The-Compiler/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
|
||||
@@ -69,9 +55,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
|
||||
|
||||
@@ -4,8 +4,7 @@ Change Log
|
||||
// http://keepachangelog.com/
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to http://semver.org/[Semantic Versioning], though minor
|
||||
breaking changes (such as renamed commands) can happen in minor releases.
|
||||
This project adheres to http://semver.org/[Semantic Versioning].
|
||||
|
||||
// tags:
|
||||
// `Added` for new features.
|
||||
@@ -15,599 +14,6 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.0.3
|
||||
------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- macOS and Windows builds are now built with PyQt 5.9.1 and Qt 5.9.2, including
|
||||
various bugfixes, as well as security fixes from Chromium up to version
|
||||
61.0.3163.79.
|
||||
- Performance improvements for tab rendering.
|
||||
- The :open-editor command is now not hidden anymore as it's also usable in
|
||||
normal mode.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Handle accessing a locked sqlite database gracefully
|
||||
- Abort pinned tab dialogs properly when a tab is closed e.g. by closing a
|
||||
window
|
||||
- Unbinding a default keybinding twice now doesn't bind it again
|
||||
- Completions are now sorted correctly again when filtered
|
||||
|
||||
v1.0.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fix workaround for black screens or crashes with Nvidia cards
|
||||
- Handle a filesystem going read-only gracefully
|
||||
- Fix crash when setting `fonts.monospace`
|
||||
- Fix list options not being modifyable via `.append()` in `config.py`
|
||||
- Mark the content.notifications setting as QtWebKit only correctly
|
||||
- Fix wrong rendering of keys like `<back>` in the completion
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Nicer error messages and other minor improvements
|
||||
|
||||
v1.0.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed starting after customizing `fonts.tabs` or `fonts.debug_console`.
|
||||
- Fixed starting with old PyQt versions compiled against newer Qt versions.
|
||||
- Fixed check for PyQt version to correctly enforce 5.7 (not 5.2).
|
||||
|
||||
v1.0.0
|
||||
------
|
||||
|
||||
Major changes
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
- Dependency changes:
|
||||
* Support for legacy QtWebKit (before 5.212 which is
|
||||
https://github.com/annulen/webkit/wiki[distributed independently from Qt])
|
||||
is dropped.
|
||||
* Support for Python 3.4 is dropped.
|
||||
* Support for Qt before 5.7.1 and PyQt before 5.7 is dropped.
|
||||
* New dependency on the QtSql module and Qt sqlite support.
|
||||
* New dependency on the http://www.attrs.org/[attrs] project (packaged as
|
||||
`python-attr` in some distributions).
|
||||
* The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
|
||||
that PyQt5.QtOpenGL is still a dependency.
|
||||
* PyQt5.QtOpenGL is now always required, even with QtWebKit.
|
||||
- The QtWebEngine backend is now used by default. Note this means that
|
||||
QtWebEngine now should be a required dependency, and QtWebKit (if new enough)
|
||||
should be changed to an optional dependency.
|
||||
- Completely rewritten configuration system which ignores the old config file.
|
||||
See link:qute://help/configuring.html[] for details.
|
||||
- Various documentation files got moved to the doc/ subfolder;
|
||||
`qutebrowser.desktop` got moved to misc/.
|
||||
- `:set` now doesn't support toggling/cycling values anymore, that functionality
|
||||
got moved to `:config-cycle`.
|
||||
- New completion engine based on sqlite, which allows to complete
|
||||
the entire browsing history. The default for
|
||||
`completion.web_history_max_items` got changed to `-1` (unlimited). If the
|
||||
completion is too slow on your machine, try setting it to a few 1000 items.
|
||||
- Up/Down now navigates through the command history instead of selecting
|
||||
completion items. Either use Tab to cycle through the completion, or
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc#migrating-older-configurations[restore the old behavior].
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine: Spell checking support, see the `spellcheck.languages` setting.
|
||||
- New `qt.args` setting to pass additional arguments to Qt/Chromium.
|
||||
- New `backend` setting to select the backend to use.
|
||||
Together with the previous setting, this should make most wrapper scripts
|
||||
unnecessary.
|
||||
- qutebrowser can now be set as the default browser on macOS.
|
||||
- New config commands:
|
||||
* `:config-cycle` to cycle an option between multiple values.
|
||||
* `:config-unset` to remove a configured option.
|
||||
* `:config-clear` to remove all configured options.
|
||||
* `:config-source` to (re-)read a `config.py` file.
|
||||
* `:config-edit` to open the `config.py` file in an editor.
|
||||
* `:config-write-py` to write a `config.py` template file.
|
||||
- New `:version` command which opens `qute://version`.
|
||||
- New back/forward indicator in the statusbar.
|
||||
- New `bindings.key_mappings` setting to map keys to other keys.
|
||||
- QtWebEngine: Support for proxy authentication.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Using `:download` now uses the page's title as filename.
|
||||
- Using `:back` or `:forward` with a count now skips intermediate pages.
|
||||
- When there are multiple messages shown, the timeout is increased.
|
||||
- `:search` now only clears the search if one was displayed before, so pressing
|
||||
`<Escape>` doesn't un-focus inputs anymore.
|
||||
- Pinned tabs now adjust to their text's width, so the `tabs.width.pinned`
|
||||
setting got removed.
|
||||
- `:set-cmd-text` now has a `--run-on-count` argument to run the underlying
|
||||
command directly if a count was given.
|
||||
- `:scroll-perc` got renamed to `:scroll-to-perc`.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- Migrating QtWebEngine data written by versions before 2016-11-15 (before
|
||||
v0.9.0) is now not supported anymore.
|
||||
- Upgrading qutebrowser with a version older than v0.4.0 still running now won't
|
||||
work properly anymore.
|
||||
- The `--harfbuzz` and `--relaxed-config` commandline arguments got dropped.
|
||||
|
||||
Fixes
|
||||
~~~~~
|
||||
|
||||
- Exiting fullscreen via `:fullscreen` or buttons on a page now
|
||||
restores the correct previous window state (maximized/fullscreen).
|
||||
- When `input.insert_mode.auto_load` is set, background tabs now don't enter
|
||||
insert mode anymore.
|
||||
- The keybinding help widget now works correctly when using keybindings with a
|
||||
count.
|
||||
- The `window.hide_wayland_decoration` setting now works correctly again.
|
||||
|
||||
v0.11.1
|
||||
-------
|
||||
|
||||
Fixes
|
||||
~~~~~
|
||||
|
||||
- Fixed empty space being shown after tabs in the tabbar in some cases.
|
||||
- Fixed `:restart` in private browsing mode.
|
||||
- Fixed printing on macOS.
|
||||
- Closing a pinned tab via mouse now also prompts for confirmation.
|
||||
- The "try again" button on error pages works correctly again.
|
||||
- :spawn -u -d is now disallowed.
|
||||
- :spawn -d shows error messages correctly now.
|
||||
|
||||
v0.11.0
|
||||
-------
|
||||
|
||||
New dependencies
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- New dependency on `PyQt5.QtOpenGL` if QtWebEngine is used. QtWebEngine depends
|
||||
on QtOpenGL already, but on distributions packaging split PyQt5 wrappers, the
|
||||
wrappers for QtOpenGL are now required.
|
||||
- New dependency on `PyOpenGL` if QtWebEngine is used.
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Private browsing is now implemented for QtWebEngine, *and changed its
|
||||
behavior*: The `general -> private-browsing` setting now only applies to newly
|
||||
opened windows, and you can use the `-p` flag to `:open` to open a private
|
||||
window.
|
||||
- New "pinned tabs" feature, with a new `:tab-pin` command (bound
|
||||
to `<Ctrl-p>` by default).
|
||||
- (QtWebEngine) Implemented `:follow-selected`.
|
||||
- New `:clear-messages` command to clear shown messages.
|
||||
- New `ui -> keyhint-delay` setting to configure the delay until
|
||||
the keyhint overlay pops up.
|
||||
- New `-s` option for `:open` to force a HTTPS scheme.
|
||||
- `:debug-log-filter` now accepts `none` as an argument to clear any log
|
||||
filters.
|
||||
- New `--debug-flag` argument which replaces `--debug-exit` and
|
||||
`--pdb-postmortem`.
|
||||
- New `tabs -> favicon-scale` option to scale up/down favicons.
|
||||
- `colors -> statusbar.bg/fg.private` and `.command.private` to
|
||||
customize statusbar colors for private windows.
|
||||
- New `{private}` field displaying `[Private Mode]` for
|
||||
`ui -> window-title-format` and `tabs -> title-format`.
|
||||
- (QtWebEngine) Proxy support with Qt 5.7.1 (already was supported for 5.8 and
|
||||
newer)
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- To prevent elaborate phishing attacks, the Punycode version (`xn--*`) is now
|
||||
shown in addition to the decoded version for international domain names
|
||||
(IDN).
|
||||
- Starting with legacy QtWebKit now shows a warning message.
|
||||
*With the next release, support for it will be removed.*
|
||||
- The Windows releases are redone from scratch, which means:
|
||||
* They now use the new QtWebEngine backend
|
||||
* The bundled Qt is updated from 5.5 to 5.9
|
||||
* The bundled Python is updated from 3.4 to 3.6
|
||||
* They are now generated with PyInstaller instead of cx_Freeze
|
||||
* The installer is now generated using NSIS instead of being a MSI
|
||||
- Improved `qute://history` page (with lazy loading)
|
||||
- Crash reports are not public anymore.
|
||||
- Paths like `C:` are now treated as absolute paths on Windows for downloads,
|
||||
and invalid paths are handled properly.
|
||||
- Comments in the config file are now placed before the individual options
|
||||
instead of being before sections.
|
||||
- Messages are now hidden when clicked.
|
||||
- stdin is now closed immediately for processes spawned from qutebrowser.
|
||||
- When `ui -> message-timeout` is set to 0, messages are now never cleared.
|
||||
- Middle/right-clicking the blank parts of the tab bar (when vertical) now
|
||||
closes the current tab.
|
||||
- The adblocker now also blocks non-GET requests (e.g. POST).
|
||||
- `javascript:` links can now be hinted.
|
||||
- `:view-source`, `:tab-clone` and `:navigate --tab` now don't open the tab as
|
||||
"explicit" anymore, i.e. (with the default settings) open it next to the
|
||||
active tab.
|
||||
- `qute:*` pages now use `qute://*` instead (e.g. `qute://version` instead of
|
||||
`qute:version`), but the old versions are automatically redirected.
|
||||
- Texts in prompts are now selectable.
|
||||
- The default level for `:messages` is now `info`, not `error`
|
||||
- Trying to focus the currently focused tab with `:tab-focus` now focuses the
|
||||
last viewed tab.
|
||||
- (QtWebEngine) With Qt 5.9, `content -> cookies-store` can now be set without
|
||||
a restart.
|
||||
- (QtWebEngine) With Qt 5.9, better error messages are now shown for failed
|
||||
downloads.
|
||||
- (QtWebEngine) The underlying Chromium version is now shown in the version
|
||||
info.
|
||||
- (QtWebKit) Renderer process crashes now show an error page on Qt 5.9 or newer.
|
||||
- (QtWebKit) storage -> offline-web-application-storage` got renamed to `...-cache`
|
||||
- (QtWebKit) PAC now supports SOCKS5 as type.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- The macOS .dmg is now built against Qt 5.9 which fixes various
|
||||
important issues (such as not being able to type dead keys).
|
||||
- Fixed crash with `:download` on PyQt 5.9.
|
||||
- Cloning a page without history doesn't crash anymore.
|
||||
- When a download results in a HTTP error, it now shows the error correctly
|
||||
instead of crashing.
|
||||
- Pressing ctrl-c while a config error is shown works as intended now.
|
||||
- When the key config isn't writable, we now show an error instead of crashing.
|
||||
- Fixed crash when unbinding an unbound key in the key config.
|
||||
- Fixed crash when using `:debug-log-filter` when `--filter` wasn't given on startup.
|
||||
- Fixed crash with some invalid setting values.
|
||||
- Continuing a search after clearing it now works correctly.
|
||||
- The tabbar and completion should now be more consistently and correctly
|
||||
styled with various system styles.
|
||||
- Applying styiles in `qt5ct` now shouldn't crash anymore.
|
||||
- The validation for colors in stylesheets is now less strict,
|
||||
allowing for all valid Qt values.
|
||||
- `data:` URLs now aren't added to the history anymore.
|
||||
- Accidentally starting with Python 2 now shows a proper error message again.
|
||||
- For some people, running some userscripts crashed - this should now be fixed.
|
||||
- Various other rare crashes should now be fixed.
|
||||
- The settings documentation was truncated with v0.10.1 which should now be
|
||||
fixed.
|
||||
- Scrolling to an anchor in a background tab now works correctly, and javascript
|
||||
gets the correct window size for background tabs.
|
||||
- (QtWebEngine) Added a workaround for a black screen with some setups
|
||||
- (QtWebEngine) Starting with Nouveau graphics now shows an error message
|
||||
instead of crashing in Qt.
|
||||
- (QtWebEngine) Retrying downloads now shows an error instead of crashing.
|
||||
- (QtWebEngine) Cloning a view-source tab now doesn't crash anymore.
|
||||
- (QtWebEngine) `window.navigator.userAgent` is now set correctly when
|
||||
customizing the user agent.
|
||||
- (QtWebEngine) HTML fullscreen is now tracked for each tab separately, which
|
||||
means it's not possible anymore to accidentally get stuck in fullscreen state
|
||||
by closing a tab with a fullscreen video.
|
||||
- (QtWebEngine) `:scroll-page` with `--bottom-navigate` now works correctly.
|
||||
- (QtWebKit) The HTTP cache is disabled on Qt 5.7.1 and 5.8 now as it leads to
|
||||
frequent crashes due to a Qt bug.
|
||||
- (QtWebKit) Fixed Crash when a PAC file returns an invalid value.
|
||||
|
||||
v0.10.1
|
||||
-------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- `--qt-arg` and `--qt-flag` can now also be used to pass arguments to Chromium when using QtWebEngine.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- URLs are now redacted properly (username/password, and path/query for HTTPS) when using Proxy Autoconfig with QtWebKit
|
||||
- Crash when updating adblock lists with invalid UTF8-chars in them
|
||||
- Fixed the web inspector with QtWebEngine
|
||||
- Version checks when starting qutebrowser now also take the Qt version PyQt was compiled against into account
|
||||
- Hinting a input now doesn't select existing text anymore with QtWebKit
|
||||
- The cursor now moves to the end when input elements are selected with QtWebEngine
|
||||
- Download suffixes like (1) are now correctly stripped with QtWebEngine
|
||||
- Crash when trying to print a tab which was closed in the meantime
|
||||
- Crash when trying to open a file twice on Windows
|
||||
|
||||
v0.10.0
|
||||
-------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Userscripts now have a new `$QUTE_COMMANDLINE_TEXT` environment variable, containing the current commandline contents
|
||||
- New `ripbang` userscript to create a searchengine from a duckduckgo bang
|
||||
- link:https://github.com/annulen/webkit/wiki[QtWebKit Reloaded] (also called QtWebKit-NG) is now fully supported
|
||||
- Various new functionality with the QtWebEngine backend:
|
||||
* Printing support with Qt >= 5.8
|
||||
* Proxy support with Qt >= 5.8
|
||||
* The `general -> print-element-backgrounds` option with Qt >= 5.8
|
||||
* The `content -> cookies-store` option
|
||||
* The `storage -> cache-size` option
|
||||
* The `colors -> webpage.bg` option
|
||||
* The HTML5 fullscreen API (e.g. youtube videos) with QtWebEngine
|
||||
* `:download --mhtml`
|
||||
- New `qute:history` URL and `:history` command to show the browsing history
|
||||
- Open tabs are now auto-saved on each successful load and restored in case of a crash
|
||||
- `:jseval` now has a `--file` flag so you can pass a javascript file
|
||||
- `:session-save` now has a `--only-active-window` flag to only save the active window
|
||||
- macOS builds are back, and built with QtWebEngine
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- PyQt 5.7/Qt 5.7.1 is now required for the QtWebEngine backend
|
||||
- Scrolling with the scrollwheel while holding shift now scrolls sideways
|
||||
- New way of clicking hints which solves various small issues
|
||||
- When yanking a mailto: link via hints, the mailto: prefix is now stripped
|
||||
- Zoom level messages are now not stacked on top of each other anymore
|
||||
- qutebrowser now automatically uses QtWebEngine if QtWebKit is unavailable
|
||||
- :history-clear now asks for a confirmation, unless it's run with --force.
|
||||
- `input -> mouse-zoom-divider` can now be 0 to disable zooming by mouse wheel
|
||||
- `network -> proxy` can also be set to `pac+file://...` now to
|
||||
use a local proxy autoconfig file (on QtWebKit)
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- (QtWebKit) Various rarely customized settings were removed:
|
||||
* `ui -> css-media-type` (defaults to desktop)
|
||||
* `general -> site-specific-quirks` (now always turned on)
|
||||
* `storage -> offline-storage-default-quota` (defaults to 5MB)
|
||||
* `storage -> offline-web-application-cache-quota` (defaults to no quota)
|
||||
* `storage -> object-cache-capacities` (default depends on disk space)
|
||||
* `content -> css-regions` (now always turned off)
|
||||
* `storage -> offline-storage-database` (merged into `storage -> local-storage`)
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Various bugs with Qt 5.8 and QtWebEngine:
|
||||
* Segfault when closing a window
|
||||
* Segfault when closing a tab with a search active
|
||||
* Fixed various mouse actions (like automatically entering insert mode) not working
|
||||
* Fixed hints sometimes not working
|
||||
* Segfault when opening a URL after a QtWebEngine renderer process crash
|
||||
- Other QtWebEngine fixes:
|
||||
* Insert mode now gets entered correctly with a non-100% zoom
|
||||
* Crash reports are now re-enabled when using QtWebEngine
|
||||
* Fixed crashes when closing tabs while hinting
|
||||
* Using :undo or :tab-clone with a view-source:// or chrome:// tab is now prevented, as it segfaults
|
||||
- `:enter-mode` now refuses to enter modes which can't be entered manually (which caused crashes)
|
||||
- `:record-macro` (`q`) now doesn't try to record macros for special keys without a text
|
||||
- Fixed PAC (proxy autoconfig) not working with QtWebKit
|
||||
- `:download --mhtml` now uses the new file dialog
|
||||
- Word hints are now upper-cased correctly when hints -> uppercase is true
|
||||
- Font validation is now more permissive in the config, allowing e.g. "Terminus
|
||||
(TTF)" as font name
|
||||
- Fixed starting on newer PyQt/sip versions with LibreSSL
|
||||
- When downloading files with QtWebKit, a User-Agent header is set when possible
|
||||
- Fixed showing of keybindings in the :help completion
|
||||
- `:navigate prev/next` now detects `rel` attributes on `<a>` elements, and
|
||||
handles multiple `rel` attributes correctly
|
||||
- Fixed a crash when hinting with target `userscript` and spawning a non-existing script
|
||||
- Lines in Jupyter notebook now trigger insert mode
|
||||
|
||||
v0.9.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Prevent websites from downloading files to a location outside of the download
|
||||
folder with QtWebEngine.
|
||||
|
||||
v0.9.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- *New dependency:* qutebrowser now depends on the Qt QML module, which is
|
||||
packaged separately in some distributions (as Qt Declarative/QML/Quick).
|
||||
- New `:rl-backward-kill-word` command which does what `:rl-unix-word-rubout`
|
||||
did before v0.8.0.
|
||||
- New `:rl-unix-filename-rubout` command which is similar to readline's
|
||||
`unix-filename-rubout`.
|
||||
- New `fonts -> completion.category` setting to customize the font used for
|
||||
completion category headers.
|
||||
- New `:debug-log-capacity` command to adjust how many lines are logged into RAM
|
||||
(to report bugs which are difficult to reproduce).
|
||||
- New `hide-unmatched-rapid-hints` option to not hide hint unmatched hint labels
|
||||
in rapid mode.
|
||||
- New `{clipboard}` and `{primary}` replacements for the commandline which
|
||||
replace the `:paste` command.
|
||||
- New `:insert-text` command to insert a given text into a field on the page,
|
||||
which replaces `:paste-primary` together with the `{primary}` replacement.
|
||||
- New `:window-only` command to close all other windows.
|
||||
- New `prev-category` and `next-category` arguments to `:completion-item-focus`
|
||||
to focus the previous/next category in the completion (bound to `<Ctrl-Tab>`
|
||||
and `<Ctrl-Shift-Tab>` by default).
|
||||
- New `:click-element` command to fake a click on a element.
|
||||
- New `:debug-log-filter` command to change console log filtering on-the-fly.
|
||||
- New `:debug-log-level` command to change the console loglevel on-the-fly.
|
||||
- New `general -> yank-ignored-url-parameters` option to configure which URL
|
||||
parameters (like `utm_source` etc.) to strip off when yanking an URL.
|
||||
- Support for the
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API[HTML5 page visibility API]
|
||||
- New `readability` userscript which shows a readable version of a page (using
|
||||
the `readability-lxml` python package)
|
||||
- New `cast` userscript to show a video on a Google Chromecast
|
||||
- New `:run-with-count` command which replaces the (undocumented) `:count:command` syntax.
|
||||
- New `:record-macro` (`q`) and `:run-macro` (`@`) commands for keyboard macros.
|
||||
- New `ui -> hide-scrollbar` setting to hide the scrollbar independently of the
|
||||
`user-stylesheet` setting.
|
||||
- New `general -> default-open-dispatcher` setting to configure what to open
|
||||
downloads with (instead of e.g. `xdg-open` on Linux).
|
||||
- Support for PAC (proxy autoconfig) with QtWebKit
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Hints are now drawn natively in Qt instead of using web elements. This has a
|
||||
few implications for users:
|
||||
* The `hints -> opacity` setting does not exist anymore, but you can use
|
||||
`rgba(r, g, b, alpha)` colors instead for `colors -> hints.bg`.
|
||||
* The `hints -> font` setting is not affected by
|
||||
`fonts -> web-family-fixed` anymore. Thus, a transformer got added to
|
||||
change `Monospace` to `${_monospace}`.
|
||||
* Gradients in hint colors can now be configured by using `qlineargradient`
|
||||
and friends instead of `-webkit-gradient`. The most common cases get
|
||||
migrated automatically, but if you drastically changed the defaults,
|
||||
you'll need to manually adjust your config.
|
||||
* Styling hints by styling `qutehint` elements in `user-stylesheet` was
|
||||
never officially supported and does not work anymore.
|
||||
* Hints are now not affected by the page's stylesheet or zoom anymore.
|
||||
- `:bookmark-add` now has a `--toggle` flag which deletes the bookmark if it
|
||||
already exists.
|
||||
- `:bookmark-load` now has a `--delete` flag which deletes the bookmark after
|
||||
loading it.
|
||||
- `:open` now also accepts quickmark names instead of URLs
|
||||
- `:tab-move` now optionally takes an index for absolute moving.
|
||||
- Commands taking either an argument or a count (like `:zoom` or `:tab-focus`)
|
||||
now prefer the count instead of showing an error message.
|
||||
- `:open` now has an `--implicit` argument to treat the opened tab as implicit
|
||||
(i.e. to open it at the position it would be opened if it was a clicked link)
|
||||
- `:download-open` and `:prompt-open-download` now have an optional `cmdline`
|
||||
argument to pass a commandline to open the download with.
|
||||
- `:yank` now has a position argument to select what to yank instead of using
|
||||
flags.
|
||||
- Replacements like `{url}` can now also be used in the middle of an argument.
|
||||
Consequently, commands taking another command (`:later`, `:repeat` and
|
||||
`:bind`) now don't immediately evaluate variables.
|
||||
- Tab titles in the `:buffer` completion now update correctly when a page's
|
||||
title is changed via javascript.
|
||||
- `:hint` now has a `--mode <mode>` flag to override the hint mode configured
|
||||
using the `hints -> mode` setting.
|
||||
- With `new-instance-open-target` set to a tab option, the tab is now opened in
|
||||
the most recently focused (instead of the last opened) window. This can be
|
||||
configured with the new `new-instance-open-target.window` setting.
|
||||
It can also be set to `last-visible` to show the pages in the most recently
|
||||
visible window, or `first-opened` to use the first (oldest) available window.
|
||||
- Word hints now are more clever about getting the element text from some elements.
|
||||
- Completions for `:help` and `:bind` now also show hidden commands
|
||||
- The `:buffer` completion now also filters using the first column (id).
|
||||
- `:undo` has been improved to reopen tabs at the position they were closed.
|
||||
- `:navigate` now takes a count for `up`/`increment`/`decrement`.
|
||||
- The `hints -> auto-follow` setting now can be set to
|
||||
`always`/`full-match`/`unique-match`/`never` to more precisely control when
|
||||
hints should be followed automatically.
|
||||
- Counts can now be used with special keybindings (e.g. with modifiers).
|
||||
This was already implemented for v0.7.0 originally, but got reverted because
|
||||
it caused some issues and then never re-applied.
|
||||
- Sending a command to an existing instance (via "qutebrowser :reload") now
|
||||
doesn't mark it as urgent anymore.
|
||||
- `tabs -> title-format` now treats an empty string as valid.
|
||||
- Bindings for `:`, `/` and `?` are now configured explicitly and not hardcoded
|
||||
anymore.
|
||||
- The `completion -> show` setting can now be set to `always`, `auto` or
|
||||
`never`.
|
||||
- `:open-editor` can now be used in any mode.
|
||||
- Lots of improvements to and bugfixes for the QtWebEngine backend, such as
|
||||
working hints. However, using qutebrowser directly from git is still advised
|
||||
when using `--backend webengine`.
|
||||
- `content -> javascript-can-open-windows` got renamed to
|
||||
`javascript-can-open-windows-automatically`.
|
||||
- `:prompt-accept` now optionally accepts a value which overrides the one
|
||||
entered in the input box. `yes` and `no` can be used as values for yes/no
|
||||
questions.
|
||||
- The new `--qt-arg` and `--qt-flag` arguments can be used to pass
|
||||
arguments/flags to Qt's commandline.
|
||||
- Error/warning/info messages are now shown stacked above the statusbar.
|
||||
This also added various new settings:
|
||||
* `colors -> messages.fg.error` (renamed from `statusbar.fg.error`)
|
||||
* `colors -> messages.bg.error` (renamed from `statusbar.bg.error`)
|
||||
* `colors -> messages.border.error`
|
||||
* `colors -> messages.fg.warning` (renamed from `statusbar.fg.warning`)
|
||||
* `colors -> messages.bg.warning` (renamed from `statusbar.bg.warning`)
|
||||
* `colors -> messages.border.warning`
|
||||
* `colors -> messages.fg.info`
|
||||
* `colors -> messages.bg.info`
|
||||
* `colors -> messages.border.info`
|
||||
* `fonts -> messages.error`
|
||||
* `fonts -> messages.warning`
|
||||
* `fonts -> messages.info`
|
||||
- The `qute:settings` page now also shows option descriptions.
|
||||
- `qute:version` and `qutebrowser --version` now show various important paths
|
||||
- `:spawn`/userscripts now show a nicer error when a script wasn't found
|
||||
- Various functionality now works when javascript is disabled with QtWebKit
|
||||
- Various commands/settings taking `left`/`right`/`previous` arguments now take
|
||||
`prev`/`next`/`last-used` to remove ambiguity.
|
||||
- The `ui -> user-stylesheet` setting now only takes filenames, not CSS snippets
|
||||
- `ui -> window-title-format` now has a new `{backend} ` replacement
|
||||
- `:hint` has a new `--add-history` argument to add the URL to the history for
|
||||
yank/spawn targets.
|
||||
- `:set` now cycles through values if more than one argument is given.
|
||||
- `:open` now opens `default-page` without an URL even without `-t`/`-b`/`-w` given.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- The `:paste` command got deprecated as `:open` with `{clipboard}` and
|
||||
`{primary}` can be used instead.
|
||||
- The `:paste-primary` command got deprecated as `:insert-text {primary}` can
|
||||
be used instead.
|
||||
- The `:prompt-yes` and `:prompt-no` commands got deprecated as
|
||||
`:prompt-accept yes` and `:prompt-accept no` can be used instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The `:yank-selected` command got merged into `:yank` as `:yank selection`
|
||||
and thus removed.
|
||||
- The `:completion-item-prev` and `:completion-item-next` commands got merged
|
||||
into a new `:completion-focus {prev,next}` command and thus removed.
|
||||
- The `ui -> hide-mouse-cursor` setting since it was completely broken and
|
||||
nobody seemed to care.
|
||||
- The `hints -> opacity` setting - see the "Changed" section for details.
|
||||
- The `completion -> auto-open` setting got merged into `completion -> show` and
|
||||
thus removed.
|
||||
- All `--qt-*` arguments got replaced by `--qt-arg` and `--qt-flag` and thus
|
||||
removed.
|
||||
- The `-c`/`--confdir`, `--datadir` and `--cachedir` arguments got removed, as
|
||||
`--basedir` should be sufficient.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- `:undo` now doesn't undo tabs "closed" by `:tab-detach` anymore.
|
||||
- Fixed an issue with hint chars not being cleared correctly when leaving hint
|
||||
mode.
|
||||
- `:tab-detach` now fails correctly when there's only one tab open.
|
||||
- Various small issues with the command completion
|
||||
- Fixed hang when using multiple spaces in a row with the URL completion
|
||||
- qutebrowser now still starts with an incorrectly configured
|
||||
`$XDG_RUNTIME_DIR`.
|
||||
- Fixed crash when a userscript writes invalid unicode data to the FIFO
|
||||
- Fixed crash when a included HTML was not found
|
||||
|
||||
v0.8.3
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed crash when doing `:<space><enter>`, another corner-case introduced in v0.8.0
|
||||
- Fixed `:open-editor` (`<Ctrl-e>`) on Windows
|
||||
- Fixed crash when setting `general -> auto-save-interval` to a too big value.
|
||||
- Fixed crash when using hints on Void Linux.
|
||||
- Fixed compatibility with Python 3.5.2+ on Debian unstable
|
||||
- Compatibility with pdfjs v1.6.210
|
||||
- `:bind` can now be used to bind to an alias (binding by editing `keys.conf`
|
||||
already worked before)
|
||||
- The command completion now updates correctly when changing aliases
|
||||
- The tabbar now displays correctly with the Adwaita Qt theme
|
||||
- The default `sk` keybinding now sets the commandline to `:bind` correctly
|
||||
- Fixed crash when closing a window without focusing it
|
||||
- Userscripts now can access QUTE_FIFO correctly on Windows
|
||||
|
||||
v0.8.2
|
||||
------
|
||||
|
||||
@@ -616,6 +22,7 @@ Fixed
|
||||
|
||||
- Fixed `general -> private-browsing` not being set correctly until a restart
|
||||
(which caused e.g. local storage to be enabled).
|
||||
- Fixed crash when using hints with JS disabled in some rare circumstances.
|
||||
- When hinting input fields (`:t`), also consider input elements without a type.
|
||||
- Fixed crash when opening an invalid URL with a percent-encoded and a real @ in it
|
||||
- Fixed default `;o` and `;O` bindings
|
||||
@@ -625,11 +32,11 @@ Fixed
|
||||
- Fixed HTML5 video playback on Windows
|
||||
- Fixed crash when using `:prompt-open-download` with a file with chars not
|
||||
encodable with the OS' filesystem encoding (e.g. with `LC_ALL=C`)
|
||||
- Fixed `:prompt-open-download` with a too long filename (> 255 bytes)
|
||||
- Fixed `:prompt-open-download` with a too long filename (< 255 bytes)
|
||||
- Fixed crash when cancelling a download after doing `:prompt-open-download`
|
||||
- Fixed crash when writing a download to disk fails with
|
||||
`:prompt-open-download`.
|
||||
- Fixed `:restart` deleting the basedir when it was given with `--basedir`.
|
||||
- Fixed HTML5 video playback on Windows
|
||||
|
||||
v0.8.1
|
||||
------
|
||||
@@ -640,7 +47,7 @@ Fixed
|
||||
- Fix crash when pressing enter without a command
|
||||
- Adjust error message to point out QtWebEngine is unsupported with the OS
|
||||
X .app currently.
|
||||
- Hide Harfbuzz warning with the macOS .app
|
||||
- Hide Harfbuzz warning with the OS X .app
|
||||
|
||||
v0.8.0
|
||||
------
|
||||
@@ -736,6 +143,7 @@ Changed
|
||||
- `:navigate` now clears the URL fragment
|
||||
- `:completion-item-del` (`Ctrl-D`) can now be used in `:buffer` completion to
|
||||
close a tab
|
||||
- Counts can now be used with special keybindings (e.g. with modifiers)
|
||||
- Various SSL ciphers are now disabled by default. With recent Qt/OpenSSL
|
||||
versions those already all are disabled, but with older versions they might
|
||||
not be.
|
||||
@@ -1003,7 +411,7 @@ Fixed
|
||||
- Fixed scrolling to the very left/right with `:scroll-perc`.
|
||||
- Using an external editor should now work correctly with some funny chars
|
||||
(U+2028/U+2029/BOM).
|
||||
- Movements in caret mode now should work correctly on macOS and Windows.
|
||||
- Movements in caret mode now should work correctly on OS X and Windows.
|
||||
- Fixed upgrade from earlier config versions.
|
||||
- Fixed crash when killing a running userscript.
|
||||
- Fixed characters being passed through when shifted with
|
||||
@@ -1078,7 +486,7 @@ Changed
|
||||
- The completion widget doesn't show a border anymore.
|
||||
- The tabbar doesn't display ugly arrows anymore if there isn't enough space
|
||||
for all tabs.
|
||||
- Some insignificant Qt warnings which were printed on macOS are now hidden.
|
||||
- Some insignificant Qt warnings which were printed on OS X are now hidden.
|
||||
- Better support for Qt 5.5 and Python 3.5.
|
||||
|
||||
Fixed
|
||||
@@ -1189,13 +597,13 @@ Fixed
|
||||
- Fixed AssertionError when closing many windows quickly.
|
||||
- Various fixes for deprecated key bindings and auto-migrations.
|
||||
- Workaround for qutebrowser not starting when there are NUL-bytes in the history (because of a currently unknown bug).
|
||||
- Fixed handling of keybindings containing Ctrl/Meta on macOS.
|
||||
- Fixed handling of keybindings containing Ctrl/Meta on OS X.
|
||||
- Fixed crash when downloading a URL without filename (e.g. magnet links) via "Save as...".
|
||||
- Fixed exception when starting qutebrowser with `:set` as argument.
|
||||
- Fixed horrible completion performance when the `shrink` option was set.
|
||||
- Sessions now store zoom/scroll-position correctly.
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/releases/tag/v0.2.1[v0.2.1]
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Fixed
|
||||
@@ -1203,7 +611,7 @@ Fixed
|
||||
|
||||
- Added missing manpage (doc/qutebrowser.1.asciidoc) to archive.
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/releases/tag/v0.2.0[v0.2.0]
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.0[v0.2.0]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Added
|
||||
@@ -1287,7 +695,7 @@ Changed
|
||||
- Add a `:search` command in addition to `/foo` so it's more visible and can be used from scripts.
|
||||
- Various improvements to documentation, logging, and the crash reporter.
|
||||
- Expand `~` to the users home directory with `:run-userscript`.
|
||||
- Improve the userscript runner on Linux/macOS by using `QSocketNotifier`.
|
||||
- Improve the userscript runner on Linux/OS X by using `QSocketNotifier`.
|
||||
- Add luakit-like `gt`/`gT` keybindings to cycle through tabs.
|
||||
- Show default value for config values in the completion.
|
||||
- Clone tab icon, tab text and zoom level when cloning tabs.
|
||||
@@ -1307,7 +715,7 @@ Changed
|
||||
* `init_venv.py` and `run_checks.py` have been replaced by http://tox.readthedocs.org/[tox]. Install tox and run `tox -e mkvenv` instead.
|
||||
* The tests now use http://pytest.org/[pytest]
|
||||
* Many new tests added
|
||||
* Mac Mini buildbot to run the tests on macOS.
|
||||
* Mac Mini buildbot to run the tests on OS X.
|
||||
* Coverage recording via http://nedbatchelder.com/code/coverage/[coverage.py].
|
||||
* New `--pdb-postmortem argument` to drop into the pdb debugger on exceptions.
|
||||
* Use https://github.com/ionelmc/python-hunter[hunter] for line tracing instead of a selfmade solution.
|
||||
@@ -1346,7 +754,7 @@ Fixed
|
||||
- Add a timeout to pastebin HTTP replies.
|
||||
- Various other fixes for small/rare bugs.
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.4[v0.1.4]
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.4[v0.1.4]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Changed
|
||||
@@ -1390,7 +798,7 @@ Security
|
||||
* Stop the icon database from being created when private-browsing is set to true.
|
||||
* Disable insecure SSL ciphers.
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.3[v0.1.3]
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.3[v0.1.3]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Changed
|
||||
@@ -1424,7 +832,7 @@ Security
|
||||
|
||||
* Fix for HTTP passwords accidentally being written to debug log.
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.2[v0.1.2]
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.2[v0.1.2]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Changed
|
||||
@@ -1443,7 +851,7 @@ Fixed
|
||||
|
||||
* Fix rare exception when a key is pressed shortly after opening a window
|
||||
* Fix exception with certain invalid URLs like `http:foo:0`
|
||||
* Work around Qt bug which renders checkboxes on macOS unusable
|
||||
* Work around Qt bug which renders checkboxes on OS X unusable
|
||||
* Fix exception when a local files can't be read in `:adblock-update`
|
||||
* Hide 2 more Qt warnings.
|
||||
* Add `!important` to hint CSS so websites don't override the hint look
|
||||
@@ -1456,7 +864,7 @@ Fixed
|
||||
* Fix user-stylesheet setting with an empty value.
|
||||
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1.1[v0.1.1]
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.1[v0.1.1]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Added
|
||||
@@ -1479,7 +887,7 @@ Changes
|
||||
* Set zoom to default instead of 100% with `:zoom`/`=`.
|
||||
* Adjust page zoom if default zoom changed.
|
||||
* Force tabs to be focused on `:undo`.
|
||||
* Replace manual installation instructions on macOS with homebrew/macports.
|
||||
* Replace manual installation instructions on OS X with homebrew/macports.
|
||||
* Allow min-/maximizing of print preview on Windows.
|
||||
* Various documentation improvements.
|
||||
* Various other small improvements and cleanups.
|
||||
@@ -1514,7 +922,7 @@ Fixed
|
||||
* Ensure the docs get included in `freeze.py`.
|
||||
* Fix crash with `:zoom`.
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/releases/tag/v0.1[v0.1]
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1[v0.1]
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Initial release.
|
||||
@@ -11,9 +11,9 @@ This document contains guidelines for contributing to qutebrowser, as well as
|
||||
useful hints when doing so.
|
||||
|
||||
If anything mentioned here would prevent you from contributing, please let me
|
||||
know, and contribute anyways! The guidelines are meant to make life easier for
|
||||
me, but if you don't follow everything in here, I won't be mad at you. In
|
||||
fact, I will probably change it for you.
|
||||
know, and contribute anyways! The guidelines are only meant to make life easier
|
||||
for me, but if you don't follow anything in here, I won't be mad at you. I will
|
||||
probably change it for you then, though.
|
||||
|
||||
If you have any problems, I'm more than happy to help! You can get help in
|
||||
several ways:
|
||||
@@ -34,22 +34,17 @@ this. It might be a good idea to ask on the mailing list or IRC channel to make
|
||||
sure nobody else started working on the same thing already.
|
||||
|
||||
If you want to find something useful to do, check the
|
||||
https://github.com/qutebrowser/qutebrowser/issues[issue tracker]. Some
|
||||
https://github.com/The-Compiler/qutebrowser/issues[issue tracker]. Some
|
||||
pointers:
|
||||
|
||||
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
|
||||
* https://github.com/The-Compiler/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?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3Ac%2B%2B[C++] (mostly work on Qt, the library behind qutebrowser)
|
||||
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Ajavascript[JavaScript]
|
||||
* https://github.com/The-Compiler/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:
|
||||
|
||||
* Help the community, e.g., on the mailinglist and the IRC channel.
|
||||
* Help the community, e.g. on the mailinglist and the IRC channel.
|
||||
* Improve the documentation.
|
||||
* Help on the website and graphics (logo, etc.).
|
||||
|
||||
@@ -60,12 +55,12 @@ qutebrowser uses http://git-scm.com/[git] for its development. You can clone
|
||||
the repo like this:
|
||||
|
||||
----
|
||||
git clone https://github.com/qutebrowser/qutebrowser.git
|
||||
git clone https://github.com/The-Compiler/qutebrowser.git
|
||||
----
|
||||
|
||||
If you don't know git, a http://git-scm.com/[git cheatsheet] might come in
|
||||
handy. Of course, if using git is the issue which prevents you from
|
||||
contributing, feel free to send normal patches instead, e.g., generated via
|
||||
contributing, feel free to send normal patches instead, e.g. generated via
|
||||
`diff -Nur`.
|
||||
|
||||
Getting patches
|
||||
@@ -82,7 +77,7 @@ based on your changes like this:
|
||||
----
|
||||
git format-patch origin/master <1>
|
||||
----
|
||||
<1> Replace `master` by the branch your work was based on, e.g.,
|
||||
<1> Replace `master` by the branch your work was based on, e.g.
|
||||
`origin/develop`.
|
||||
|
||||
Useful utilities
|
||||
@@ -94,45 +89,46 @@ Checkers
|
||||
qutebrowser uses http://tox.readthedocs.org/en/latest/[tox] to run its
|
||||
unittests and several linters/checkers.
|
||||
|
||||
Currently, the following tox environments are available:
|
||||
Currently, 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).
|
||||
- `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].
|
||||
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].
|
||||
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
|
||||
- VCS conflict markers
|
||||
- common spelling mistakes
|
||||
|
||||
The default test suite is run with `tox`; the list of default
|
||||
environments is obtained with `tox -l`.
|
||||
The default test suite is run with `tox`, the list of default
|
||||
environments is obtained with `tox -l` .
|
||||
|
||||
Please make sure the checks run without any warnings on your new contributions.
|
||||
|
||||
There's always the possibility of false positives; the following
|
||||
There's of course the possibility of false-positives, and the following
|
||||
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
|
||||
following comment:
|
||||
* If you think you have a good reason to suppress a message, add the following
|
||||
comment:
|
||||
+
|
||||
----
|
||||
# pylint: disable=message-name
|
||||
@@ -179,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`)
|
||||
@@ -190,10 +186,10 @@ the output in four different ways:
|
||||
Debugging
|
||||
~~~~~~~~~
|
||||
|
||||
There are some useful functions for debugging in the `qutebrowser.utils.debug`
|
||||
module.
|
||||
In the `qutebrowser.utils.debug` module there are some useful functions for
|
||||
debugging.
|
||||
|
||||
When starting qutebrowser with the `--debug` flag, you also get useful debug
|
||||
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.
|
||||
|
||||
@@ -218,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:
|
||||
@@ -280,23 +276,23 @@ Hints
|
||||
Python and Qt objects
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For many tasks, there are solutions available in both Qt and the Python
|
||||
standard library.
|
||||
For many tasks, there are solutions in both Qt and the Python standard library
|
||||
available.
|
||||
|
||||
In qutebrowser, the policy is usually to use the Python libraries, as they
|
||||
In qutebrowser, the policy is usually using the Python libraries, as they
|
||||
provide exceptions and other benefits.
|
||||
|
||||
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.
|
||||
|
||||
When using Qt objects, two issues must be taken care of:
|
||||
|
||||
* Methods of Qt objects report their status with their return values,
|
||||
* Methods of Qt objects report their status by using their return values,
|
||||
instead of using exceptions.
|
||||
+
|
||||
If a function gets or returns a Qt object which has an `.isValid()`
|
||||
@@ -308,12 +304,13 @@ on all such objects. It will raise
|
||||
If a function returns something else on error, the return value should
|
||||
carefully be checked.
|
||||
|
||||
* Methods of Qt objects have certain maximum values based on their
|
||||
* 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
|
||||
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).
|
||||
When passing a numeric parameter to a Qt function, all numbers should
|
||||
be range-checked using `qutebrowser.qtutils.check_overflow`, or
|
||||
passing a value which is too large should be avoided by other means
|
||||
(e.g. by setting a maximum value for a config object).
|
||||
|
||||
[[object-registry]]
|
||||
The object registry
|
||||
@@ -325,7 +322,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
|
||||
@@ -342,8 +339,8 @@ window=_win-id_, tab=_tab-id_])+. The default scope is `global`.
|
||||
All objects can be printed by starting with the `--debug` flag and using the
|
||||
`:debug-all-objects` command.
|
||||
|
||||
The registry is mainly used for <<commands,command handlers>>, but it can
|
||||
also be useful in places where using Qt's
|
||||
The registry is mainly used for <<commands,command handlers>> but also can be
|
||||
useful in places where using Qt's
|
||||
http://doc.qt.io/qt-5/signalsandslots.html[signals and slots] mechanism would
|
||||
be difficult.
|
||||
|
||||
@@ -353,7 +350,7 @@ Logging
|
||||
Logging is used at various places throughout the qutebrowser code. If you add a
|
||||
new feature, you should also add some strategic debug logging.
|
||||
|
||||
Unlike other Python projects, qutebrowser doesn't use a logger per file,
|
||||
Unless other Python projects, qutebrowser doesn't use a logger per file,
|
||||
instead it uses custom-named loggers.
|
||||
|
||||
The existing loggers are defined in `qutebrowser.utils.log`. If your feature
|
||||
@@ -416,11 +413,11 @@ selects which object registry (global, per-tab, etc.) to use. See the
|
||||
<<object-registry,object registry>> section for details.
|
||||
|
||||
There are also other arguments to customize the way the command is
|
||||
registered; see the class documentation for `register` in
|
||||
registered, see the class documentation for `register` in
|
||||
`qutebrowser.commands.cmdutils` for details.
|
||||
|
||||
The types of the function arguments are inferred based on their default values,
|
||||
e.g., an argument `foo=True` will be converted to a flag `-f`/`--foo` in
|
||||
e.g. an argument `foo=True` will be converted to a flag `-f`/`--foo` in
|
||||
qutebrowser's commandline.
|
||||
|
||||
The type can be overridden using Python's
|
||||
@@ -434,15 +431,15 @@ 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
|
||||
customize the flag an argument should get:
|
||||
decorator *after* `@cmdutils.register`. This can e.g. be used to customize the
|
||||
flag an argument should get:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
@@ -468,11 +465,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.
|
||||
- `hide=True`: Hide the argument from the documentation.
|
||||
- `completion`: A completion function (see `qutebrowser.completions.models.*`)
|
||||
to use when completing arguments for the given command.
|
||||
- `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
|
||||
@@ -486,13 +482,13 @@ qutebrowser handles two different types of URLs: URLs as a string, and URLs as
|
||||
the Qt `QUrl` type. As this can get confusing quickly, please follow the
|
||||
following guidelines:
|
||||
|
||||
* Convert a string to a QUrl object as early as possible, i.e., directly after
|
||||
* Convert a string to a QUrl object as early as possible, i.e. directly after
|
||||
the user did enter it.
|
||||
- Use `utils.urlutils.fuzzy_url` if the URL is entered by the user
|
||||
somewhere.
|
||||
- Be sure you handle `utils.urlutils.FuzzyError` and display an error
|
||||
message to the user.
|
||||
* Convert a `QUrl` object to a string as late as possible, i.e., before
|
||||
* Convert a `QUrl` object to a string as late as possible, e.g. before
|
||||
displaying it to the user.
|
||||
- If you want to display the URL to the user, use `url.toDisplayString()`
|
||||
so password information is removed.
|
||||
@@ -518,8 +514,8 @@ This is needed so valgrind handles self-modifying code correctly:
|
||||
[quote]
|
||||
____
|
||||
This option controls Valgrind's detection of self-modifying code. If no
|
||||
checking is done and a program executes some code, overwrites it with new
|
||||
code, and then executes the new code, Valgrind will continue to execute the
|
||||
checking is done, if a program executes some code, then overwrites it with new
|
||||
code, and executes the new code, Valgrind will continue to execute the
|
||||
translations it made for the old code. This will likely lead to incorrect
|
||||
behavior and/or crashes.
|
||||
|
||||
@@ -531,49 +527,6 @@ generate code and subsequently overwrite part or all of it. Running with all
|
||||
will slow Valgrind down noticeably.
|
||||
____
|
||||
|
||||
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].
|
||||
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`.
|
||||
|
||||
Note that the `flake8` tox env might not run due to encoding errors despite having LANG/LC_* set correctly.
|
||||
|
||||
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/
|
||||
|
||||
Style conventions
|
||||
-----------------
|
||||
|
||||
@@ -658,42 +611,63 @@ 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
|
||||
https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Aqt[qutebrowser
|
||||
https://github.com/The-Compiler/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Aqt[qutebrowser
|
||||
bugs] and check if they're fixed.
|
||||
* As soon as Homebrew updated, update the custom OS X bottle:
|
||||
- Update https://github.com/The-Compiler/homebrew-qt5-webkit/blob/master/Formula/qt5.rb[qt5.rb]
|
||||
- `brew install --build-from-source --build-bottle --verbose qt5.rb`
|
||||
- `brew bottle qt5.rb`
|
||||
- `brew install --build-from-source --build-bottle --verbose pyqt5`
|
||||
- `brew bottle pyqt5`
|
||||
- Upload bottles to github
|
||||
- Adjust `scripts/dev/ci/travis_install.sh`
|
||||
|
||||
New PyQt release
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* See above.
|
||||
* Install new PyQt in Windows VM (32- and 64-bit).
|
||||
* 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.
|
||||
* Run `x=... y=...` to set the respective shell variables.
|
||||
|
||||
* 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`.
|
||||
* Update changelog (remove *(unreleased)*).
|
||||
* Run tests again.
|
||||
* Commit.
|
||||
* Remove *(unreleased)* from changelog.
|
||||
* Run tests again
|
||||
* Run `asciidoc2html.py`.
|
||||
* 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"`)
|
||||
* If committing on minor branch, cherry-pick release commit to master.
|
||||
* Create release on github.
|
||||
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones
|
||||
* `git push origin`; `git push origin v0.X.Y`
|
||||
* Create release on github
|
||||
* Mark the milestone at https://github.com/The-Compiler/qutebrowser/milestones
|
||||
as closed.
|
||||
|
||||
* Linux: Run `python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||
* Windows: Run `C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* On server: Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
||||
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed.
|
||||
* Announce to qutebrowser and qutebrowser-announce mailinglist.
|
||||
* Run `scripts/dev/build_release.py` on Linux to build an sdist
|
||||
* Upload to PyPI: `twine upload dist/foo{,.asc}`
|
||||
* Create Windows packages via `C:\Python34_x32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py`
|
||||
* Upload to github
|
||||
|
||||
* Upload to qutebrowser.org with checksum/GPG
|
||||
- On server: `sudo mkdir -p /srv/http/qutebrowser/releases/v0.X.Y/windows`
|
||||
- `rsync -avPh dist/ tonks:`
|
||||
- On server: `sudo mv qutebrowser-0.X.Y.tar.gz* /srv/http/qutebrowser/releases/v0.X.Y`
|
||||
- Upload windows release:
|
||||
- `scp bb-win8:proj/qutebrowser/qutebrowser-0.X.Y-windows.zip .`
|
||||
- `aunpack qutebrowser-0.X.Y-windows.zip`
|
||||
- `sudo mv qutebrowser-0.X.Y-windows/* /srv/http/qutebrowser/releases/v0.X.Y/windows`
|
||||
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed
|
||||
* Announce to qutebrowser mailinglist
|
||||
@@ -72,8 +72,8 @@ Is there an adblocker?::
|
||||
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.
|
||||
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
|
||||
@@ -105,85 +105,13 @@ It also works nicely with rapid hints:
|
||||
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
|
||||
https://github.com/The-Compiler/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 `install_dict.py` script to install dictionaries.
|
||||
Run the script with `-h` for the parameter description.
|
||||
. Set `spellcheck.languages` to the desired list of languages, e.g.:
|
||||
`:set spellcheck.languages "['en-US', 'pl-PL']"`
|
||||
|
||||
How do I use Tor with qutebrowser?::
|
||||
Start tor on your machine, and do `:set network proxy socks://localhost:9050/`
|
||||
in qutebrowser. Note this won't give you the same amount of fingerprinting
|
||||
protection that the Tor Browser does, but it's useful to be able to access
|
||||
`.onion` sites.
|
||||
|
||||
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.
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
Configuration not saved after modifying config.::
|
||||
@@ -192,7 +120,7 @@ Configuration not saved after modifying config.::
|
||||
|
||||
Unable to view flash content.::
|
||||
If you have flash installed for on your system, it's necessary to enable plugins
|
||||
to use the flash plugin. Using the command `:set content.plugins true`
|
||||
to use the flash plugin. Using the command `:set content allow-plugins true`
|
||||
in qutebrowser will enable plugins. Packages for flash should
|
||||
be provided for your platform or it can be obtained from
|
||||
http://get.adobe.com/flashplayer/[Adobe].
|
||||
@@ -201,24 +129,32 @@ 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]
|
||||
See https://github.com/The-Compiler/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"
|
||||
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/The-Compiler/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL].
|
||||
+
|
||||
And then re-emerging qtwebengine with: +
|
||||
|
||||
emerge -1 qtwebengine
|
||||
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
|
||||
https://github.com/The-Compiler/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
|
||||
327
INSTALL.asciidoc
Normal file
@@ -0,0 +1,327 @@
|
||||
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-sip python3-jinja2 python3-pygments python3-yaml
|
||||
----
|
||||
|
||||
Get the packages from the https://github.com/The-Compiler/qutebrowser/releases[release page]
|
||||
|
||||
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-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 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 Windows
|
||||
----------
|
||||
|
||||
There are different ways to install qutebrowser on Windows:
|
||||
|
||||
Prebuilt binaries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Prebuilt standalone packages and MSI installers
|
||||
https://github.com/The-Compiler/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
|
||||
-------
|
||||
|
||||
The easiest way to install qutebrowser on OS X is to use the prebuilt `.app`
|
||||
files from the
|
||||
https://github.com/The-Compiler/qutebrowser/releases[release page].
|
||||
|
||||
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.
|
||||
|
||||
For Homebrew, a few extra steps are necessary since Homebrew dropped QtWebKit
|
||||
from Qt 5.6 - however, some users reported this didn't work for them, so using
|
||||
the `.app` is strongly encouraged.
|
||||
|
||||
This installs a Qt 5.5 and symlinks it so PyQt5 will work with it instead of Qt
|
||||
5.6. This requires that `qt5` is not installed via Homebrew:
|
||||
|
||||
----
|
||||
$ brew install python3 d-bus mysql sip xz
|
||||
$ brew install homebrew/versions/qt55
|
||||
$ brew install --ignore-dependencies pyqt5
|
||||
$ ln -s /usr/local/opt/qt55 /usr/local/opt/qt5
|
||||
|
||||
$ pip3.5 install qutebrowser
|
||||
----
|
||||
|
||||
For MacPorts, run:
|
||||
|
||||
----
|
||||
$ sudo port install python34 py34-jinja2 asciidoc py34-pygments py34-pyqt5
|
||||
$ sudo pip3.4 install qutebrowser
|
||||
----
|
||||
|
||||
The preferences for qutebrowser are stored in
|
||||
`~/Library/Preferences/qutebrowser`, the application data is stored in
|
||||
`~/Library/Application Support/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/The-Compiler/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
|
||||
----
|
||||
|
||||
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
|
||||
----
|
||||
22
MANIFEST.in
@@ -8,16 +8,15 @@ graft icons
|
||||
graft doc/img
|
||||
graft misc/apparmor
|
||||
graft misc/userscripts
|
||||
recursive-include scripts *.py *.sh
|
||||
recursive-include scripts *.py
|
||||
include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include LICENSE doc/* README.asciidoc
|
||||
include misc/qutebrowser.desktop
|
||||
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
|
||||
@@ -27,19 +26,24 @@ 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 qutebrowser/javascript/.eslintrc.yaml
|
||||
exclude qutebrowser/javascript/.eslintignore
|
||||
exclude .coveragerc
|
||||
exclude .pylintrc
|
||||
exclude .eslintrc
|
||||
exclude .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 .flake8
|
||||
|
||||
global-exclude __pycache__ *.pyc *.pyo
|
||||
|
||||
284
README.asciidoc
@@ -1,25 +1,26 @@
|
||||
// If you are reading this in plaintext or on PyPi:
|
||||
//
|
||||
// A rendered version is available at:
|
||||
// https://github.com/qutebrowser/qutebrowser/blob/master/README.asciidoc
|
||||
// https://github.com/The-Compiler/qutebrowser/blob/master/README.asciidoc
|
||||
|
||||
qutebrowser
|
||||
===========
|
||||
|
||||
// QUTE_WEB_HIDE
|
||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
|
||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.*
|
||||
|
||||
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/LICENSE"]
|
||||
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/The-Compiler/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://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"]
|
||||
image:https://requires.io/github/The-Compiler/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/The-Compiler/qutebrowser/requirements/?branch=master"]
|
||||
image:https://travis-ci.org/The-Compiler/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/The-Compiler/qutebrowser"]
|
||||
image:https://ci.appveyor.com/api/projects/status/9gmnuip6i1oq7046?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/The-Compiler/qutebrowser"]
|
||||
image:https://codecov.io/github/The-Compiler/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/The-Compiler/qutebrowser?branch=master"]
|
||||
|
||||
link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | link:https://github.com/qutebrowser/qutebrowser/releases[releases]
|
||||
link:http://www.qutebrowser.org[website] | link:http://blog.qutebrowser.org[blog] | link:https://github.com/The-Compiler/qutebrowser/releases[releases]
|
||||
// QUTE_WEB_HIDE_END
|
||||
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
|
||||
on Python and PyQt5 and 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.
|
||||
|
||||
@@ -34,9 +35,12 @@ image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"]
|
||||
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.
|
||||
See the https://github.com/The-Compiler/qutebrowser/releases[github releases
|
||||
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
|
||||
-------------
|
||||
@@ -44,15 +48,13 @@ Documentation
|
||||
In addition to the topics mentioned in this README, the following documents are
|
||||
available:
|
||||
|
||||
* 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"]
|
||||
* A http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="http://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]
|
||||
* 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]
|
||||
|
||||
@@ -67,18 +69,11 @@ 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.
|
||||
|
||||
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
|
||||
@@ -91,43 +86,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[0xFD55A072].
|
||||
http://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 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.9 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
|
||||
* 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]
|
||||
* http://pyyaml.org/wiki/PyYAML[PyYAML]
|
||||
* http://www.attrs.org/[attrs]
|
||||
|
||||
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
|
||||
--------
|
||||
@@ -148,62 +136,170 @@ 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
|
||||
* Antoni Boucher
|
||||
* Lamar Pavel
|
||||
* Bruno Oliveira
|
||||
* Alexander Cogneau
|
||||
* Felix Van der Jeugt
|
||||
* Martin Tournoij
|
||||
* Jakub Klinkovský
|
||||
* Raphael Pierzina
|
||||
* Joel Torstensson
|
||||
* Jan Verbeek
|
||||
* Tarcisio Fedrizzi
|
||||
* Patric Schmitz
|
||||
* Claude
|
||||
* Corentin Julé
|
||||
* meles5
|
||||
* Philipp Hansch
|
||||
* Panagiotis Ktistakis
|
||||
* Artur Shaik
|
||||
* Nathan Isom
|
||||
* Thorsten Wißmann
|
||||
* Kevin Velghe
|
||||
* Austin Anderson
|
||||
* Jimmy
|
||||
* Marshall Lochbaum
|
||||
* Alexey "Averrin" Nabrodov
|
||||
* avk
|
||||
* ZDarian
|
||||
* Milan Svoboda
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* Peter Vilim
|
||||
* Clayton Craft
|
||||
* Oliver Caldwell
|
||||
* Jonas Schürmann
|
||||
* error800
|
||||
* Liam BEGUIN
|
||||
* skinnay
|
||||
* Zach-Button
|
||||
* Tomasz Kramkowski
|
||||
* Ismail S
|
||||
* Halfwit
|
||||
* David Vogt
|
||||
* rikn00
|
||||
* kanikaa1234
|
||||
* haitaka
|
||||
* Nick Ginther
|
||||
* Michał Góral
|
||||
* Michael Ilsaas
|
||||
* Martin Zimmermann
|
||||
* Fritz Reichwald
|
||||
* Brian Jackson
|
||||
* sbinix
|
||||
* neeasade
|
||||
* jnphilipp
|
||||
* Tobias Patzl
|
||||
* Stefan Tatschner
|
||||
* Samuel Loury
|
||||
* Peter Michely
|
||||
* Panashe M. Fundira
|
||||
* Link
|
||||
* Larry Hynes
|
||||
* Johannes Altmanninger
|
||||
* Jeremy Kaplan
|
||||
* Ismail
|
||||
* Edgar Hipp
|
||||
* Daryl Finlay
|
||||
* adam
|
||||
* Samir Benmendil
|
||||
* Regina Hug
|
||||
* Mathias Fussenegger
|
||||
* Marcelo Santos
|
||||
* Jean-Louis Fuchs
|
||||
* Fritz V155 Reichwald
|
||||
* Franz Fellner
|
||||
* zwarag
|
||||
* xd1le
|
||||
* oniondreams
|
||||
* issue
|
||||
* haxwithaxe
|
||||
* evan
|
||||
* dylan araps
|
||||
* Xitian9
|
||||
* Tomas Orsava
|
||||
* Tobias Werth
|
||||
* Tim Harder
|
||||
* Thiago Barroso Perrotta
|
||||
* Sorokin Alexei
|
||||
* Noah Huesser
|
||||
* Matthias Lisin
|
||||
* Marcel Schilling
|
||||
* Johannes Martinsson
|
||||
* Jean-Christophe Petkovich
|
||||
* Jay Kamat
|
||||
* Helen Sherwood-Taylor
|
||||
* HalosGhost
|
||||
* Gregor Pohl
|
||||
* Eivind Uggedal
|
||||
* Dietrich Daroch
|
||||
* Daniel Lu
|
||||
* Arseniy Seroka
|
||||
* Andy Balaam
|
||||
* Andreas Fischer
|
||||
// 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/cmcaine/tridactyl[Tridactyl] (in early development, working
|
||||
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
|
||||
keyboard integration in Firefox).
|
||||
* 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],
|
||||
https://github.com/shinglyu/QuantumVim[QuantumVim]
|
||||
* Chrome/Chromium addons:
|
||||
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
|
||||
https://github.com/jinzhu/vrome[Vrome]
|
||||
* 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
|
||||
-------
|
||||
@@ -219,7 +315,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
@@ -0,0 +1,9 @@
|
||||
status:
|
||||
project:
|
||||
enabled: no
|
||||
patch:
|
||||
enabled: no
|
||||
changes:
|
||||
enabled: no
|
||||
|
||||
comment: off
|
||||
@@ -1,135 +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
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
TODO
|
||||
|
||||
Silver sponsors
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
TODO
|
||||
|
||||
Other sponsors
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
TODO: people with t-shirts or higher pledge levels
|
||||
|
||||
- 7scan
|
||||
- Alex Suykov
|
||||
- Alexey Zhikhartsev
|
||||
- Allan Nordhøy
|
||||
- Anirudh Sanjeev
|
||||
- Anssi Puustinen
|
||||
- Benedikt Steindorf
|
||||
- Bernardo Kuri
|
||||
- Blaise Duszynski
|
||||
- Bostan
|
||||
- Bruno Oliveira
|
||||
- Colin Jacobs
|
||||
- Daniel Andersson
|
||||
- Danilo
|
||||
- David Beley
|
||||
- David Hollings
|
||||
- David Parrish
|
||||
- Derin Yarsuvat
|
||||
- Dmytro Kostiuchenko
|
||||
- Frederik Thorøe
|
||||
- G4v4g4i
|
||||
- Gyula Teleki
|
||||
- H
|
||||
- Hosaka
|
||||
- Iordanis Grigoriou
|
||||
- Isaac Sandaljian
|
||||
- Jakub Podeszwik
|
||||
- Jamie Anderson
|
||||
- Jasper Woudenberg
|
||||
- Jens Højgaard
|
||||
- Johannes
|
||||
- John Baber-Lucero
|
||||
- Jonas Schürmann
|
||||
- Kenichiro Ito
|
||||
- Kenny Low
|
||||
- Lars Ivar Igesund
|
||||
- Lucas Aride Moulin
|
||||
- Ludovic Chabant
|
||||
- Lukas Gierth
|
||||
- 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
|
||||
- Prescott
|
||||
- Robotichead
|
||||
- Roshless
|
||||
- Ryan Ellis
|
||||
- Ryan P Deslandes
|
||||
- Sam Doshi
|
||||
- Sam Stone
|
||||
- Sean Herman
|
||||
- Sebastian Frysztak
|
||||
- Shelby Cruver
|
||||
- SirCmpwn
|
||||
- Soham Pal
|
||||
- Stewart Webb
|
||||
- Sven Reinecke
|
||||
- Tom Bass
|
||||
- Tomas Slusny
|
||||
- Tomasz Kramkowski
|
||||
- Tommy Thomas
|
||||
- Vasilij Schneidermann
|
||||
- Vlaaaaaaad
|
||||
- beanieuptop
|
||||
- demure
|
||||
- evenorbert
|
||||
- fishss
|
||||
- gsnewmark
|
||||
- guillermohs9
|
||||
- hubcaps
|
||||
- lobachevsky
|
||||
- neodarz
|
||||
- nihlaeth
|
||||
- notbenh
|
||||
- patrick suwanvithaya
|
||||
- pyratebeard
|
||||
- randm_dave
|
||||
- sabreman
|
||||
- toml
|
||||
- vimja
|
||||
- wiz
|
||||
- 43 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
|
||||
@@ -138,7 +16,7 @@ Gold sponsors
|
||||
- 1 Anonymous
|
||||
|
||||
Day sponsors
|
||||
~~~~~~~~~~~~
|
||||
------------
|
||||
|
||||
- Agent 42
|
||||
- Iggy Jackson
|
||||
@@ -150,7 +28,7 @@ Day sponsors
|
||||
- 4 Anonymous
|
||||
|
||||
Other sponsors
|
||||
~~~~~~~~~~~~~~
|
||||
--------------
|
||||
|
||||
- AP M
|
||||
- Alessandro Balzano
|
||||
|
||||
@@ -1,369 +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. **If you had an old configuration
|
||||
around and upgraded, this page will automatically open once**. To view it at a
|
||||
later time, use the `:help` command.
|
||||
|
||||
Migrating older configurations
|
||||
------------------------------
|
||||
|
||||
qutebrowser does no automatic migration for the new configuration. However,
|
||||
there's a special link:qute://configdiff/old[configdiff] page in qutebrowser,
|
||||
which will show you the changes you did in your old configuration, compared to
|
||||
the old defaults.
|
||||
|
||||
Other changes in default settings:
|
||||
|
||||
- `<Up>` and `<Down>` in the completion now navigate through command history
|
||||
instead of selecting completion items. You can get back the old behavior by
|
||||
doing:
|
||||
+
|
||||
----
|
||||
:bind -m command <Up> completion-item-focus prev
|
||||
:bind -m command <Down> completion-item-focus next
|
||||
----
|
||||
|
||||
- The default for `completion.web_history_max_items` is now set to `-1`, showing
|
||||
an unlimited number of items in the completion for `:open` as the new
|
||||
sqlite-based completion is much faster. If the `:open` completion is too slow
|
||||
on your machine, set an appropriate limit again.
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
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')
|
||||
----
|
||||
|
||||
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 = {}`.
|
||||
|
||||
Loading `autoconfig.yml`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
By default, all customization done via `:set`, `:bind` and `:unbind` is
|
||||
temporary as soon as a `config.py` exists. The settings done that way are always
|
||||
saved in the `autoconfig.yml` file, but you'll need to explicitly load it in
|
||||
your `config.py` by doing:
|
||||
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
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.
|
||||
|
||||
Getting the config directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you need to get the qutebrowser config directory, you can do so by reading
|
||||
`config.configdir`. Similarily, you can get the qutebrowser data directory via
|
||||
`config.datadir`.
|
||||
|
||||
This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path`
|
||||
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')
|
||||
----
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you use an editor with flake8 integration which complains about `c` and `config` being undefined, you can use:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
c = c # noqa: F821
|
||||
config = config # noqa: F821
|
||||
----
|
||||
|
||||
For type annotation support (note that those imports aren't guaranteed to be
|
||||
stable across qutebrowser versions):
|
||||
|
||||
[source,python]
|
||||
----
|
||||
from qutebrowser.config.configfiles import ConfigAPI # noqa: F401
|
||||
from qutebrowser.config.config import ConfigContainer # noqa: F401
|
||||
config = config # type: ConfigAPI # noqa: F821
|
||||
c = c # type: ConfigContainer # noqa: F821
|
||||
----
|
||||
@@ -6,14 +6,13 @@ 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:quickstart.html[Quick start guide]
|
||||
* 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:userscripts.html[How to write userscripts]
|
||||
* link:CONTRIBUTING.html[Contributing to qutebrowser]
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
|
Before Width: | Height: | Size: 989 KiB After Width: | Height: | Size: 989 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 32 KiB |
BIN
doc/img/main.png
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 26 KiB |
@@ -1,380 +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_ipca
|
||||
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>>.
|
||||
|
||||
Debian Stretch / Ubuntu 17.04 and newer
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Those versions come with QtWebEngine in the repositories. This makes it possible
|
||||
to install qutebrowser via the Debian package.
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt install python-tox python3-{lxml,pyqt5,sip,jinja2,pygments,yaml,attr} python3-pyqt5.qt{webengine,quick,opengl,sql} libqt5sql5-sqlite
|
||||
----
|
||||
|
||||
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
|
||||
----
|
||||
|
||||
Some 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-get install --no-install-recommends asciidoc source-highlight
|
||||
$ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
- If you prefer using QtWebKit, there's an up-to-date version available in
|
||||
Debian experimental, or from http://repo.paretje.be/unstable/[this repository]
|
||||
for Debian Stretch.
|
||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||
+
|
||||
----
|
||||
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||
----
|
||||
|
||||
On Fedora
|
||||
---------
|
||||
|
||||
qutebrowser is available in the official repositories for Fedora 22 and newer.
|
||||
|
||||
----
|
||||
# dnf install qutebrowser
|
||||
----
|
||||
|
||||
It's also recommended to install `python3-qt5-webengine` and start with `--backend
|
||||
webengine` to use the new backend. v1.0.0 (which is not in the Fedora repos
|
||||
currently) uses QtWebEngine by default.
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
|
||||
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
|
||||
---------
|
||||
|
||||
The Gentoo packages (even the live version) are lagging behind a lot and are
|
||||
effectively unmaintained. If you want to create and maintain an official
|
||||
qutebrowser overlay for Gentoo, please mailto:mail@qutebrowser.org[get in
|
||||
touch.]
|
||||
|
||||
It's recommended to <<tox,install qutebrowser via tox>> instead.
|
||||
|
||||
To get an up-to-date QtWebKit, you can use
|
||||
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
|
||||
|
||||
If video or sound don't work with QtWebKit, try installing the gstreamer
|
||||
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
|
||||
----------
|
||||
|
||||
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
|
||||
|
||||
Install the package:
|
||||
|
||||
----
|
||||
# pkg_add qutebrowser
|
||||
----
|
||||
|
||||
Or alternatively, use the ports system :
|
||||
|
||||
----
|
||||
# cd /usr/ports/www/qutebrowser
|
||||
# make install
|
||||
----
|
||||
|
||||
On Windows
|
||||
----------
|
||||
|
||||
There are different ways to install qutebrowser on Windows:
|
||||
|
||||
Prebuilt binaries
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Prebuilt standalone packages and installers
|
||||
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
|
||||
release.
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
* 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).
|
||||
|
||||
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 depdendencies (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
|
||||
----
|
||||
|
||||
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 `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
||||
Python3 is in your PATH before running tox.
|
||||
|
||||
Creating a wrapper script
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can then create a simple wrapper script to start qutebrowser somewhere in
|
||||
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
|
||||
|
||||
----
|
||||
#!/bin/bash
|
||||
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
|
||||
----
|
||||
|
||||
Updating
|
||||
~~~~~~~~
|
||||
|
||||
When you updated your local copy of the code (e.g. by pulling the git repo, or
|
||||
extracting a new version), the virtualenv should automatically use the updated
|
||||
code. However, if dependencies got added, this won't be reflected in the
|
||||
virtualenv. Thus it's recommended to run the following command to recreate the
|
||||
virtualenv:
|
||||
|
||||
----
|
||||
$ tox -r -e mkvenv-pypi
|
||||
----
|
||||
196
doc/notes
Normal file
@@ -0,0 +1,196 @@
|
||||
henk's thoughts
|
||||
===============
|
||||
|
||||
1. Power to the user! Protect privacy!
|
||||
Things the browser should only do with explicit consent from the user, if
|
||||
applicable the user should be able to choose which protocol/host/port triplets
|
||||
to white/blacklist:
|
||||
|
||||
- load/run executable code, like js, flash, java applets, ... (think NoScript)
|
||||
- requests to other domains, ports or using a different protocol than what the
|
||||
user requested (think RequestPolicy)
|
||||
- accept cookies
|
||||
- storing/saving/caching things, e.g. open tabs ("session"), cookies, page
|
||||
contents, browsing/download history, form data, ...
|
||||
- send referrer
|
||||
- disclose any (presence, type, version, settings, capabilities, etc.)
|
||||
information about OS, browser, installed fonts, plugins, addons, etc.
|
||||
|
||||
2. Be efficient!
|
||||
I tend to leave a lot of tabs open and nobody can deny that some websites
|
||||
simply suck, so the browser should, unless told otherwise by the user:
|
||||
|
||||
- load tabs only when needed
|
||||
- run code in tabs only when needed, i.e. when the tab is currently being
|
||||
used/viewed (background tabs doing some JS magic even when they are not being
|
||||
used can create a lot of unnecessary load on the machine)
|
||||
- finish requests to the domain the user requested (e.g. www.example.org)
|
||||
before doing any requests to other subdomains (e.g. images.example.org) and
|
||||
finish those before doing requests to thirdparty domains (e.g. example.com)
|
||||
|
||||
3. Be stable!
|
||||
- one site should not make the complete browser crash, only that site's tab
|
||||
|
||||
|
||||
Upstream Bugs
|
||||
=============
|
||||
|
||||
- Web inspector is blank unless .hide()/.show() is called.
|
||||
Asked on SO: http://stackoverflow.com/q/23499159/2085149
|
||||
TODO: Report to PyQt/Qt
|
||||
|
||||
- Report some other crashes
|
||||
|
||||
|
||||
/u/angelic_sedition's thoughts
|
||||
==============================
|
||||
|
||||
Well support for greasemonkey scripts and bookmarklets/js (which was mentioned
|
||||
in the arch forum post) would be a big addition. What I've usually missed when
|
||||
using other vim-like browsers is things that allow for different settings and
|
||||
key bindings for different contexts. With that implemented I think I could
|
||||
switch to a lightweight browser (and believe me, I'd like to) for the most part
|
||||
and only use firefox when I needed downthemall or something.
|
||||
|
||||
For example, I have different bindings based on tab position that are reloaded
|
||||
with a pentadactyl autocmd so that <space><homerow keys> will take me to tab
|
||||
1-10 if I'm in that range or 2-20 if I'm in that range. I have an autocmd that
|
||||
will run on completed downloads that passes the file path to a script that will
|
||||
open ranger in a floating window with that file cut (this is basically like
|
||||
using ranger to save files instead of the crappy gui popup).
|
||||
|
||||
I also have a few bindings based on tabgroups. Tabgroups are a firefox feature,
|
||||
but I find them very useful for sorting things by topic so that only the tabs
|
||||
I'm interested at the moment are visible.
|
||||
|
||||
Pentadactyl has a feature it calls groups. You can create a group that will
|
||||
activate for sites/urls that match a pattern with some regex support. This
|
||||
allows me, for example, to set up different (more convenient) bindings for
|
||||
zooming only on images. I'll never need use the equivalent of vim n (next text
|
||||
search match), so I can bind that to zoom. This allows setting up custom
|
||||
quickmarks/gotos using the same keys for different websites. For example, on
|
||||
reddit I have different g(some key) bindings to go to different subreddits.
|
||||
This can also be used to pass certain keys directly to the site (e.g. for use
|
||||
with RES). For sites that don't have modifiable bindings, I can use this with
|
||||
pentadactyl's feedkeys or xdotool to create my own custom bindings. I even have
|
||||
a binding that will call out to bash script with different arguments depending
|
||||
on the site to download an image or an image gallery depending on the site (in
|
||||
some cases passing the url to some cli program).
|
||||
|
||||
I've also noticed the lack of completion. For example, on "o" pentadactyl will
|
||||
show sites (e.g. from history) that can be completed. I think I've been spoiled
|
||||
by pentadactyl having completion for just about everything.
|
||||
|
||||
|
||||
suckless surf ML post
|
||||
=====================
|
||||
|
||||
From: Ben Woolley <tautolog_AT_gmail.com>
|
||||
Date: Wed, 7 Jan 2015 18:29:25 -0800
|
||||
|
||||
Hi all,
|
||||
|
||||
This patch is a bit of a beast for surf. It is intended to be applied after
|
||||
the disk cache patch. It breaks some internal interfaces, so it could
|
||||
conflict with other patches.
|
||||
|
||||
I have been wanting a browser to implement a complete same-origin policy,
|
||||
and have been investigating how to do this in various browsers for many
|
||||
months. When I saw how surf opened new windows in a separate process, and
|
||||
was so simple, I knew I could do it quickly. Over the last two weeks, I
|
||||
have been developing this implementation on surf.
|
||||
|
||||
The basic idea is to prevent browser-based tracking as you browse from site
|
||||
to site, or origin to origin. By "origin" domain, I mean the "first-party"
|
||||
domain, the domain normally in the location bar (of the typical browser
|
||||
interface). Each origin domain effectively gets its own browser profile,
|
||||
and a browser process only ever deals with one origin domain at a time.
|
||||
This isolates origins vertically, preventing cookies, disk cache, memory
|
||||
cache, and window.name vulnerabilities. Basically, all known
|
||||
vulnerabilities that google and Mozilla cite as counter-examples when they
|
||||
explain why they haven't disabled third-party cookies yet.
|
||||
|
||||
When you are on msnbc.com, the tracking pixels will be stored in a cookie
|
||||
file for msnbc.com. When you go to cnn.com, the tracking pixels will be
|
||||
stored in a cookie file for cnn.com. You will not be tracked between them.
|
||||
However, third-party cookies, and the caching of third party resources will
|
||||
still work, but they will be isolated between origin domains. Instead of
|
||||
blocking cookies and cache entries, they are "double-keyed", or *also*
|
||||
keyed by origin.
|
||||
|
||||
There is a unidirectional communication channel, however, from one origin
|
||||
to the next, through navigation from one origin to the next. That is, the
|
||||
query string is passed from one origin to the next, and may embed
|
||||
identifiers. One example is an affiliate link that identifies where the
|
||||
lead came from. I have implemented what I call "horizontal isolation", in
|
||||
the form of an "Origin Crossing Gate".
|
||||
|
||||
Whenever you follow a link to a new domain, or even are just redirected to
|
||||
a new domain, a new window/tab is opened, and passed the referring origin
|
||||
via -R. The page passed to -O, for example -O originprompt.html, is an HTML
|
||||
page that is loaded in the new origin's context. That page tells you the
|
||||
origin you were on, the new origin, and the full link, and you can decide
|
||||
to go just to the new origin, or go to the full URL, after reviewing it for
|
||||
tracking data.
|
||||
|
||||
Also, you may click links that store your trust of that relationship with
|
||||
various expiration times, the same way you would trust geolocation requests
|
||||
for a particular origin for a period of time. The database used is actually
|
||||
the new origin's cookie file. Since the origin prompt is loaded in the new
|
||||
origin's context, I can set a cookie on behalf of the new origin. The
|
||||
expiration time of the trust is the expiration time of the cookie. The
|
||||
cookie implementation in webkit automatically expires the trust as part of
|
||||
how cookies work. Each time you cross an origin, the origin crossing page
|
||||
checks the cookie to see if trust is still established. If so, it will use
|
||||
window.location.replace() to continue on automatically. The initial page
|
||||
renders blank until the trust is invalidated, in which case the content of
|
||||
the gate is made visible.
|
||||
|
||||
However, the new origin is technically able to mess with those cookies, so
|
||||
a website could set trust for an origin crossing. I have addressed that by
|
||||
hashing the key with a salt, and setting the real expiration time as the
|
||||
value, along with an HMAC to verify the contents of the value. If the
|
||||
cookie is messed with in any way, the trust will be disabled, and the
|
||||
prompt will appear again. So it has a fail-safe function.
|
||||
|
||||
I know it seems a bit convoluted, but it just started out as a nice little
|
||||
rabbit hole, and I just wanted to get something workable. At first I
|
||||
thought using the cookie expiration time was convenient, but then when I
|
||||
realized that I needed to protect the cookie, things got a bit hairy. But
|
||||
it works.
|
||||
|
||||
Each profile is, by default, stored in ~/.surf/origins/$origin/
|
||||
The interesting side effect is that if there is a problem where a website
|
||||
relies on the cross-site cookie vulnerability to make a connection, you can
|
||||
simply make a symbolic link from one origin folder to another, and they
|
||||
will share the same profile. And if you want to delete cookies and/or cache
|
||||
for a particular origin, you just rm -rf the origin's profile folder, and
|
||||
don't have to interfere with your other sites that are working just fine.
|
||||
|
||||
One thing I don't handle are cross-origins POSTs. They just end up as GET
|
||||
requests right now. I intend to do something about that, but I haven't
|
||||
figured that out yet.
|
||||
|
||||
I have only been using this functionality for a few days myself, so I have
|
||||
absolutely no feedback yet. I wanted to provide the first implementation of
|
||||
the management of identity as a system resource the same way that things
|
||||
like geolocation, camera, and microphone resources are managed in browsers
|
||||
and mobile apps.
|
||||
|
||||
Currently, Mozilla and Tor have are working on third-party tracking issues
|
||||
in Firefox.
|
||||
https://blog.mozilla.org/privacy/2014/11/10/introducing-polaris-privacy-initiative-to-accelerate-user-focused-privacy-online/
|
||||
|
||||
Up to this point, Tor has provided a patch that double-keys cookies with
|
||||
the origin domain, but no other progress is visible. I have seen no
|
||||
discussion of how horizontal isolation is supposed to happen, and I wanted
|
||||
to show people that it can be done, and this is one way it can be done, and
|
||||
to compel the other browser makers to catch up, and hopefully the community
|
||||
can work toward a standard *without* the tracking loopholes, by showing
|
||||
people what a *complete* solution looks like.
|
||||
|
||||
Thank you,
|
||||
|
||||
Ben Woolley
|
||||
|
||||
Patch: http://lists.suckless.org/dev/att-25070/0005-same-origin-policy.patch
|
||||
@@ -9,8 +9,7 @@ Basic keybindings to get you started
|
||||
------------------------------------
|
||||
|
||||
* Use the arrow keys or `hjkl` to move around a webpage (vim-like syntax is used in quite a few places)
|
||||
* To go to a new webpage, press `o`, then type a url, then press Enter (Use `O` to open the url in a new tab, `go` to edit the current URL)
|
||||
* If what you've typed isn't a url, then a search engine will be used instead (DuckDuckGo, by default)
|
||||
* To go to a new webpage, press `o`, then type a url, then press Enter (Use `O` to open the url in a new tab). If what you've typed isn't a url, then a search engine will be used instead (DuckDuckGo, by default)
|
||||
* To switch between tabs, use `J` (next tab) and `K` (previous tab), or press `<Alt-num>`, where `num` is the position of the tab to switch to
|
||||
* To close the current tab, press `d` (and press `u` to undo closing a tab)
|
||||
* Use `H` and `L` to go back and forth in the history
|
||||
@@ -25,16 +24,13 @@ What to do now
|
||||
* View the link:http://qutebrowser.org/img/cheatsheet-big.png[key binding cheatsheet]
|
||||
to make yourself familiar with the key bindings: +
|
||||
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.
|
||||
* 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].
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist]
|
||||
where there are weekly "what's new in qutebrowser" posts.
|
||||
* Let me know what features you are missing or things that need (even small!)
|
||||
improvements.
|
||||
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
:man source: qutebrowser
|
||||
:man manual: qutebrowser manpage
|
||||
:toc:
|
||||
:homepage: https://www.qutebrowser.org/
|
||||
:homepage: http://www.qutebrowser.org/
|
||||
|
||||
== NAME
|
||||
qutebrowser - a keyboard-driven, vim-like browser based on PyQt5.
|
||||
qutebrowser - a keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.
|
||||
|
||||
== SYNOPSIS
|
||||
*qutebrowser* ['-OPTION' ['...']] [':COMMAND' ['...']] ['URL' ['...']]
|
||||
|
||||
== 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,22 @@ show it.
|
||||
*-h*, *--help*::
|
||||
show this help message and exit
|
||||
|
||||
*-c* 'CONFDIR', *--confdir* 'CONFDIR'::
|
||||
Set config directory (empty for no config storage).
|
||||
|
||||
*--datadir* 'DATADIR'::
|
||||
Set data directory (empty for no data storage).
|
||||
|
||||
*--cachedir* 'CACHEDIR'::
|
||||
Set cache directory (empty for no cache storage).
|
||||
|
||||
*--basedir* 'BASEDIR'::
|
||||
Base directory for all storage.
|
||||
Base directory for all storage. Other --*dir arguments are ignored if this is given.
|
||||
|
||||
*-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 +66,7 @@ show it.
|
||||
How URLs should be opened if there is already a qutebrowser instance running.
|
||||
|
||||
*--backend* '{webkit,webengine}'::
|
||||
Which backend to use.
|
||||
|
||||
*--enable-webengine-inspector*::
|
||||
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
|
||||
Which backend to use (webengine backend is EXPERIMENTAL!).
|
||||
|
||||
=== debug arguments
|
||||
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
||||
@@ -84,30 +90,51 @@ 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.
|
||||
|
||||
*--debug-exit*::
|
||||
Turn on debugging of late exit.
|
||||
|
||||
*--pdb-postmortem*::
|
||||
Drop into pdb on exceptions.
|
||||
|
||||
*--temp-basedir*::
|
||||
Use a temporary basedir.
|
||||
|
||||
*--no-err-windows*::
|
||||
Don't show any error windows (used for tests/smoke.py).
|
||||
|
||||
*--qt-arg* 'NAME' 'VALUE'::
|
||||
Pass an argument with a value to Qt. For example, you can do `--qt-arg geometry 650x555+200+300` to set the window geometry.
|
||||
*--qt-name* 'NAME'::
|
||||
Set the window name.
|
||||
|
||||
*--qt-flag* 'QT_FLAG'::
|
||||
Pass an argument to Qt as flag.
|
||||
*--qt-style* 'STYLE'::
|
||||
Set the Qt GUI style to use.
|
||||
|
||||
*--debug-flag* 'DEBUG_FLAGS'::
|
||||
Pass name of debugging feature to be turned on.
|
||||
*--qt-stylesheet* 'STYLESHEET'::
|
||||
Override the Qt application stylesheet.
|
||||
|
||||
*--qt-widgetcount*::
|
||||
Print debug message at the end about number of widgets left undestroyed and maximum number of widgets existed at the same time.
|
||||
|
||||
*--qt-reverse*::
|
||||
Set the application's layout direction to right-to-left.
|
||||
|
||||
*--qt-qmljsdebugger* 'port:PORT[,block]'::
|
||||
Activate the QML/JS debugger with a specified port. 'block' is optional and will make the application wait until a debugger connects to it.
|
||||
// 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.
|
||||
|
||||
@@ -118,7 +145,7 @@ defaults.
|
||||
|
||||
== BUGS
|
||||
Bugs are tracked in the Github issue tracker at
|
||||
https://github.com/qutebrowser/qutebrowser/issues.
|
||||
https://github.com/The-Compiler/qutebrowser/issues.
|
||||
|
||||
If you found a bug, use the built-in ':report' command to create a bug report
|
||||
with all information needed.
|
||||
@@ -128,7 +155,7 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||
mailto:qutebrowser@lists.qutebrowser.org[] instead.
|
||||
|
||||
For security bugs, please contact me directly at me@the-compiler.org, GPG ID
|
||||
https://www.the-compiler.org/pubkey.asc[0xFD55A072].
|
||||
http://www.the-compiler.org/pubkey.asc[0xFD55A072].
|
||||
|
||||
== COPYRIGHT
|
||||
This program is free software: you can redistribute it and/or modify it under
|
||||
@@ -144,14 +171,12 @@ You should have received a copy of the GNU General Public License along with
|
||||
this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
== RESOURCES
|
||||
* Website: https://www.qutebrowser.org/
|
||||
* Website: http://www.qutebrowser.org/
|
||||
* Mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] /
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser
|
||||
* Announce-only mailinglist: mailto:qutebrowser-announce@lists.qutebrowser.org[] /
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce
|
||||
* IRC: irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on
|
||||
http://freenode.net/[Freenode]
|
||||
* Github: https://github.com/qutebrowser/qutebrowser
|
||||
* Github: https://github.com/The-Compiler/qutebrowser
|
||||
|
||||
== AUTHOR
|
||||
*qutebrowser* was written by Florian Bruhin. All contributors can be found in
|
||||
|
||||
@@ -41,8 +41,21 @@ 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).
|
||||
|
||||
To install my pre-built packages
|
||||
++++++++++++++++++++++++++++++++
|
||||
.To compile by yourself
|
||||
|
||||
----
|
||||
$ git clone https://github.com/The-Compiler/qt-debug-pkgbuild.git
|
||||
$ cd qt-debug-pkgbuild
|
||||
$ git checkout symbols
|
||||
$ export DEBUG_CFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
|
||||
$ export DEBUG_CXXFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
|
||||
$ cd qt5
|
||||
$ makepkg -si --pkg qt5-base-debug,qt5-webkit-debug,qt5-webengine-debug
|
||||
$ cd ../pyqt5
|
||||
$ makepkg -si --pkg pyqt5-common-debug,python-pyqt5-debug
|
||||
----
|
||||
|
||||
.To install my pre-built packages
|
||||
|
||||
First download and sign the key:
|
||||
|
||||
@@ -63,30 +76,12 @@ Server = http://qutebrowser.org/qt-debug/$arch
|
||||
Then install the packages:
|
||||
|
||||
----
|
||||
# pacman -Suy pyqt5-common-debug python-pyqt5-debug qt5-base-debug qt5-webkit-debug qt5-webengine-debug
|
||||
# pacman -Suy pyqt5-common-debug python-pyqt5-debug qt5-base-debug qt5-webkit-debug,qt5-webengine-debug
|
||||
----
|
||||
|
||||
The `-debug` packages conflict with the non-debug variants - it's safe to
|
||||
remove them.
|
||||
|
||||
To compile by yourself
|
||||
++++++++++++++++++++++
|
||||
|
||||
Note that building Qt will likely take multiple hours, even on a recent system.
|
||||
I'd also expect it to take around 6 GB of RAM and 30 GB of disk space for a
|
||||
successful compile run.
|
||||
|
||||
----
|
||||
$ git clone https://github.com/qutebrowser/qt-debug-pkgbuild.git
|
||||
$ cd qt-debug-pkgbuild
|
||||
$ export DEBUG_CFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
|
||||
$ export DEBUG_CXXFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
|
||||
$ cd qt5
|
||||
$ makepkg -si --pkg qt5-base-debug,qt5-webkit-debug,qt5-webengine-debug
|
||||
$ cd ../pyqt5
|
||||
$ makepkg -si --pkg pyqt5-common-debug,python-pyqt5-debug
|
||||
----
|
||||
|
||||
Getting the stack trace
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -174,7 +169,7 @@ When you see the _qutebrowser.exe has stopped working_ window, do not click
|
||||
file displayed there.
|
||||
|
||||
Now install
|
||||
https://www.microsoft.com/en-us/download/details.aspx?id=49924[DebugDiag] from
|
||||
http://www.microsoft.com/en-us/download/details.aspx?id=42933[DebugDiag] from
|
||||
Microsoft, then run the *DebugDiag 2 Analysis* tool. There, check
|
||||
*CrashHangAnalysis* and add your crash dump via *Add Data files*. Then click
|
||||
*Start analysis*.
|
||||
|
||||
@@ -22,8 +22,6 @@ To call a userscript, it needs to be stored in your data directory under
|
||||
`userscripts` (for example: `~/.local/share/qutebrowser/userscripts/myscript`),
|
||||
or just use an absolute path.
|
||||
|
||||
NOTE: On Windows, only userscripts with `com`, `bat`, or `exe` extensions will be launched.
|
||||
|
||||
Getting information
|
||||
-------------------
|
||||
|
||||
@@ -38,15 +36,13 @@ The following environment variables will be set when a userscript is launched:
|
||||
- `QUTE_CONFIG_DIR`: Path of the directory containing qutebrowser's configuration.
|
||||
- `QUTE_DATA_DIR`: Path of the directory containing qutebrowser's data.
|
||||
- `QUTE_DOWNLOAD_DIR`: Path of the downloads directory.
|
||||
- `QUTE_COMMANDLINE_TEXT`: Text currently in qutebrowser's command line.
|
||||
|
||||
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).
|
||||
- `QUTE_SELECTED_HTML` The HTML currently selected on the page.
|
||||
|
||||
In `hints` mode:
|
||||
|
||||
@@ -60,7 +56,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
|
||||
@@ -78,11 +74,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]
|
||||
|
||||
4136
misc/cheatsheet.svg
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 137 KiB |
35
misc/docker/archlinux/Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
FROM base/archlinux
|
||||
MAINTAINER Florian Bruhin <me@the-compiler.org>
|
||||
|
||||
RUN echo 'Server = http://mirror.de.leaseweb.net/archlinux/$repo/os/$arch' > /etc/pacman.d/mirrorlist
|
||||
RUN pacman-key --init && pacman-key --populate archlinux && pacman -Sy --noconfirm archlinux-keyring
|
||||
|
||||
RUN pacman -Suyy --noconfirm
|
||||
RUN pacman-db-upgrade
|
||||
|
||||
RUN pacman -S --noconfirm \
|
||||
git \
|
||||
python-tox \
|
||||
qt5-base \
|
||||
qt5-webkit \
|
||||
python-pyqt5 \
|
||||
xorg-xinit \
|
||||
herbstluftwm \
|
||||
xorg-server-xvfb
|
||||
|
||||
RUN echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && locale-gen
|
||||
|
||||
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
|
||||
USER user
|
||||
WORKDIR /home/user
|
||||
|
||||
ENV DISPLAY=:0
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
CMD Xvfb -screen 0 800x600x24 :0 & \
|
||||
sleep 2 && \
|
||||
herbstluftwm & \
|
||||
git clone /outside qutebrowser.git && \
|
||||
cd qutebrowser.git && \
|
||||
tox -e py35
|
||||
35
misc/docker/debian-jessie/Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
FROM debian:jessie
|
||||
MAINTAINER Florian Bruhin <me@the-compiler.org>
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get -y update && \
|
||||
apt-get -y dist-upgrade && \
|
||||
apt-get -y install --no-install-recommends \
|
||||
python3-pyqt5 \
|
||||
python3-pyqt5.qtwebkit \
|
||||
python-tox \
|
||||
python3-sip \
|
||||
xvfb \
|
||||
git \
|
||||
python3-setuptools \
|
||||
wget \
|
||||
herbstluftwm \
|
||||
locales \
|
||||
libjs-pdf
|
||||
RUN echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && locale-gen
|
||||
|
||||
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
|
||||
USER user
|
||||
WORKDIR /home/user
|
||||
|
||||
ENV DISPLAY=:0
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
CMD Xvfb -screen 0 800x600x24 :0 & \
|
||||
sleep 2 && \
|
||||
herbstluftwm & \
|
||||
git clone /outside qutebrowser.git && \
|
||||
cd qutebrowser.git && \
|
||||
tox -e py34
|
||||
37
misc/docker/ubuntu-xenial/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM ubuntu:xenial
|
||||
MAINTAINER Florian Bruhin <me@the-compiler.org>
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get -y update && \
|
||||
apt-get -y dist-upgrade && \
|
||||
apt-get -y install --no-install-recommends \
|
||||
python3-pyqt5 \
|
||||
python3-pyqt5.qtwebkit \
|
||||
python-tox \
|
||||
python3-sip \
|
||||
xvfb \
|
||||
git \
|
||||
python3-setuptools \
|
||||
wget \
|
||||
herbstluftwm \
|
||||
language-pack-en \
|
||||
libjs-pdf \
|
||||
dbus
|
||||
|
||||
RUN dbus-uuidgen --ensure
|
||||
|
||||
RUN useradd user && mkdir /home/user && chown -R user:users /home/user
|
||||
USER user
|
||||
WORKDIR /home/user
|
||||
|
||||
ENV DISPLAY=:0
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
CMD Xvfb -screen 0 800x600x24 :0 & \
|
||||
sleep 2 && \
|
||||
herbstluftwm & \
|
||||
git clone /outside qutebrowser.git && \
|
||||
cd qutebrowser.git && \
|
||||
tox -e py35
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -71,5 +70,5 @@ coll = COLLECT(exe,
|
||||
app = BUNDLE(coll,
|
||||
name='qutebrowser.app',
|
||||
icon=icon,
|
||||
# https://github.com/pyinstaller/pyinstaller/blob/b78bfe530cdc2904f65ce098bdf2de08c9037abb/PyInstaller/hooks/hook-PyQt5.QtWebEngineWidgets.py#L24
|
||||
bundle_identifier='org.qt-project.Qt.QtWebEngineCore')
|
||||
info_plist={'NSHighResolutionCapable': 'True'},
|
||||
bundle_identifier=None)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
check-manifest==0.35
|
||||
check-manifest==0.31
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
codecov==2.0.9
|
||||
coverage==4.4.1
|
||||
idna==2.6
|
||||
requests==2.18.4
|
||||
urllib3==1.22
|
||||
codecov==2.0.5
|
||||
coverage==4.1
|
||||
requests==2.10.0
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.9.1
|
||||
sip==4.19.4
|
||||
cx-Freeze==4.3.4
|
||||
1
misc/requirements/requirements-cxfreeze.txt-raw
Normal file
@@ -0,0 +1 @@
|
||||
cx_Freeze
|
||||
@@ -2,22 +2,25 @@
|
||||
|
||||
flake8==2.6.2 # rq.filter: < 3.0.0
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==1.4.0 # rq.filter: != 2.0.0
|
||||
flake8-deprecated==1.2.1
|
||||
flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
|
||||
flake8-debugger==1.4.0
|
||||
flake8-deprecated==1.0
|
||||
flake8-docstrings==0.2.8
|
||||
flake8-future-import==0.4.3
|
||||
flake8-mock==0.3
|
||||
flake8-pep3101==1.0 # rq.filter: < 1.1
|
||||
flake8-polyfill==1.0.1
|
||||
flake8-mock==0.2
|
||||
flake8-pep3101==0.4
|
||||
flake8-putty==0.4.0
|
||||
flake8-string-format==0.2.3
|
||||
flake8-tidy-imports==1.1.0
|
||||
flake8-tuple==0.2.13
|
||||
mccabe==0.6.1
|
||||
packaging==16.8
|
||||
flake8-string-format==0.2.2
|
||||
flake8-tidy-imports==1.0.2
|
||||
flake8-tuple==0.2.12
|
||||
hacking==0.11.0
|
||||
mccabe==0.5.0
|
||||
packaging==16.7
|
||||
pbr==1.10.0
|
||||
pep257==0.7.0 # still needed by flake8-docstrings but ignored
|
||||
pep8==1.7.0
|
||||
pep8-naming==0.4.1
|
||||
pycodestyle==2.3.1
|
||||
pydocstyle==1.1.1 # rq.filter: < 2.0.0
|
||||
pyflakes==1.6.0
|
||||
pyparsing==2.2.0
|
||||
six==1.11.0
|
||||
pycodestyle==2.0.0
|
||||
pydocstyle==1.0.0
|
||||
pyflakes==1.2.3
|
||||
pyparsing==2.1.5
|
||||
six==1.10.0
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
flake8<3.0.0
|
||||
flake8-copyright
|
||||
flake8-debugger!=2.0.0
|
||||
flake8-debugger
|
||||
flake8-deprecated
|
||||
flake8-docstrings<1.1.0
|
||||
flake8-docstrings
|
||||
flake8-future-import
|
||||
flake8-mock
|
||||
flake8-pep3101<1.1
|
||||
flake8-pep3101
|
||||
flake8-putty
|
||||
flake8-string-format
|
||||
flake8-tidy-imports
|
||||
flake8-tuple
|
||||
hacking
|
||||
pep8-naming
|
||||
pydocstyle<2.0.0
|
||||
pydocstyle
|
||||
pyflakes
|
||||
|
||||
# Pinned to 2.0.0 otherwise
|
||||
pycodestyle==2.3.1
|
||||
# Pinned to 0.5.3 otherwise
|
||||
mccabe==0.6.1
|
||||
pep8==1.7.0
|
||||
|
||||
# Waiting until flake8-putty updated
|
||||
#@ comment: pep257 still needed by flake8-docstrings but ignored
|
||||
|
||||
# Waiting until hacking/flake8-tuple are updated
|
||||
#@ filter: flake8 < 3.0.0
|
||||
#@ filter: pydocstyle < 2.0.0
|
||||
#@ filter: flake8-docstrings < 1.1.0
|
||||
#@ filter: flake8-pep3101 < 1.1
|
||||
|
||||
# https://github.com/JBKahn/flake8-debugger/issues/5
|
||||
#@ filter: flake8-debugger != 2.0.0
|
||||
|
||||
@@ -1,8 +1 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
appdirs==1.4.3
|
||||
packaging==16.8
|
||||
pyparsing==2.2.0
|
||||
setuptools==36.5.0
|
||||
six==1.11.0
|
||||
wheel==0.30.0
|
||||
pip==8.1.2
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
altgraph==0.14
|
||||
future==0.16.0
|
||||
macholib==1.8
|
||||
pefile==2017.9.3
|
||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||
PyInstaller==3.2
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# @develop#
|
||||
PyInstaller
|
||||
|
||||
@@ -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==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
isort==4.2.5
|
||||
lazy-object-proxy==1.2.2
|
||||
mccabe==0.5.0
|
||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
uritemplate==3.0.0
|
||||
uritemplate.py==3.0.2
|
||||
urllib3==1.22
|
||||
wrapt==1.10.11
|
||||
requests==2.10.0
|
||||
six==1.10.0
|
||||
wrapt==1.10.8
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||
./scripts/dev/pylint_checkers
|
||||
requests
|
||||
github3.py
|
||||
|
||||
# https://github.com/PyCQA/pylint/issues/932
|
||||
mccabe==0.5.0
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# #
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==1.5.3
|
||||
certifi==2017.7.27.1
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
idna==2.6
|
||||
isort==4.2.15
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.7.4
|
||||
astroid==1.4.7
|
||||
isort==4.2.5
|
||||
lazy-object-proxy==1.2.2
|
||||
mccabe==0.5.0
|
||||
pylint==1.6.4
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
uritemplate==3.0.0
|
||||
uritemplate.py==3.0.2
|
||||
urllib3==1.22
|
||||
wrapt==1.10.11
|
||||
requests==2.10.0
|
||||
six==1.10.0
|
||||
wrapt==1.10.8
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pylint
|
||||
./scripts/dev/pylint_checkers
|
||||
requests
|
||||
github3.py
|
||||
|
||||
# fix qute-pylint location
|
||||
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
PyQt5
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.14
|
||||
pyroma==2.2
|
||||
docutils==0.12
|
||||
pyroma==2.0.2
|
||||
|
||||
@@ -4,4 +4,3 @@ pyPEG2
|
||||
PyYAML
|
||||
colorama
|
||||
cssutils
|
||||
attrs
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
bzr+lp:beautifulsoup
|
||||
git+https://github.com/cherrypy/cheroot.git
|
||||
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/HypothesisWorks/hypothesis-python.git
|
||||
git+https://github.com/pallets/itsdangerous.git
|
||||
git+https://bitbucket.org/zzzeek/mako.git
|
||||
git+https://github.com/r1chardj0n3s/parse.git
|
||||
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
|
||||
git+https://github.com/pytest-dev/pytest-mock.git
|
||||
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/The-Compiler/pytest-xvfb.git
|
||||
hg+https://bitbucket.org/gutworth/six
|
||||
hg+https://bitbucket.org/jendrikseipp/vulture
|
||||
git+https://github.com/pallets/werkzeug.git
|
||||
|
||||
|
||||
## qutebrowser dependencies
|
||||
|
||||
git+https://github.com/tartley/colorama.git
|
||||
hg+https://bitbucket.org/cthedot/cssutils
|
||||
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
|
||||
|
||||
# Fails to build:
|
||||
# gcc: error: ext/_yaml.c: No such file or directory
|
||||
# hg+https://bitbucket.org/xi/pyyaml
|
||||
PyYAML==3.12
|
||||
@@ -1,39 +1,32 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==17.2.0
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==5.8.3
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.4.1
|
||||
EasyProcess==0.2.3
|
||||
fields==5.0.0
|
||||
Flask==0.12.2
|
||||
glob2==0.6
|
||||
hunter==2.0.1
|
||||
hypothesis==3.32.0
|
||||
beautifulsoup4==4.5.0
|
||||
CherryPy==7.1.0
|
||||
coverage==4.1
|
||||
decorator==4.0.10
|
||||
Flask==0.10.1 # rq.filter: < 0.11.0
|
||||
glob2==0.4.1
|
||||
httpbin==0.4.1
|
||||
hypothesis==3.4.2
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.9.6
|
||||
Mako==1.0.7
|
||||
# MarkupSafe==1.0
|
||||
parse==1.8.2
|
||||
parse-type==0.4.2
|
||||
py==1.4.34
|
||||
py-cpuinfo==3.3.0
|
||||
pytest==3.2.3
|
||||
pytest-bdd==2.18.2
|
||||
pytest-benchmark==3.1.1
|
||||
# Jinja2==2.8
|
||||
Mako==1.0.4
|
||||
# MarkupSafe==0.23
|
||||
parse==1.6.6
|
||||
parse-type==0.3.4
|
||||
py==1.4.31
|
||||
pytest==2.9.2
|
||||
pytest-bdd==2.17.0
|
||||
pytest-catchlog==1.2.2
|
||||
pytest-cov==2.5.1
|
||||
pytest-faulthandler==1.3.1
|
||||
pytest-cov==2.3.0
|
||||
pytest-faulthandler==1.3.0
|
||||
pytest-instafail==0.3.0
|
||||
pytest-mock==1.6.3
|
||||
pytest-qt==2.2.1
|
||||
pytest-repeat==0.4.1
|
||||
pytest-rerunfailures==3.1
|
||||
pytest-mock==1.1
|
||||
pytest-qt==1.11.0
|
||||
pytest-repeat==0.3.0
|
||||
pytest-rerunfailures==2.0.0
|
||||
pytest-travis-fold==1.2.0
|
||||
pytest-xvfb==1.0.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.11.0
|
||||
vulture==0.26
|
||||
Werkzeug==0.12.2
|
||||
pytest-xvfb==0.2.0
|
||||
six==1.10.0
|
||||
vulture==0.10
|
||||
Werkzeug==0.11.10
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
beautifulsoup4
|
||||
cheroot
|
||||
CherryPy
|
||||
coverage
|
||||
Flask
|
||||
hunter
|
||||
Flask==0.10.1
|
||||
httpbin
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
pytest-benchmark
|
||||
pytest-catchlog
|
||||
pytest-cov
|
||||
pytest-faulthandler
|
||||
@@ -19,4 +18,5 @@ pytest-travis-fold
|
||||
pytest-xvfb
|
||||
vulture
|
||||
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
#@ filter: Flask < 0.11.0
|
||||
#@ ignore: Jinja2, MarkupSafe
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pluggy==0.5.2
|
||||
py==1.4.34
|
||||
tox==2.9.1
|
||||
virtualenv==15.1.0
|
||||
pluggy==0.3.1
|
||||
py==1.4.31
|
||||
tox==2.3.1
|
||||
virtualenv==15.0.2
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
tox
|
||||
|
||||
# The latest tox release still depends on pluggy < 0.4...
|
||||
pluggy==0.4.0
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==0.26
|
||||
vulture==0.10
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Behaviour
|
||||
# Userscript for qutebrowser which casts the url passed in $1 to the default
|
||||
# ChromeCast device in the network using the program `castnow`
|
||||
#
|
||||
# Usage
|
||||
# You can launch the script from qutebrowser as follows:
|
||||
# spawn --userscript ${PATH_TO_FILE} {url}
|
||||
#
|
||||
# Then, you can control the chromecast by launching the simple command
|
||||
# `castnow` in a shell which will connect to the running castnow instance.
|
||||
#
|
||||
# For stopping the script, issue the command `pkill -f castnow` which would
|
||||
# then let the rest of the userscript execute for cleaning temporary file.
|
||||
#
|
||||
# Thanks
|
||||
# This userscript borrows Thorsten Wißmann's javascript code from his `mpv`
|
||||
# userscript.
|
||||
#
|
||||
# Dependencies
|
||||
# - castnow, https://github.com/xat/castnow
|
||||
#
|
||||
# Author
|
||||
# Simon Désaulniers <sim.desaulniers@gmail.com>
|
||||
|
||||
if [ -z "$QUTE_FIFO" ] ; then
|
||||
cat 1>&2 <<EOF
|
||||
Error: $0 can not be run as a standalone script.
|
||||
|
||||
It is a qutebrowser userscript. In order to use it, call it using
|
||||
'spawn --userscript' as described in qute://help/userscripts.html
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
msg() {
|
||||
local cmd="$1"
|
||||
shift
|
||||
local msg="$*"
|
||||
if [ -z "$QUTE_FIFO" ] ; then
|
||||
echo "$cmd: $msg" >&2
|
||||
else
|
||||
echo "message-$cmd '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||
fi
|
||||
}
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
|
||||
function descendantOfTagName(child, ancestorTagName) {
|
||||
// tells whether child has some (proper) ancestor
|
||||
// with the tag name ancestorTagName
|
||||
while (child.parentNode != null) {
|
||||
child = child.parentNode;
|
||||
if (typeof child.tagName === 'undefined') break;
|
||||
if (child.tagName.toUpperCase() == ancestorTagName.toUpperCase()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var App = {};
|
||||
|
||||
var all_videos = [];
|
||||
all_videos.push.apply(all_videos, document.getElementsByTagName("video"));
|
||||
all_videos.push.apply(all_videos, document.getElementsByTagName("object"));
|
||||
all_videos.push.apply(all_videos, document.getElementsByTagName("embed"));
|
||||
App.backup_videos = Array();
|
||||
App.all_replacements = Array();
|
||||
for (i = 0; i < all_videos.length; i++) {
|
||||
var video = all_videos[i];
|
||||
if (descendantOfTagName(video, "object")) {
|
||||
// skip tags that are contained in an object, because we hide
|
||||
// the object anyway.
|
||||
continue;
|
||||
}
|
||||
var replacement = document.createElement("div");
|
||||
replacement.innerHTML = "
|
||||
<p style=\\"margin-bottom: 0.5em\\">
|
||||
The video is being cast on your ChromeCast device.
|
||||
</p>
|
||||
<p>
|
||||
In order to restore this particular video
|
||||
<a style=\\"font-weight: bold;
|
||||
color: white;
|
||||
background: transparent;
|
||||
\\"
|
||||
onClick=\\"restore_video(this, " + i + ");\\"
|
||||
href=\\"javascript: restore_video(this, " + i + ")\\"
|
||||
>click here</a>.
|
||||
</p>
|
||||
";
|
||||
replacement.style.position = "relative";
|
||||
replacement.style.zIndex = "100003000000";
|
||||
replacement.style.fontSize = "1rem";
|
||||
replacement.style.textAlign = "center";
|
||||
replacement.style.verticalAlign = "middle";
|
||||
replacement.style.height = "100%";
|
||||
replacement.style.background = "#101010";
|
||||
replacement.style.color = "white";
|
||||
replacement.style.border = "4px dashed #545454";
|
||||
replacement.style.padding = "2em";
|
||||
replacement.style.margin = "auto";
|
||||
App.all_replacements[i] = replacement;
|
||||
App.backup_videos[i] = video;
|
||||
video.parentNode.replaceChild(replacement, video);
|
||||
}
|
||||
|
||||
function restore_video(obj, index) {
|
||||
obj = App.all_replacements[index];
|
||||
video = App.backup_videos[index];
|
||||
console.log(video);
|
||||
obj.parentNode.replaceChild(video, obj);
|
||||
}
|
||||
|
||||
/** force repainting the video, thanks to:
|
||||
* http://martinwolf.org/2014/06/10/force-repaint-of-an-element-with-javascript/
|
||||
*/
|
||||
var siteHeader = document.getElementById('header');
|
||||
siteHeader.style.display='none';
|
||||
siteHeader.offsetHeight; // no need to store this anywhere, the reference is enough
|
||||
siteHeader.style.display='block';
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
printjs() {
|
||||
js | sed 's,//.*$,,' | tr '\n' ' '
|
||||
}
|
||||
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
file_to_cast=${tmpdir}/qutecast
|
||||
|
||||
# kill any running instance of castnow
|
||||
pkill -f /usr/bin/castnow
|
||||
|
||||
# start youtube download in stream mode (-o -) into temporary file
|
||||
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}" | castnow -
|
||||
|
||||
# cleanup remaining background process and file on disk
|
||||
kill ${ytdl_pid}
|
||||
rm -rf ${tmpdir}
|
||||
@@ -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
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# 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
|
||||
|
||||
# default style to monokai if none is provided
|
||||
STYLE=${1:-monokai}
|
||||
# format json using jq
|
||||
FORMATTED_JSON="$(cat "$QUTE_TEXT" | jq '.')"
|
||||
|
||||
# if jq command failed or formatted json is empty, assume failure and terminate
|
||||
if [ $? -ne 0 ] || [ -z "$FORMATTED_JSON" ]; then
|
||||
echo "Invalid json, aborting..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# calculate the filesize of the json document
|
||||
FILE_SIZE=$(ls -s --block-size=1048576 "$QUTE_TEXT" | cut -d' ' -f1)
|
||||
|
||||
# use pygments to pretty-up the json (syntax highlight) if file is less than 10MB
|
||||
if [ "$FILE_SIZE" -lt "10" ]; then
|
||||
FORMATTED_JSON="$(echo "$FORMATTED_JSON" | pygmentize -l json -f html -O full,style=$STYLE)"
|
||||
fi
|
||||
|
||||
# create a temp file and write the formatted json to that file
|
||||
TEMP_FILE="$(mktemp --suffix '.html')"
|
||||
echo "$FORMATTED_JSON" > $TEMP_FILE
|
||||
|
||||
|
||||
# send the command to qutebrowser to open the new file containing the formatted json
|
||||
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.:
|
||||
@@ -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"
|
||||
}
|
||||
@@ -327,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++) {
|
||||
@@ -352,15 +341,11 @@ 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")) {
|
||||
input.focus();
|
||||
if (input.type == "text" || input.type == "email") {
|
||||
input.value = "$(javascript_escape "${username}")";
|
||||
input.blur();
|
||||
}
|
||||
if (input.type == "password") {
|
||||
input.focus();
|
||||
input.value = "$(javascript_escape "${password}")";
|
||||
input.blur();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -25,9 +24,9 @@
|
||||
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
|
||||
#
|
||||
|
||||
path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html)
|
||||
path=/tmp/qutebrowser_$(mktemp XXXXXXXX).html
|
||||
|
||||
curl "$QUTE_URL" > "$path"
|
||||
curl "$QUTE_URL" > $path
|
||||
urxvt -e vim "$path"
|
||||
|
||||
rm "$path"
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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
|
||||
|
||||
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())
|
||||
|
||||
with codecs.open(tmpfile, 'w', 'utf-8') as target:
|
||||
target.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')
|
||||
target.write(content)
|
||||
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write('open -t %s' % tmpfile)
|
||||
@@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Adds DuckDuckGo bang as searchengine.
|
||||
#
|
||||
# Usage:
|
||||
# :spawn --userscript ripbang [bang]...
|
||||
#
|
||||
# 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
|
||||
|
||||
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 = searchengine.replace('url=', '')
|
||||
searchengine = searchengine.replace('/l/?kh=-1&uddg=', '')
|
||||
searchengine = searchengine.replace('SEARCHTEXT', '{}')
|
||||
|
||||
if os.getenv('QUTE_FIFO'):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write('set searchengines %s %s' % (bang, searchengine))
|
||||
else:
|
||||
print('%s %s' % (bang, searchengine))
|
||||
@@ -1,122 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Copyright 2016 Jan Verbeek (blyxxyz) <ring@openmailbox.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# This script keeps track of URLs in RSS feeds and opens new ones.
|
||||
# New feeds can be added with ':spawn -u /path/to/userscripts/rss add' or
|
||||
# ':spawn -u /path/to/userscripts/rss <url>'.
|
||||
# New items can be opened with ':spawn -u /path/to/userscripts/rss'.
|
||||
# The script doesn't really parse XML, and searches for things that look like
|
||||
# item links. It might open things that aren't real links, and it might miss
|
||||
# real links.
|
||||
|
||||
config_dir="$HOME/.qute-rss"
|
||||
|
||||
add_feed () {
|
||||
touch "feeds"
|
||||
if grep -Fq "$1" "feeds"; then
|
||||
notice "$1 is saved already."
|
||||
else
|
||||
printf "%s\n" "$1" >> "feeds"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show an error message and exit
|
||||
fail () {
|
||||
echo "message-error '$*'" > "$QUTE_FIFO"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get a sorted list of item URLs from a RSS feed
|
||||
get_items () {
|
||||
$curl "$@" | grep "$text_only" -zo -e '<guid[^<>]*>[^<>]*</guid>' \
|
||||
-e '<link[^<>]*>[^<>]*</link>' \
|
||||
-e '<link[^<>]*href="[^"]*"' |
|
||||
grep "$text_only" -o 'http[^<>"]*' | sort | uniq
|
||||
}
|
||||
|
||||
# Show an info message
|
||||
notice () {
|
||||
echo "message-info '$*'" > "$QUTE_FIFO"
|
||||
}
|
||||
|
||||
# Update a database of a feed and open new URLs
|
||||
read_items () {
|
||||
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")"
|
||||
get_items "$1" > "$feed_temp_file"
|
||||
if [ ! -s "$feed_temp_file" ]; then
|
||||
notice "No items found for $1."
|
||||
rm "$feed_temp_file" "$feed_new_items"
|
||||
elif [ ! -f "$feed_file" ]; then
|
||||
notice "$1 is a new feed. All items will be marked as read."
|
||||
mv "$feed_temp_file" "$feed_file"
|
||||
rm "$feed_new_items"
|
||||
else
|
||||
sort -o "$feed_file" "$feed_file"
|
||||
comm -2 -3 "$feed_temp_file" "$feed_file" | tee "$feed_new_items"
|
||||
cat "$feed_new_items" >> "$feed_file"
|
||||
sort -o "$feed_file" "$feed_file"
|
||||
rm "$feed_temp_file" "$feed_new_items"
|
||||
fi | while read item; do
|
||||
echo "open -t $item" > "$QUTE_FIFO"
|
||||
done
|
||||
}
|
||||
|
||||
if [ ! -d "$config_dir/read_urls" ]; then
|
||||
notice "Creating configuration directory."
|
||||
mkdir -p "$config_dir/read_urls"
|
||||
fi
|
||||
|
||||
cd "$config_dir"
|
||||
|
||||
if [ $# != 0 ]; then
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "add" ]; then
|
||||
add_feed "$QUTE_URL"
|
||||
else
|
||||
add_feed "$arg"
|
||||
fi
|
||||
done
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ ! -f "feeds" ]; then
|
||||
fail "Add feeds by running ':spawn -u rss add' or ':spawn -u rss <url>'."
|
||||
fi
|
||||
|
||||
if curl --version >&-; then
|
||||
curl="curl -sL"
|
||||
elif wget --version >&-; then
|
||||
curl="wget -qO -"
|
||||
else
|
||||
fail "Either curl or wget is needed to run this script."
|
||||
fi
|
||||
|
||||
# Detect GNU grep so we can force it to treat everything as text
|
||||
if < /dev/null grep --help 2>&1 | grep -q -- -a; then
|
||||
text_only="-a"
|
||||
fi
|
||||
|
||||
while read feed_url; do
|
||||
read_items "$feed_url" &
|
||||
done < "$config_dir/feeds"
|
||||
|
||||
wait
|
||||
@@ -49,7 +49,7 @@ 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 --ytdl-raw-options=yes-playlist=}
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl }
|
||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
||||
|
||||
js() {
|
||||
@@ -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"
|
||||
|
||||
36
pytest.ini
@@ -1,13 +1,12 @@
|
||||
[pytest]
|
||||
addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
|
||||
testpaths = tests
|
||||
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail
|
||||
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.
|
||||
@@ -15,28 +14,15 @@ 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
|
||||
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
|
||||
qtwebengine_mac_xfail: Tests which fail on macOS 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
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
flaky_once: Try to rerun this test once if it fails
|
||||
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"
|
||||
^propsReply "Method \\"GetAll\\" with signature \\"s\\" on interface \\"org\.freedesktop\.DBus\.Properties\\" doesn't exist\\n"
|
||||
^nmReply "Method \\"GetDevices\\" with signature \\"\\" on interface \\"org\.freedesktop\.NetworkManager\\" doesn't exist\\n"
|
||||
^"Object path cannot be empty"
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSL write failed with error: -9805
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSLRead failed with: -9805
|
||||
^Type conversion already registered from type .*
|
||||
@@ -44,15 +30,11 @@ 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 .*
|
||||
^QXcbClipboard: SelectionRequest too old
|
||||
^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 *
|
||||
^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"
|
||||
qt_wait_signal_raising = true
|
||||
xfail_strict = true
|
||||
|
||||
@@ -8,4 +8,3 @@ 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;
|
||||
Keywords=Browser
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -17,17 +17,19 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""A keyboard-driven, vim-like browser based on PyQt5."""
|
||||
# pylint: disable=line-too-long
|
||||
|
||||
"""A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."""
|
||||
|
||||
import os.path
|
||||
|
||||
__author__ = "Florian Bruhin"
|
||||
__copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
|
||||
__copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 0, 3)
|
||||
__version_info__ = (0, 8, 2)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."
|
||||
|
||||
basedir = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 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
|
||||
@@ -49,9 +32,10 @@ import datetime
|
||||
import tokenize
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QWindow
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QCursor, QWindow
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
QObject, QEvent, pyqtSignal)
|
||||
QObject, Qt, QEvent, pyqtSignal)
|
||||
try:
|
||||
import hunter
|
||||
except ImportError:
|
||||
@@ -59,27 +43,19 @@ 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.browser import (urlmarks, adblock, history, browsertab,
|
||||
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
|
||||
from qutebrowser.config import style, config, websettings, configexc
|
||||
from qutebrowser.browser import urlmarks, adblock
|
||||
from qutebrowser.browser.webkit import cookies, cache, history, downloads
|
||||
from qutebrowser.browser.webkit.network import (qutescheme, proxy,
|
||||
networkmanager)
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.misc import readline, ipc, savemanager, sessions, crashsignal
|
||||
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
|
||||
@@ -87,18 +63,16 @@ qApp = None
|
||||
|
||||
def run(args):
|
||||
"""Initialize everything and run the application."""
|
||||
if args.version:
|
||||
print(version.version())
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
|
||||
if args.temp_basedir:
|
||||
args.basedir = tempfile.mkdtemp(prefix='qutebrowser-basedir-')
|
||||
|
||||
quitter = Quitter(args)
|
||||
objreg.register('quitter', quitter)
|
||||
|
||||
log.init.debug("Initializing directories...")
|
||||
standarddir.init(args)
|
||||
|
||||
log.init.debug("Initializing config...")
|
||||
configinit.early_init(args)
|
||||
|
||||
global qApp
|
||||
qApp = Application(args)
|
||||
qApp.setOrganizationName("qutebrowser")
|
||||
@@ -106,10 +80,6 @@ def run(args):
|
||||
qApp.setApplicationVersion(qutebrowser.__version__)
|
||||
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
|
||||
|
||||
if args.version:
|
||||
print(version.version())
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
|
||||
crash_handler = crashsignal.CrashHandler(
|
||||
app=qApp, quitter=quitter, args=args, parent=qApp)
|
||||
crash_handler.activate()
|
||||
@@ -158,30 +128,33 @@ 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)
|
||||
|
||||
QTimer.singleShot(0, functools.partial(_process_args, args))
|
||||
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
|
||||
|
||||
log.init.debug("Initializing eventfilter...")
|
||||
event_filter = EventFilter(qApp)
|
||||
qApp.installEventFilter(event_filter)
|
||||
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)
|
||||
qApp.focusChanged.connect(message.on_focus_changed)
|
||||
|
||||
QDesktopServices.setUrlHandler('http', open_desktopservices_url)
|
||||
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
|
||||
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
|
||||
|
||||
objreg.get('web-history').import_txt()
|
||||
|
||||
log.init.debug("Init done!")
|
||||
crash_handler.raise_crashdlg()
|
||||
|
||||
@@ -193,35 +166,37 @@ 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('current', "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)
|
||||
|
||||
delta = datetime.datetime.now() - earlyinit.START_TIME
|
||||
log.init.debug("Init finished after {}s".format(delta.total_seconds()))
|
||||
_open_quickstart(args)
|
||||
|
||||
|
||||
def _load_session(name):
|
||||
@@ -230,25 +205,24 @@ def _load_session(name):
|
||||
Args:
|
||||
name: The name of the session to load, or None to read state file.
|
||||
"""
|
||||
session_manager = objreg.get('session-manager')
|
||||
if name is None and session_manager.exists('_autosave'):
|
||||
name = '_autosave'
|
||||
elif name is None:
|
||||
state_config = objreg.get('state-config')
|
||||
if 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
|
||||
return
|
||||
|
||||
session_manager = objreg.get('session-manager')
|
||||
try:
|
||||
session_manager.load(name)
|
||||
except sessions.SessionNotFoundError:
|
||||
message.error("Session {} not found!".format(name))
|
||||
message.error('current', "Session {} not found!".format(name))
|
||||
except sessions.SessionError as e:
|
||||
message.error("Failed to load session {}: {}".format(name, e))
|
||||
message.error('current', "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.
|
||||
@@ -280,7 +254,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)
|
||||
@@ -288,39 +262,22 @@ 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:
|
||||
url = urlutils.fuzzy_url(cmd, cwd, relative=True)
|
||||
except urlutils.InvalidUrlError as e:
|
||||
message.error("Error in startup argument '{}': {}".format(
|
||||
cmd, e))
|
||||
message.error('current', "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):
|
||||
@@ -336,70 +293,65 @@ 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.count() == 0:
|
||||
log.init.debug("Opening start pages")
|
||||
for url in config.val.url.start_pages:
|
||||
tabbed_browser.tabopen(url)
|
||||
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('current', "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.
|
||||
if args.datadir is not None or args.basedir is not None:
|
||||
# With --datadir or --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'
|
||||
QUrl('http://www.qutebrowser.org/quickstart.html'))
|
||||
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):
|
||||
"""Register currently focused main window in the object registry."""
|
||||
if new is None:
|
||||
return
|
||||
|
||||
if not isinstance(new, QWidget):
|
||||
log.misc.debug("on_focus_changed called with non-QWidget {!r}".format(
|
||||
new))
|
||||
return
|
||||
|
||||
window = new.window()
|
||||
if isinstance(window, mainwindow.MainWindow):
|
||||
objreg.register('last-focused-main-window', window, update=True)
|
||||
# A focused window must also be visible, and in this case we should
|
||||
# consider it as the most recently looked-at window
|
||||
objreg.register('last-visible-main-window', window, update=True)
|
||||
if new is None or not isinstance(new, mainwindow.MainWindow):
|
||||
try:
|
||||
objreg.delete('last-focused-main-window')
|
||||
except KeyError:
|
||||
pass
|
||||
qApp.restoreOverrideCursor()
|
||||
else:
|
||||
objreg.register('last-focused-main-window', new.window(), update=True)
|
||||
_maybe_hide_mouse_cursor()
|
||||
|
||||
|
||||
def open_desktopservices_url(url):
|
||||
@@ -410,6 +362,17 @@ def open_desktopservices_url(url):
|
||||
tabbed_browser.tabopen(url)
|
||||
|
||||
|
||||
@config.change_filter('ui', 'hide-mouse-cursor', function=True)
|
||||
def _maybe_hide_mouse_cursor():
|
||||
"""Hide the mouse cursor if it isn't yet and it's configured."""
|
||||
if config.get('ui', 'hide-mouse-cursor'):
|
||||
if qApp.overrideCursor() is not None:
|
||||
return
|
||||
qApp.setOverrideCursor(QCursor(Qt.BlankCursor))
|
||||
else:
|
||||
qApp.restoreOverrideCursor()
|
||||
|
||||
|
||||
def _init_modules(args, crash_handler):
|
||||
"""Initialize all 'modules' which need to be initialized.
|
||||
|
||||
@@ -421,81 +384,77 @@ def _init_modules(args, crash_handler):
|
||||
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()
|
||||
|
||||
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 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 directories...")
|
||||
standarddir.init(args)
|
||||
log.init.debug("Initializing config...")
|
||||
config.init(qApp)
|
||||
save_manager.init_autosave()
|
||||
log.init.debug("Initializing web history...")
|
||||
history.init(qApp)
|
||||
log.init.debug("Initializing crashlog...")
|
||||
if not args.no_err_windows:
|
||||
crash_handler.handle_segfault()
|
||||
|
||||
log.init.debug("Initializing sessions...")
|
||||
sessions.init(qApp)
|
||||
|
||||
log.init.debug("Initializing js-bridge...")
|
||||
js_bridge = qutescheme.JSBridge(qApp)
|
||||
objreg.register('js-bridge', js_bridge)
|
||||
log.init.debug("Initializing websettings...")
|
||||
websettings.init(args)
|
||||
|
||||
websettings.init()
|
||||
log.init.debug("Initializing adblock...")
|
||||
host_blocker = adblock.HostBlocker()
|
||||
host_blocker.read_hosts()
|
||||
objreg.register('host-blocker', host_blocker)
|
||||
|
||||
log.init.debug("Initializing quickmarks...")
|
||||
quickmark_manager = urlmarks.QuickmarkManager(qApp)
|
||||
objreg.register('quickmark-manager', quickmark_manager)
|
||||
|
||||
log.init.debug("Initializing bookmarks...")
|
||||
bookmark_manager = urlmarks.BookmarkManager(qApp)
|
||||
objreg.register('bookmark-manager', bookmark_manager)
|
||||
|
||||
log.init.debug("Initializing proxy...")
|
||||
proxy.init()
|
||||
log.init.debug("Initializing cookies...")
|
||||
cookie_jar = cookies.CookieJar(qApp)
|
||||
ram_cookie_jar = cookies.RAMCookieJar(qApp)
|
||||
objreg.register('cookie-jar', cookie_jar)
|
||||
objreg.register('ram-cookie-jar', ram_cookie_jar)
|
||||
|
||||
log.init.debug("Initializing cache...")
|
||||
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
|
||||
objreg.register('cache', diskcache)
|
||||
|
||||
log.init.debug("Initializing completions...")
|
||||
completionmodels.init()
|
||||
log.init.debug("Misc initialization...")
|
||||
macros.init()
|
||||
# Init backend-specific stuff
|
||||
browsertab.init()
|
||||
if config.get('ui', 'hide-wayland-decoration'):
|
||||
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
|
||||
else:
|
||||
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
|
||||
_maybe_hide_mouse_cursor()
|
||||
objreg.get('config').changed.connect(_maybe_hide_mouse_cursor)
|
||||
temp_downloads = downloads.TempDownloadManager(qApp)
|
||||
objreg.register('temporary-downloads', temp_downloads)
|
||||
|
||||
|
||||
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:
|
||||
@@ -540,13 +499,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.
|
||||
@@ -567,7 +525,7 @@ class Quitter:
|
||||
if not os.path.isdir(cwd):
|
||||
# Probably running from a python egg. Let's fallback to
|
||||
# cwd=None and see if that works out.
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/323
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/323
|
||||
cwd = None
|
||||
|
||||
# Add all open pages so they get reopened.
|
||||
@@ -593,12 +551,8 @@ class Quitter:
|
||||
argdict['session'] = session
|
||||
argdict['override_restore'] = False
|
||||
# Ensure :restart works with --temp-basedir
|
||||
if self._args.temp_basedir:
|
||||
argdict['temp_basedir'] = False
|
||||
argdict['temp_basedir_restarted'] = True
|
||||
|
||||
if override_args is not None:
|
||||
argdict.update(override_args)
|
||||
argdict['temp_basedir'] = False
|
||||
argdict['temp_basedir_restarted'] = True
|
||||
|
||||
# Dump the data
|
||||
data = json.dumps(argdict)
|
||||
@@ -624,7 +578,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
|
||||
@@ -637,7 +591,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.
|
||||
@@ -647,19 +600,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:
|
||||
@@ -670,25 +617,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.
|
||||
@@ -705,16 +635,22 @@ 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)
|
||||
|
||||
if prompt.prompt_queue.shutdown():
|
||||
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)
|
||||
|
||||
deferrer = False
|
||||
for win_id in objreg.window_registry:
|
||||
prompter = objreg.get('prompter', None, scope='window',
|
||||
window=win_id)
|
||||
if prompter is not None and prompter.shutdown():
|
||||
deferrer = True
|
||||
if deferrer:
|
||||
# If shutdown was called while we were asking a question, we're in
|
||||
# a still sub-eventloop (which gets quit now) and not in the main
|
||||
# one.
|
||||
@@ -729,7 +665,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:
|
||||
@@ -738,16 +674,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
|
||||
@@ -765,7 +699,9 @@ class Quitter:
|
||||
e, self._args, "Error while saving!",
|
||||
pre_text="Error while saving {}".format(key))
|
||||
# Disable storage so removing tempdir will work
|
||||
websettings.shutdown()
|
||||
QWebSettings.setIconDatabasePath('')
|
||||
QWebSettings.setOfflineWebApplicationCachePath('')
|
||||
QWebSettings.globalSettings().setLocalStoragePath('')
|
||||
# Re-enable faulthandler to stdout, then remove crash log
|
||||
log.destroy.debug("Deactivating crash log...")
|
||||
objreg.get('crash-handler').destroy_crashlogfile()
|
||||
@@ -775,20 +711,27 @@ class Quitter:
|
||||
atexit.register(shutil.rmtree, self._args.basedir,
|
||||
ignore_errors=True)
|
||||
# Delete temp download dir
|
||||
downloads.temp_download_manager.cleanup()
|
||||
objreg.get('temporary-downloads').cleanup()
|
||||
# If we don't kill our custom handler here we might get segfaults
|
||||
log.destroy.debug("Deactivating message handler...")
|
||||
qInstallMessageHandler(None)
|
||||
# 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()
|
||||
# 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):
|
||||
|
||||
@@ -796,7 +739,6 @@ class Application(QApplication):
|
||||
|
||||
Attributes:
|
||||
_args: ArgumentParser instance.
|
||||
_last_focus_object: The last focused object's repr.
|
||||
"""
|
||||
|
||||
new_window = pyqtSignal(mainwindow.MainWindow)
|
||||
@@ -807,9 +749,7 @@ class Application(QApplication):
|
||||
Args:
|
||||
Argument namespace from argparse.
|
||||
"""
|
||||
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)
|
||||
|
||||
@@ -825,19 +765,7 @@ class Application(QApplication):
|
||||
@pyqtSlot(QObject)
|
||||
def on_focus_object_changed(self, obj):
|
||||
"""Log when the focus object changed."""
|
||||
output = repr(obj)
|
||||
if self._last_focus_object != output:
|
||||
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:
|
||||
open_url(e.url(), no_raise=True)
|
||||
else:
|
||||
return super().event(e)
|
||||
|
||||
return True
|
||||
log.misc.debug("Focus object changed: {!r}".format(obj))
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
@@ -845,7 +773,7 @@ class Application(QApplication):
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -26,10 +26,9 @@ import posixpath
|
||||
import zipfile
|
||||
import fnmatch
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, standarddir, log, message
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.utils import objreg, standarddir, log, message, usertypes
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
|
||||
|
||||
def guess_zip_filename(zf):
|
||||
@@ -58,7 +57,7 @@ def get_fileobj(byte_io):
|
||||
byte_io = zf.open(filename, mode='r')
|
||||
else:
|
||||
byte_io.seek(0) # rewind what zipfile.is_zipfile did
|
||||
return byte_io
|
||||
return io.TextIOWrapper(byte_io, encoding='utf-8')
|
||||
|
||||
|
||||
def is_whitelisted_host(host):
|
||||
@@ -67,7 +66,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
|
||||
@@ -109,17 +112,23 @@ class HostBlocker:
|
||||
self._done_count = 0
|
||||
|
||||
data_dir = standarddir.data()
|
||||
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
|
||||
self._update_files()
|
||||
if data_dir is None:
|
||||
self._local_hosts_file = None
|
||||
else:
|
||||
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
|
||||
self.on_config_changed()
|
||||
|
||||
config_dir = standarddir.config()
|
||||
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
|
||||
if config_dir is None:
|
||||
self._config_hosts_file = None
|
||||
else:
|
||||
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
|
||||
@@ -136,14 +145,14 @@ class HostBlocker:
|
||||
Return:
|
||||
True if a read was attempted, False otherwise
|
||||
"""
|
||||
if not os.path.exists(filename):
|
||||
if filename is None or not os.path.exists(filename):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
target.add(line.strip())
|
||||
except (OSError, UnicodeDecodeError):
|
||||
except OSError:
|
||||
log.misc.exception("Failed to read host blocklist!")
|
||||
|
||||
return True
|
||||
@@ -152,6 +161,9 @@ class HostBlocker:
|
||||
"""Read hosts from the existing blocked-hosts file."""
|
||||
self._blocked_hosts = set()
|
||||
|
||||
if self._local_hosts_file is None:
|
||||
return
|
||||
|
||||
self._read_hosts_file(self._config_hosts_file,
|
||||
self._config_blocked_hosts)
|
||||
|
||||
@@ -160,32 +172,37 @@ 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):
|
||||
message.info("Run :adblock-update to get adblock lists.")
|
||||
if (config.get('content', 'host-block-lists') is not None and
|
||||
args.basedir is None):
|
||||
message.info('current',
|
||||
"Run :adblock-update to get adblock lists.")
|
||||
|
||||
@cmdutils.register(instance='host-blocker')
|
||||
def adblock_update(self):
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def adblock_update(self, win_id):
|
||||
"""Update the adblock block lists.
|
||||
|
||||
This updates `~/.local/share/qutebrowser/blocked-hosts` with downloaded
|
||||
host lists and re-reads `~/.config/qutebrowser/blocked-hosts`.
|
||||
This updates ~/.local/share/qutebrowser/blocked-hosts with downloaded
|
||||
host lists and re-reads ~/.config/qutebrowser/blocked-hosts.
|
||||
"""
|
||||
self._read_hosts_file(self._config_hosts_file,
|
||||
self._config_blocked_hosts)
|
||||
if self._local_hosts_file is None:
|
||||
raise cmdexc.CommandError("No data storage is configured!")
|
||||
self._blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window='last-focused')
|
||||
for url in config.val.content.host_blocking.lists:
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
download_manager = objreg.get('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))
|
||||
message.error(win_id, "adblock: Error while reading {}: "
|
||||
"{}".format(url.path(), e.strerror))
|
||||
continue
|
||||
download = FakeDownload(fileobj)
|
||||
self._in_progress.append(download)
|
||||
@@ -193,61 +210,13 @@ class HostBlocker:
|
||||
else:
|
||||
fobj = io.BytesIO()
|
||||
fobj.name = 'adblock: ' + url.host()
|
||||
target = downloads.FileObjDownloadTarget(fobj)
|
||||
target = usertypes.FileObjDownloadTarget(fobj)
|
||||
download = download_manager.get(url, target=target,
|
||||
auto_remove=True)
|
||||
self._in_progress.append(download)
|
||||
download.finished.connect(
|
||||
functools.partial(self.on_download_finished, download))
|
||||
|
||||
def _parse_line(self, line):
|
||||
"""Parse a line from a host file.
|
||||
|
||||
Args:
|
||||
line: The bytes object to parse.
|
||||
|
||||
Returns:
|
||||
True if parsing succeeded, False otherwise.
|
||||
"""
|
||||
if line.startswith(b'#'):
|
||||
# Ignoring comments early so we don't have to care about
|
||||
# encoding errors in them.
|
||||
return True
|
||||
|
||||
try:
|
||||
line = line.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
log.misc.error("Failed to decode: {!r}".format(line))
|
||||
return False
|
||||
|
||||
# Remove comments
|
||||
try:
|
||||
hash_idx = line.index('#')
|
||||
line = line[:hash_idx]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
line = line.strip()
|
||||
# Skip empty lines
|
||||
if not line:
|
||||
return True
|
||||
|
||||
parts = line.split()
|
||||
if len(parts) == 1:
|
||||
# "one host per line" format
|
||||
host = parts[0]
|
||||
elif len(parts) == 2:
|
||||
# /etc/hosts format
|
||||
host = parts[1]
|
||||
else:
|
||||
log.misc.error("Failed to parse: {!r}".format(line))
|
||||
return False
|
||||
|
||||
if host not in self.WHITELISTED:
|
||||
self._blocked_hosts.add(host)
|
||||
|
||||
return True
|
||||
|
||||
def _merge_file(self, byte_io):
|
||||
"""Read and merge host files.
|
||||
|
||||
@@ -261,21 +230,38 @@ class HostBlocker:
|
||||
line_count = 0
|
||||
try:
|
||||
f = get_fileobj(byte_io)
|
||||
except (OSError, zipfile.BadZipFile, zipfile.LargeZipFile,
|
||||
LookupError) as e:
|
||||
message.error("adblock: Error while reading {}: {} - {}".format(
|
||||
byte_io.name, e.__class__.__name__, e))
|
||||
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
|
||||
zipfile.LargeZipFile) as e:
|
||||
message.error('current', "adblock: Error while reading {}: {} - "
|
||||
"{}".format(byte_io.name, e.__class__.__name__, e))
|
||||
return
|
||||
|
||||
for line in f:
|
||||
line_count += 1
|
||||
ok = self._parse_line(line)
|
||||
if not ok:
|
||||
# Remove comments
|
||||
try:
|
||||
hash_idx = line.index('#')
|
||||
line = line[:hash_idx]
|
||||
except ValueError:
|
||||
pass
|
||||
line = line.strip()
|
||||
# Skip empty lines
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) == 1:
|
||||
# "one host per line" format
|
||||
host = parts[0]
|
||||
elif len(parts) == 2:
|
||||
# /etc/hosts format
|
||||
host = parts[1]
|
||||
else:
|
||||
error_count += 1
|
||||
|
||||
continue
|
||||
if host not in self.WHITELISTED:
|
||||
self._blocked_hosts.add(host)
|
||||
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
|
||||
if error_count > 0:
|
||||
message.error("adblock: {} read errors for {}".format(
|
||||
message.error('current', "adblock: {} read errors for {}".format(
|
||||
error_count, byte_io.name))
|
||||
|
||||
def on_lists_downloaded(self):
|
||||
@@ -283,13 +269,14 @@ class HostBlocker:
|
||||
with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
|
||||
for host in sorted(self._blocked_hosts):
|
||||
f.write(host + '\n')
|
||||
message.info("adblock: Read {} hosts from {} sources.".format(
|
||||
len(self._blocked_hosts), self._done_count))
|
||||
message.info('current', "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 and self._local_hosts_file is not None:
|
||||
try:
|
||||
os.remove(self._local_hosts_file)
|
||||
except FileNotFoundError:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,47 +21,35 @@
|
||||
|
||||
import itertools
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QPoint
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QApplication
|
||||
from PyQt5.QtWidgets import QWidget, QLayout
|
||||
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, objreg, usertypes, log, qtutils
|
||||
from qutebrowser.misc import miscwidgets, objects
|
||||
from qutebrowser.browser import mouse, hints
|
||||
from qutebrowser.utils import utils, objreg, usertypes, message, log, qtutils
|
||||
|
||||
|
||||
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
|
||||
# argument and to avoid circular imports.
|
||||
mode_manager = modeman.instance(win_id)
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
if objreg.get('args').backend == 'webengine':
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
tab_class = webenginetab.WebEngineTab
|
||||
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)
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize backend-specific modules."""
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
from qutebrowser.browser.webengine import webenginetab
|
||||
webenginetab.init()
|
||||
return tab_class(win_id=win_id, mode_manager=mode_manager, parent=parent)
|
||||
|
||||
|
||||
class WebTabError(Exception):
|
||||
@@ -69,21 +57,35 @@ class WebTabError(Exception):
|
||||
"""Base class for various errors."""
|
||||
|
||||
|
||||
class UnsupportedOperationError(WebTabError):
|
||||
class WrapperLayout(QLayout):
|
||||
|
||||
"""Raised when an operation is not supported with the given backend."""
|
||||
"""A Qt layout which simply wraps a single widget.
|
||||
|
||||
This is used so the widget is hidden behind a AbstractTab API and can't
|
||||
easily be accidentally accessed.
|
||||
"""
|
||||
|
||||
def __init__(self, widget, parent=None):
|
||||
super().__init__(parent)
|
||||
self._widget = widget
|
||||
|
||||
def addItem(self, _widget):
|
||||
raise AssertionError("Should never be called!")
|
||||
|
||||
def sizeHint(self):
|
||||
return self._widget.sizeHint()
|
||||
|
||||
def itemAt(self, _index): # pragma: no cover
|
||||
# For some reason this sometimes gets called by Qt.
|
||||
return None
|
||||
|
||||
def takeAt(self, _index):
|
||||
raise AssertionError("Should never be called!")
|
||||
|
||||
def setGeometry(self, rect):
|
||||
self._widget.setGeometry(rect)
|
||||
|
||||
|
||||
TerminationStatus = usertypes.enum('TerminationStatus', [
|
||||
'normal',
|
||||
'abnormal', # non-zero exit status
|
||||
'crashed', # e.g. segfault
|
||||
'killed',
|
||||
'unknown',
|
||||
])
|
||||
|
||||
|
||||
@attr.s
|
||||
class TabData:
|
||||
|
||||
"""A simple namespace with a fixed set of attributes.
|
||||
@@ -93,49 +95,14 @@ class TabData:
|
||||
load.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
viewing_source: Set if we're currently showing a source view.
|
||||
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.
|
||||
"""
|
||||
|
||||
keep_icon = attr.ib(False)
|
||||
viewing_source = attr.ib(False)
|
||||
inspector = attr.ib(None)
|
||||
override_target = attr.ib(None)
|
||||
pinned = attr.ib(False)
|
||||
fullscreen = attr.ib(False)
|
||||
|
||||
|
||||
class AbstractAction:
|
||||
|
||||
"""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
|
||||
__slots__ = ['keep_icon', 'viewing_source', 'inspector']
|
||||
|
||||
def __init__(self):
|
||||
self._widget = None
|
||||
|
||||
def exit_fullscreen(self):
|
||||
"""Exit the fullscreen mode."""
|
||||
raise NotImplementedError
|
||||
|
||||
def save_page(self):
|
||||
"""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)
|
||||
self.keep_icon = False
|
||||
self.viewing_source = False
|
||||
self.inspector = None
|
||||
|
||||
|
||||
class AbstractPrinting:
|
||||
@@ -151,20 +118,10 @@ class AbstractPrinting:
|
||||
def check_printer_support(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def check_preview_support(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_pdf(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_printer(self, printer, callback=None):
|
||||
"""Print the tab.
|
||||
|
||||
Args:
|
||||
printer: The QPrinter to print to.
|
||||
callback: Called with a boolean
|
||||
(True if printing succeeded, False otherwise)
|
||||
"""
|
||||
def to_printer(self, printer):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -174,8 +131,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.
|
||||
"""
|
||||
@@ -184,30 +139,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.
|
||||
"""
|
||||
@@ -249,30 +188,30 @@ class AbstractZoom(QObject):
|
||||
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
|
||||
# # when the WebView is destroyed on older PyQt versions.
|
||||
# # See https://github.com/qutebrowser/qutebrowser/issues/390
|
||||
# # See https://github.com/The-Compiler/qutebrowser/issues/390
|
||||
# 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.
|
||||
@@ -287,9 +226,6 @@ class AbstractZoom(QObject):
|
||||
self.set_factor(float(level) / 100, fuzzyval=False)
|
||||
return level
|
||||
|
||||
def _set_factor_internal(self, factor):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_factor(self, factor, *, fuzzyval=True):
|
||||
"""Zoom to a given zoom factor.
|
||||
|
||||
@@ -301,21 +237,28 @@ 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)
|
||||
default_zoom = config.get('ui', 'default-zoom')
|
||||
self._set_factor_internal(float(default_zoom) / 100)
|
||||
|
||||
def set_current(self):
|
||||
self._set_factor_internal(self._zoom_factor)
|
||||
@pyqtSlot(QPoint)
|
||||
def _on_mouse_wheel_zoom(self, delta):
|
||||
"""Handle zooming via mousewheel requested by the web view."""
|
||||
divider = config.get('input', 'mouse-zoom-divider')
|
||||
factor = self.factor() + delta.y() / divider
|
||||
if factor < 0:
|
||||
return
|
||||
perc = int(100 * factor)
|
||||
message.info(self._win_id, "Zoom level: {}%".format(perc))
|
||||
self._neighborlist.fuzzyval = perc
|
||||
self._set_factor_internal(factor)
|
||||
self._default_zoom_changed = True
|
||||
|
||||
|
||||
class AbstractCaret(QObject):
|
||||
@@ -408,12 +351,6 @@ class AbstractScroller(QObject):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
self.perc_changed.connect(self._log_scroll_pos_change)
|
||||
|
||||
@pyqtSlot()
|
||||
def _log_scroll_pos_change(self):
|
||||
log.webview.vdebug("Scroll position changed to {}".format(
|
||||
self.pos_px()))
|
||||
|
||||
def _init_widget(self, widget):
|
||||
self._widget = widget
|
||||
@@ -484,21 +421,11 @@ class AbstractHistory:
|
||||
def current_idx(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def back(self, count=1):
|
||||
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):
|
||||
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
|
||||
@@ -506,12 +433,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
|
||||
@@ -525,55 +446,6 @@ class AbstractHistory:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractElements:
|
||||
|
||||
"""Finding and handling of elements on the page."""
|
||||
|
||||
def __init__(self, tab):
|
||||
self._widget = None
|
||||
self._tab = tab
|
||||
|
||||
def find_css(self, selector, callback, *, only_visible=False):
|
||||
"""Find all HTML elements matching a given selector async.
|
||||
|
||||
Args:
|
||||
callback: The callback to be called when the search finished.
|
||||
selector: The CSS selector to search for.
|
||||
only_visible: Only show elements which are visible on screen.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def find_id(self, elem_id, callback):
|
||||
"""Find the HTML element with the given ID async.
|
||||
|
||||
Args:
|
||||
callback: The callback to be called when the search finished.
|
||||
elem_id: The ID to search for.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def find_focused(self, callback):
|
||||
"""Find the focused element on the page async.
|
||||
|
||||
Args:
|
||||
callback: The callback to be called when the search finished.
|
||||
Called with a WebEngineElement or None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def find_at_pos(self, pos, callback):
|
||||
"""Find the element at the given position async.
|
||||
|
||||
This is also called "hit test" elsewhere.
|
||||
|
||||
Args:
|
||||
pos: The QPoint to get the element for.
|
||||
callback: The callback to be called when the search finished.
|
||||
Called with a WebEngineElement or None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractTab(QWidget):
|
||||
|
||||
"""A wrapper over the given widget to hide its API and expose another one.
|
||||
@@ -583,7 +455,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.
|
||||
@@ -598,13 +469,6 @@ class AbstractTab(QWidget):
|
||||
new_tab_requested: Emitted when a new tab should be opened with the
|
||||
given URL.
|
||||
load_status_changed: The loading status changed
|
||||
fullscreen_requested: Fullscreen display was requested by the page.
|
||||
arg: True if fullscreen should be turned on,
|
||||
False if it should be turned off.
|
||||
renderer_process_terminated: Emitted when the underlying renderer
|
||||
process terminated.
|
||||
arg 0: A TerminationStatus member.
|
||||
arg 1: The exit code.
|
||||
"""
|
||||
|
||||
window_close_requested = pyqtSignal()
|
||||
@@ -618,13 +482,8 @@ class AbstractTab(QWidget):
|
||||
new_tab_requested = pyqtSignal(QUrl)
|
||||
url_changed = pyqtSignal(QUrl)
|
||||
shutting_down = pyqtSignal()
|
||||
contents_size_changed = pyqtSignal(QSizeF)
|
||||
add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title
|
||||
fullscreen_requested = pyqtSignal(bool)
|
||||
renderer_process_terminated = pyqtSignal(TerminationStatus, int)
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
self.private = private
|
||||
def __init__(self, win_id, parent=None):
|
||||
self.win_id = win_id
|
||||
self.tab_id = next(tab_id_gen)
|
||||
super().__init__(parent)
|
||||
@@ -637,49 +496,32 @@ class AbstractTab(QWidget):
|
||||
|
||||
# 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.caret = AbstractCaret(win_id=win_id, tab=self, 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._layout = None
|
||||
self._widget = None
|
||||
self._progress = 0
|
||||
self._has_ssl_errors = False
|
||||
self._mode_manager = mode_manager
|
||||
self._load_status = usertypes.LoadStatus.none
|
||||
self._mouse_event_filter = mouse.MouseEventFilter(
|
||||
self, parent=self)
|
||||
self.backend = None
|
||||
|
||||
# FIXME:qtwebengine Should this be public api via self.hints?
|
||||
# Also, should we get it out of objreg?
|
||||
hintmanager = hints.HintManager(win_id, self.tab_id, parent=self)
|
||||
objreg.register('hintmanager', hintmanager, scope='tab',
|
||||
window=self.win_id, tab=self.tab_id)
|
||||
|
||||
def _set_widget(self, widget):
|
||||
# pylint: disable=protected-access
|
||||
self._layout = WrapperLayout(widget, self)
|
||||
self._widget = widget
|
||||
self._layout.wrap(self, widget)
|
||||
self.history._history = widget.history()
|
||||
self.scroller._init_widget(widget)
|
||||
self.caret._widget = widget
|
||||
self.zoom._widget = widget
|
||||
self.search._widget = widget
|
||||
self.printing._widget = widget
|
||||
self.action._widget = widget
|
||||
self.elements._widget = widget
|
||||
|
||||
self._install_event_filter()
|
||||
self.zoom.set_default()
|
||||
|
||||
def _install_event_filter(self):
|
||||
raise NotImplementedError
|
||||
widget.mouse_wheel_zoom.connect(self.zoom._on_mouse_wheel_zoom)
|
||||
widget.setParent(self)
|
||||
self.setFocusProxy(widget)
|
||||
|
||||
def _set_load_status(self, val):
|
||||
"""Setter for load_status."""
|
||||
@@ -689,25 +531,6 @@ class AbstractTab(QWidget):
|
||||
self._load_status = val
|
||||
self.load_status_changed.emit(val.name)
|
||||
|
||||
def event_target(self):
|
||||
"""Return the widget events should be sent to."""
|
||||
raise NotImplementedError
|
||||
|
||||
def send_event(self, evt):
|
||||
"""Send the given event to the underlying widget.
|
||||
|
||||
The event will be sent via QApplication.postEvent.
|
||||
Note that a posted event may not be re-used in any way!
|
||||
"""
|
||||
# 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 AssertionError("Can't re-use an event which was already "
|
||||
"posted!")
|
||||
recipient = self.event_target()
|
||||
evt.posted = True
|
||||
QApplication.postEvent(recipient, evt)
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_url_changed(self, url):
|
||||
"""Update title when URL has changed and no title is available."""
|
||||
@@ -723,36 +546,14 @@ class AbstractTab(QWidget):
|
||||
self._set_load_status(usertypes.LoadStatus.loading)
|
||||
self.load_started.emit()
|
||||
|
||||
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:
|
||||
return
|
||||
|
||||
cur_mode = self._mode_manager.mode
|
||||
if cur_mode == usertypes.KeyMode.insert:
|
||||
return
|
||||
|
||||
def _auto_insert_mode_cb(elem):
|
||||
"""Called from JS after finding the focused element."""
|
||||
if elem is None:
|
||||
log.webview.debug("No focused element!")
|
||||
return
|
||||
if elem.is_editable():
|
||||
modeman.enter(self.win_id, usertypes.KeyMode.insert,
|
||||
'load finished', only_if_normal=True)
|
||||
|
||||
self.elements.find_focused(_auto_insert_mode_cb)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok):
|
||||
sess_manager = objreg.get('session-manager')
|
||||
sess_manager.save_autosave()
|
||||
|
||||
if ok and not self._has_ssl_errors:
|
||||
if self.url().scheme() == 'https':
|
||||
self._set_load_status(usertypes.LoadStatus.success_https)
|
||||
else:
|
||||
self._set_load_status(usertypes.LoadStatus.success)
|
||||
|
||||
elif ok:
|
||||
self._set_load_status(usertypes.LoadStatus.warn)
|
||||
else:
|
||||
@@ -761,13 +562,6 @@ class AbstractTab(QWidget):
|
||||
if not self.title():
|
||||
self.title_changed.emit(self.url().toDisplayString())
|
||||
|
||||
self.zoom.set_current()
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_history_trigger(self):
|
||||
"""Emit add_history_item when triggered by backend-specific signal."""
|
||||
raise NotImplementedError
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _on_load_progress(self, perc):
|
||||
self._progress = perc
|
||||
@@ -777,7 +571,7 @@ class AbstractTab(QWidget):
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
def url(self, requested=False):
|
||||
def url(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def progress(self):
|
||||
@@ -802,10 +596,6 @@ 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 to a file ascync.
|
||||
|
||||
@@ -814,17 +604,19 @@ class AbstractTab(QWidget):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def run_js_async(self, code, callback=None, *, world=None):
|
||||
def run_js_async(self, code, callback=None):
|
||||
"""Run javascript async.
|
||||
|
||||
The given callback will be called with the result when running JS is
|
||||
complete.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
Args:
|
||||
code: The javascript code to run.
|
||||
callback: The callback to call with the result, or None.
|
||||
world: A world ID (int or usertypes.JsWorld member) to run the JS
|
||||
in the main world or in another isolated world.
|
||||
def run_js_blocking(self, code):
|
||||
"""Run javascript and block.
|
||||
|
||||
This returns the result to the caller. Its use should be avoided when
|
||||
possible as it runs a local event loop for QtWebEngine.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -837,29 +629,13 @@ class AbstractTab(QWidget):
|
||||
def icon(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_html(self, html, base_url=QUrl()):
|
||||
raise NotImplementedError
|
||||
|
||||
def networkaccessmanager(self):
|
||||
"""Get the QNetworkAccessManager for this tab.
|
||||
|
||||
This is only implemented for QtWebKit.
|
||||
For QtWebEngine, always returns None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def user_agent(self):
|
||||
"""Get the user agent for this tab.
|
||||
|
||||
This is only implemented for QtWebKit.
|
||||
For QtWebEngine, always returns None.
|
||||
"""
|
||||
def set_html(self, html, base_url):
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -23,10 +23,10 @@ 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.browser.webkit import downloads
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.utils import qtutils, utils, objreg
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@ def update_geometry(obj):
|
||||
|
||||
Here we check if obj ("self") was deleted and just ignore the event if so.
|
||||
|
||||
Original bug: https://github.com/qutebrowser/qutebrowser/issues/167
|
||||
Workaround bug: https://github.com/qutebrowser/qutebrowser/issues/171
|
||||
Original bug: https://github.com/The-Compiler/qutebrowser/issues/167
|
||||
Workaround bug: https://github.com/The-Compiler/qutebrowser/issues/171
|
||||
"""
|
||||
def _update_geometry():
|
||||
"""Actually update the geometry if the object still exists."""
|
||||
@@ -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)
|
||||
@@ -84,7 +83,7 @@ class DownloadView(QListView):
|
||||
self.setFlow(QListView.LeftToRight)
|
||||
self.setSpacing(1)
|
||||
self._menu = None
|
||||
model = objreg.get('download-model', scope='window', window=win_id)
|
||||
model = objreg.get('download-manager', scope='window', window=win_id)
|
||||
model.rowsInserted.connect(functools.partial(update_geometry, self))
|
||||
model.rowsRemoved.connect(functools.partial(update_geometry, self))
|
||||
model.dataChanged.connect(functools.partial(update_geometry, self))
|
||||
@@ -114,7 +113,7 @@ class DownloadView(QListView):
|
||||
item = self.model().data(index, downloads.ModelRole.item)
|
||||
if item.done and item.successful:
|
||||
item.open_file()
|
||||
item.remove()
|
||||
self.model().remove_item(item)
|
||||
|
||||
def _get_menu_actions(self, item):
|
||||
"""Get the available context menu actions for a given DownloadItem.
|
||||
@@ -135,8 +134,9 @@ class DownloadView(QListView):
|
||||
if item.successful:
|
||||
actions.append(("Open", item.open_file))
|
||||
else:
|
||||
actions.append(("Retry", item.try_retry))
|
||||
actions.append(("Remove", item.remove))
|
||||
actions.append(("Retry", item.retry))
|
||||
actions.append(("Remove",
|
||||
functools.partial(model.remove_item, item)))
|
||||
else:
|
||||
actions.append(("Cancel", item.cancel))
|
||||
if model.can_clear():
|
||||
|
||||
@@ -1,350 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Simple history which gets written to disk."""
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
|
||||
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
|
||||
debug, standarddir, qtutils)
|
||||
from qutebrowser.misc import objects, sql
|
||||
|
||||
|
||||
# increment to indicate that HistoryCompletion must be regenerated
|
||||
_USER_VERSION = 1
|
||||
|
||||
|
||||
class CompletionHistory(sql.SqlTable):
|
||||
|
||||
"""History which only has the newest entry for each URL."""
|
||||
|
||||
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')
|
||||
|
||||
|
||||
class WebHistory(sql.SqlTable):
|
||||
|
||||
"""The global history of visited pages."""
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, length=len(self))
|
||||
|
||||
def __contains__(self, url):
|
||||
return self._contains_query.run(val=url).value()
|
||||
|
||||
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 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 get_recent(self):
|
||||
"""Get the most recent history entries."""
|
||||
return self.select(sort_by='atime', sort_order='desc', limit=100)
|
||||
|
||||
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)
|
||||
|
||||
@cmdutils.register(name='history-clear', instance='web-history')
|
||||
def clear(self, force=False):
|
||||
"""Clear all browsing history.
|
||||
|
||||
Note this only clears the global history
|
||||
(e.g. `~/.local/share/qutebrowser/history` on Linux) but not cookies,
|
||||
the back/forward history of a tab, cache or other persistent data.
|
||||
|
||||
Args:
|
||||
force: Don't ask for confirmation.
|
||||
"""
|
||||
if force:
|
||||
self._do_clear()
|
||||
else:
|
||||
message.confirm_async(self._do_clear, title="Clear all browsing "
|
||||
"history?")
|
||||
|
||||
def _do_clear(self):
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
|
||||
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))
|
||||
|
||||
@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 url.scheme() == 'data' or requested_url.scheme() == 'data':
|
||||
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)):
|
||||
# If the url of the page is different than the url of the link
|
||||
# originally clicked, save them both.
|
||||
self.add_url(requested_url, title, redirect=True)
|
||||
self.add_url(url, title)
|
||||
|
||||
def add_url(self, url, title="", *, redirect=False, atime=None):
|
||||
"""Called via add_from_tab when a URL should be added to the history.
|
||||
|
||||
Args:
|
||||
url: A url (as QUrl) to add to the history.
|
||||
redirect: Whether the entry was redirected to another URL
|
||||
(hidden in completion)
|
||||
atime: Override the atime used to add the entry
|
||||
"""
|
||||
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())
|
||||
|
||||
try:
|
||||
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)
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
message.error("Failed to write history: {}".format(e.text()))
|
||||
else:
|
||||
raise
|
||||
|
||||
def _parse_entry(self, line):
|
||||
"""Parse a history line like '12345 http://example.com title'."""
|
||||
if not line or line.startswith('#'):
|
||||
return None
|
||||
data = line.split(maxsplit=2)
|
||||
if len(data) == 2:
|
||||
atime, url = data
|
||||
title = ""
|
||||
elif len(data) == 3:
|
||||
atime, url, title = data
|
||||
else:
|
||||
raise ValueError("2 or 3 fields expected")
|
||||
|
||||
# http://xn--pple-43d.com/ with
|
||||
# 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():
|
||||
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: {}', e)
|
||||
|
||||
|
||||
def init(parent=None):
|
||||
"""Initialize the web history.
|
||||
|
||||
Args:
|
||||
parent: The parent to use for WebHistory.
|
||||
"""
|
||||
history = WebHistory(parent=parent)
|
||||
objreg.register('web-history', history)
|
||||
|
||||
if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover
|
||||
from qutebrowser.browser.webkit import webkithistory
|
||||
webkithistory.init(history)
|
||||
@@ -1,216 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Mouse handling for a browser tab."""
|
||||
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
|
||||
class ChildEventFilter(QObject):
|
||||
|
||||
"""An event filter re-adding MouseEventFilter on ChildEvent.
|
||||
|
||||
This is needed because QtWebEngine likes to randomly change its
|
||||
focusProxy...
|
||||
|
||||
FIXME:qtwebengine Add a test for this happening
|
||||
|
||||
Attributes:
|
||||
_filter: The event filter to install.
|
||||
_widget: The widget expected to send out childEvents.
|
||||
"""
|
||||
|
||||
def __init__(self, eventfilter, widget, parent=None):
|
||||
super().__init__(parent)
|
||||
self._filter = eventfilter
|
||||
assert widget is not None
|
||||
self._widget = widget
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Act on ChildAdded events."""
|
||||
if event.type() == QEvent.ChildAdded:
|
||||
child = event.child()
|
||||
log.mouse.debug("{} got new child {}, installing filter".format(
|
||||
obj, child))
|
||||
assert obj is self._widget
|
||||
child.installEventFilter(self._filter)
|
||||
return False
|
||||
|
||||
|
||||
class MouseEventFilter(QObject):
|
||||
|
||||
"""Handle mouse events on a tab.
|
||||
|
||||
Attributes:
|
||||
_tab: The browsertab object this filter is installed on.
|
||||
_handlers: A dict of handler functions for the handled events.
|
||||
_ignore_wheel_event: Whether to ignore the next wheelEvent.
|
||||
_check_insertmode_on_release: Whether an insertmode check should be
|
||||
done when the mouse is released.
|
||||
"""
|
||||
|
||||
def __init__(self, tab, *, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._handlers = {
|
||||
QEvent.MouseButtonPress: self._handle_mouse_press,
|
||||
QEvent.MouseButtonRelease: self._handle_mouse_release,
|
||||
QEvent.Wheel: self._handle_wheel,
|
||||
QEvent.ContextMenu: self._handle_context_menu,
|
||||
}
|
||||
self._ignore_wheel_event = False
|
||||
self._check_insertmode_on_release = False
|
||||
|
||||
def _handle_mouse_press(self, e):
|
||||
"""Handle pressing of a mouse button."""
|
||||
is_rocker_gesture = (config.val.input.rocker_gestures and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
|
||||
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
|
||||
self._mousepress_backforward(e)
|
||||
return True
|
||||
|
||||
self._ignore_wheel_event = True
|
||||
|
||||
if e.button() != Qt.NoButton:
|
||||
self._tab.elements.find_at_pos(e.pos(),
|
||||
self._mousepress_insertmode_cb)
|
||||
|
||||
return False
|
||||
|
||||
def _handle_mouse_release(self, _e):
|
||||
"""Handle releasing of a mouse button."""
|
||||
# We want to make sure we check the focus element after the WebView is
|
||||
# updated completely.
|
||||
QTimer.singleShot(0, self._mouserelease_insertmode)
|
||||
return False
|
||||
|
||||
def _handle_wheel(self, e):
|
||||
"""Zoom on Ctrl-Mousewheel.
|
||||
|
||||
Args:
|
||||
e: The QWheelEvent.
|
||||
"""
|
||||
if self._ignore_wheel_event:
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/395
|
||||
self._ignore_wheel_event = False
|
||||
return True
|
||||
|
||||
if e.modifiers() & Qt.ControlModifier:
|
||||
divider = config.val.zoom.mouse_divider
|
||||
if divider == 0:
|
||||
return False
|
||||
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
|
||||
if factor < 0:
|
||||
return False
|
||||
perc = int(100 * factor)
|
||||
message.info("Zoom level: {}%".format(perc), replace=True)
|
||||
self._tab.zoom.set_factor(factor)
|
||||
elif e.modifiers() & Qt.ShiftModifier:
|
||||
if e.angleDelta().y() > 0:
|
||||
self._tab.scroller.left()
|
||||
else:
|
||||
self._tab.scroller.right()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _handle_context_menu(self, _e):
|
||||
"""Suppress context menus if rocker gestures are turned on."""
|
||||
return config.val.input.rocker_gestures
|
||||
|
||||
def _mousepress_insertmode_cb(self, elem):
|
||||
"""Check if the clicked element is editable."""
|
||||
if elem is None:
|
||||
# Something didn't work out, let's find the focus element after
|
||||
# a mouse release.
|
||||
log.mouse.debug("Got None element, scheduling check on "
|
||||
"mouse release")
|
||||
self._check_insertmode_on_release = True
|
||||
return
|
||||
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element!")
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click', maybe=True)
|
||||
|
||||
def _mouserelease_insertmode(self):
|
||||
"""If we have an insertmode check scheduled, handle it."""
|
||||
if not self._check_insertmode_on_release:
|
||||
return
|
||||
self._check_insertmode_on_release = False
|
||||
|
||||
def mouserelease_insertmode_cb(elem):
|
||||
"""Callback which gets called from JS."""
|
||||
if elem is None:
|
||||
log.mouse.debug("Element vanished!")
|
||||
return
|
||||
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element (delayed)!")
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element (delayed)!")
|
||||
if config.val.input.insert_mode.auto_leave:
|
||||
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed', maybe=True)
|
||||
|
||||
self._tab.elements.find_focused(mouserelease_insertmode_cb)
|
||||
|
||||
def _mousepress_backforward(self, e):
|
||||
"""Handle back/forward mouse button presses.
|
||||
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
"""
|
||||
if e.button() in [Qt.XButton1, Qt.LeftButton]:
|
||||
# Back button on mice which have it, or rocker gesture
|
||||
if self._tab.history.can_go_back():
|
||||
self._tab.history.back()
|
||||
else:
|
||||
message.error("At beginning of history.")
|
||||
elif e.button() in [Qt.XButton2, Qt.RightButton]:
|
||||
# Forward button on mice which have it, or rocker gesture
|
||||
if self._tab.history.can_go_forward():
|
||||
self._tab.history.forward()
|
||||
else:
|
||||
message.error("At end of history.")
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Filter events going to a QWeb(Engine)View."""
|
||||
evtype = event.type()
|
||||
if evtype not in self._handlers:
|
||||
return False
|
||||
if obj is not self._tab.event_target():
|
||||
log.mouse.debug("Ignoring {} to {}".format(
|
||||
event.__class__.__name__, obj))
|
||||
return False
|
||||
return self._handlers[evtype](event)
|
||||
@@ -1,150 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Implementation of :navigate."""
|
||||
|
||||
import posixpath
|
||||
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
"""Raised when the navigation can't be done."""
|
||||
|
||||
|
||||
def incdec(url, count, inc_or_dec):
|
||||
"""Helper method for :navigate when `where' is increment/decrement.
|
||||
|
||||
Args:
|
||||
url: The current url.
|
||||
count: How much to increment or decrement by.
|
||||
inc_or_dec: Either 'increment' or 'decrement'.
|
||||
tab: Whether to open the link in a new tab.
|
||||
background: Open the link in a new background tab.
|
||||
window: Open the link in a new window.
|
||||
"""
|
||||
segments = set(config.val.url.incdec_segments)
|
||||
try:
|
||||
new_url = urlutils.incdec_number(url, inc_or_dec, count,
|
||||
segments=segments)
|
||||
except urlutils.IncDecError as error:
|
||||
raise Error(error.msg)
|
||||
return new_url
|
||||
|
||||
|
||||
def path_up(url, count):
|
||||
"""Helper method for :navigate when `where' is up.
|
||||
|
||||
Args:
|
||||
url: The current url.
|
||||
count: The number of levels to go up in the url.
|
||||
"""
|
||||
path = url.path()
|
||||
if not path or path == '/':
|
||||
raise Error("Can't go up!")
|
||||
for _i in range(0, min(count, path.count('/'))):
|
||||
path = posixpath.join(path, posixpath.pardir)
|
||||
url.setPath(path)
|
||||
return url
|
||||
|
||||
|
||||
def _find_prevnext(prev, elems):
|
||||
"""Find a prev/next element in the given list of elements."""
|
||||
# First check for <link rel="prev(ious)|next">
|
||||
rel_values = {'prev', 'previous'} if prev else {'next'}
|
||||
for e in elems:
|
||||
if e.tag_name() not in ['link', 'a'] or 'rel' not in e:
|
||||
continue
|
||||
if set(e['rel'].split(' ')) & rel_values:
|
||||
log.hints.debug("Found {!r} with rel={}".format(e, e['rel']))
|
||||
return e
|
||||
|
||||
# Then check for regular links/buttons.
|
||||
elems = [e for e in elems if e.tag_name() != 'link']
|
||||
option = 'prev_regexes' if prev else 'next_regexes'
|
||||
if not elems:
|
||||
return None
|
||||
|
||||
# pylint: disable=bad-config-option
|
||||
for regex in getattr(config.val.hints, option):
|
||||
# pylint: enable=bad-config-option
|
||||
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
|
||||
for e in elems:
|
||||
text = str(e)
|
||||
if not text:
|
||||
continue
|
||||
if regex.search(text):
|
||||
log.hints.debug("Regex '{}' matched on '{}'.".format(
|
||||
regex.pattern, text))
|
||||
return e
|
||||
else:
|
||||
log.hints.vdebug("No match on '{}'!".format(text))
|
||||
return None
|
||||
|
||||
|
||||
def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
||||
tab=False, background=False, window=False):
|
||||
"""Click a "previous"/"next" element on the page.
|
||||
|
||||
Args:
|
||||
browsertab: The WebKitTab/WebEngineTab of the page.
|
||||
baseurl: The base URL of the current tab.
|
||||
prev: True to open a "previous" link, False to open a "next" link.
|
||||
tab: True to open in a new tab, False for the current tab.
|
||||
background: True to open in a background tab.
|
||||
window: True to open in a new window, False for the current one.
|
||||
"""
|
||||
def _prevnext_cb(elems):
|
||||
if elems is None:
|
||||
message.error("There was an error while getting hint elements")
|
||||
return
|
||||
|
||||
elem = _find_prevnext(prev, elems)
|
||||
word = 'prev' if prev else 'forward'
|
||||
|
||||
if elem is None:
|
||||
message.error("No {} links found!".format(word))
|
||||
return
|
||||
url = elem.resolve_url(baseurl)
|
||||
if url is None:
|
||||
message.error("No {} links found!".format(word))
|
||||
return
|
||||
qtutils.ensure_valid(url)
|
||||
|
||||
cur_tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
|
||||
if window:
|
||||
new_window = mainwindow.MainWindow(
|
||||
private=cur_tabbed_browser.private)
|
||||
new_window.show()
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=new_window.win_id)
|
||||
tabbed_browser.tabopen(url, background=False)
|
||||
elif tab:
|
||||
cur_tabbed_browser.tabopen(url, background=background)
|
||||
else:
|
||||
browsertab.openurl(url)
|
||||
|
||||
browsertab.elements.find_css(webelem.SELECTORS[webelem.Group.links],
|
||||
_prevnext_cb)
|
||||
@@ -1,3 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
"""Modules related to network operations."""
|
||||
@@ -1,326 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Evaluation of PAC scripts."""
|
||||
|
||||
import sys
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, QUrl
|
||||
from PyQt5.QtNetwork import (QNetworkProxy, QNetworkRequest, QHostInfo,
|
||||
QNetworkReply, QNetworkAccessManager,
|
||||
QHostAddress)
|
||||
from PyQt5.QtQml import QJSEngine, QJSValue
|
||||
|
||||
from qutebrowser.utils import log, utils, qtutils
|
||||
|
||||
|
||||
class ParseProxyError(Exception):
|
||||
|
||||
"""Error while parsing PAC result string."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EvalProxyError(Exception):
|
||||
|
||||
"""Error while evaluating PAC script."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def _js_slot(*args):
|
||||
"""Wrap a methods as a JavaScript function.
|
||||
|
||||
Register a PACContext method as a JavaScript function, and catch
|
||||
exceptions returning them as JavaScript Error objects.
|
||||
|
||||
Args:
|
||||
args: Types of method arguments.
|
||||
|
||||
Return: Wrapped method.
|
||||
"""
|
||||
def _decorator(method):
|
||||
@functools.wraps(method)
|
||||
def new_method(self, *args, **kwargs):
|
||||
try:
|
||||
return method(self, *args, **kwargs)
|
||||
except:
|
||||
e = str(sys.exc_info()[0])
|
||||
log.network.exception("PAC evaluation error")
|
||||
# pylint: disable=protected-access
|
||||
return self._error_con.callAsConstructor([e])
|
||||
# pylint: enable=protected-access
|
||||
return pyqtSlot(*args, result=QJSValue)(new_method)
|
||||
return _decorator
|
||||
|
||||
|
||||
class _PACContext(QObject):
|
||||
|
||||
"""Implementation of PAC API functions that require native calls.
|
||||
|
||||
See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Necko/Proxy_Auto-Configuration_(PAC)_file
|
||||
"""
|
||||
|
||||
JS_DEFINITIONS = """
|
||||
function dnsResolve(host) {
|
||||
return PAC.dnsResolve(host);
|
||||
}
|
||||
|
||||
function myIpAddress() {
|
||||
return PAC.myIpAddress();
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self, engine):
|
||||
"""Create a new PAC API implementation instance.
|
||||
|
||||
Args:
|
||||
engine: QJSEngine which is used for running PAC.
|
||||
"""
|
||||
super().__init__(parent=engine)
|
||||
self._engine = engine
|
||||
self._error_con = engine.globalObject().property("Error")
|
||||
|
||||
@_js_slot(str)
|
||||
def dnsResolve(self, host):
|
||||
"""Resolve a DNS hostname.
|
||||
|
||||
Resolves the given DNS hostname into an IP address, and returns it
|
||||
in the dot-separated format as a string.
|
||||
|
||||
Args:
|
||||
host: hostname to resolve.
|
||||
"""
|
||||
ips = QHostInfo.fromName(host)
|
||||
if ips.error() != QHostInfo.NoError or not ips.addresses():
|
||||
err_f = "Failed to resolve host during PAC evaluation: {}"
|
||||
log.network.info(err_f.format(host))
|
||||
return QJSValue(QJSValue.NullValue)
|
||||
else:
|
||||
return ips.addresses()[0].toString()
|
||||
|
||||
@_js_slot()
|
||||
def myIpAddress(self):
|
||||
"""Get host IP address.
|
||||
|
||||
Return the server IP address of the current machine, as a string in
|
||||
the dot-separated integer format.
|
||||
"""
|
||||
return QHostAddress(QHostAddress.LocalHost).toString()
|
||||
|
||||
|
||||
class PACResolver:
|
||||
|
||||
"""Evaluate PAC script files and resolve proxies."""
|
||||
|
||||
@staticmethod
|
||||
def _parse_proxy_host(host_str):
|
||||
host, _colon, port_str = host_str.partition(':')
|
||||
try:
|
||||
port = int(port_str)
|
||||
except ValueError:
|
||||
raise ParseProxyError("Invalid port number")
|
||||
return (host, port)
|
||||
|
||||
@staticmethod
|
||||
def _parse_proxy_entry(proxy_str):
|
||||
"""Parse one proxy string entry, as described in PAC specification."""
|
||||
config = [c.strip() for c in proxy_str.split(' ') if c]
|
||||
if not config:
|
||||
raise ParseProxyError("Empty proxy entry")
|
||||
elif config[0] == "DIRECT":
|
||||
if len(config) != 1:
|
||||
raise ParseProxyError("Invalid number of parameters for " +
|
||||
"DIRECT")
|
||||
return QNetworkProxy(QNetworkProxy.NoProxy)
|
||||
elif config[0] == "PROXY":
|
||||
if len(config) != 2:
|
||||
raise ParseProxyError("Invalid number of parameters for PROXY")
|
||||
host, port = PACResolver._parse_proxy_host(config[1])
|
||||
return QNetworkProxy(QNetworkProxy.HttpProxy, host, port)
|
||||
elif config[0] in ["SOCKS", "SOCKS5"]:
|
||||
if len(config) != 2:
|
||||
raise ParseProxyError("Invalid number of parameters for SOCKS")
|
||||
host, port = PACResolver._parse_proxy_host(config[1])
|
||||
return QNetworkProxy(QNetworkProxy.Socks5Proxy, host, port)
|
||||
else:
|
||||
err = "Unknown proxy type: {}"
|
||||
raise ParseProxyError(err.format(config[0]))
|
||||
|
||||
@staticmethod
|
||||
def _parse_proxy_string(proxy_str):
|
||||
proxies = proxy_str.split(';')
|
||||
return [PACResolver._parse_proxy_entry(x) for x in proxies]
|
||||
|
||||
def _evaluate(self, js_code, js_file):
|
||||
ret = self._engine.evaluate(js_code, js_file)
|
||||
if ret.isError():
|
||||
err = "JavaScript error while evaluating PAC file: {}"
|
||||
raise EvalProxyError(err.format(ret.toString()))
|
||||
|
||||
def __init__(self, pac_str):
|
||||
"""Create a PAC resolver.
|
||||
|
||||
Args:
|
||||
pac_str: JavaScript code containing PAC resolver.
|
||||
"""
|
||||
self._engine = QJSEngine()
|
||||
|
||||
self._ctx = _PACContext(self._engine)
|
||||
self._engine.globalObject().setProperty(
|
||||
"PAC", self._engine.newQObject(self._ctx))
|
||||
self._evaluate(_PACContext.JS_DEFINITIONS, "pac_js_definitions")
|
||||
self._evaluate(utils.read_file("javascript/pac_utils.js"), "pac_utils")
|
||||
proxy_config = self._engine.newObject()
|
||||
proxy_config.setProperty("bindings", self._engine.newObject())
|
||||
self._engine.globalObject().setProperty("ProxyConfig", proxy_config)
|
||||
|
||||
self._evaluate(pac_str, "pac")
|
||||
global_js_object = self._engine.globalObject()
|
||||
self._resolver = global_js_object.property("FindProxyForURL")
|
||||
if not self._resolver.isCallable():
|
||||
err = "Cannot resolve FindProxyForURL function, got '{}' instead"
|
||||
raise EvalProxyError(err.format(self._resolver.toString()))
|
||||
|
||||
def resolve(self, query, from_file=False):
|
||||
"""Resolve a proxy via PAC.
|
||||
|
||||
Args:
|
||||
query: QNetworkProxyQuery.
|
||||
from_file: Whether the proxy info is coming from a file.
|
||||
|
||||
Return:
|
||||
A list of QNetworkProxy objects in order of preference.
|
||||
"""
|
||||
if from_file:
|
||||
string_flags = QUrl.PrettyDecoded
|
||||
else:
|
||||
string_flags = QUrl.RemoveUserInfo
|
||||
if query.url().scheme() == 'https':
|
||||
string_flags |= QUrl.RemovePath | QUrl.RemoveQuery
|
||||
|
||||
result = self._resolver.call([query.url().toString(string_flags),
|
||||
query.peerHostName()])
|
||||
result_str = result.toString()
|
||||
if not result.isString():
|
||||
err = "Got strange value from FindProxyForURL: '{}'"
|
||||
raise EvalProxyError(err.format(result_str))
|
||||
return self._parse_proxy_string(result_str)
|
||||
|
||||
|
||||
class PACFetcher(QObject):
|
||||
|
||||
"""Asynchronous fetcher of PAC files."""
|
||||
|
||||
finished = pyqtSignal()
|
||||
|
||||
def __init__(self, url, parent=None):
|
||||
"""Resolve a PAC proxy from URL.
|
||||
|
||||
Args:
|
||||
url: QUrl of a PAC proxy.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
|
||||
pac_prefix = "pac+"
|
||||
|
||||
assert url.scheme().startswith(pac_prefix)
|
||||
url.setScheme(url.scheme()[len(pac_prefix):])
|
||||
|
||||
self._pac_url = url
|
||||
self._manager = QNetworkAccessManager()
|
||||
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
|
||||
self._pac = None
|
||||
self._error_message = None
|
||||
self._reply = None
|
||||
|
||||
def __eq__(self, other):
|
||||
# pylint: disable=protected-access
|
||||
return self._pac_url == other._pac_url
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, url=self._pac_url, constructor=True)
|
||||
|
||||
def fetch(self):
|
||||
"""Fetch the proxy from the remote URL."""
|
||||
self._reply = self._manager.get(QNetworkRequest(self._pac_url))
|
||||
self._reply.finished.connect(self._finish)
|
||||
|
||||
@pyqtSlot()
|
||||
def _finish(self):
|
||||
if self._reply.error() != QNetworkReply.NoError:
|
||||
error = "Can't fetch PAC file from URL, error code {}: {}"
|
||||
self._error_message = error.format(
|
||||
self._reply.error(), self._reply.errorString())
|
||||
log.network.error(self._error_message)
|
||||
else:
|
||||
try:
|
||||
pacscript = bytes(self._reply.readAll()).decode("utf-8")
|
||||
except UnicodeError as e:
|
||||
error = "Invalid encoding of a PAC file: {}"
|
||||
self._error_message = error.format(e)
|
||||
log.network.exception(self._error_message)
|
||||
try:
|
||||
self._pac = PACResolver(pacscript)
|
||||
log.network.debug("Successfully evaluated PAC file.")
|
||||
except EvalProxyError as e:
|
||||
error = "Error in PAC evaluation: {}"
|
||||
self._error_message = error.format(e)
|
||||
log.network.exception(self._error_message)
|
||||
self._manager = None
|
||||
self._reply = None
|
||||
self.finished.emit()
|
||||
|
||||
def _wait(self):
|
||||
"""Wait until a reply from the remote server is received."""
|
||||
if self._manager is not None:
|
||||
loop = qtutils.EventLoop()
|
||||
self.finished.connect(loop.quit)
|
||||
loop.exec_()
|
||||
|
||||
def fetch_error(self):
|
||||
"""Check if PAC script is successfully fetched.
|
||||
|
||||
Return None iff PAC script is downloaded and evaluated successfully,
|
||||
error string otherwise.
|
||||
"""
|
||||
self._wait()
|
||||
return self._error_message
|
||||
|
||||
def resolve(self, query):
|
||||
"""Resolve a query via PAC.
|
||||
|
||||
Args: QNetworkProxyQuery.
|
||||
|
||||
Return a list of QNetworkProxy objects in order of preference.
|
||||
"""
|
||||
self._wait()
|
||||
from_file = self._pac_url.scheme() == 'file'
|
||||
try:
|
||||
return self._pac.resolve(query, from_file=from_file)
|
||||
except (EvalProxyError, ParseProxyError) as e:
|
||||
log.network.exception("Error in PAC resolution: {}.".format(e))
|
||||
# .invalid is guaranteed to be inaccessible in RFC 6761.
|
||||
# Port 9 is for DISCARD protocol -- DISCARD servers act like
|
||||
# /dev/null.
|
||||
# Later NetworkManager.createRequest will detect this and display
|
||||
# an error message.
|
||||
error_host = "pac-resolve-error.qutebrowser.invalid"
|
||||
return [QNetworkProxy(QNetworkProxy.HttpProxy, error_host, 9)]
|
||||
@@ -1,7 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2015 Daniel Schadt
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,7 +23,8 @@ import os
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.utils import utils, javascript
|
||||
from qutebrowser.browser.webkit import webelem
|
||||
from qutebrowser.utils import utils
|
||||
|
||||
|
||||
class PDFJSNotFound(Exception):
|
||||
@@ -63,11 +63,9 @@ def _generate_pdfjs_script(url):
|
||||
url: The url of the pdf page as QUrl.
|
||||
"""
|
||||
return (
|
||||
'document.addEventListener("DOMContentLoaded", function() {{\n'
|
||||
' PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
|
||||
' (window.PDFView || window.PDFViewerApplication).open("{url}");\n'
|
||||
'}});\n'
|
||||
).format(url=javascript.string_escape(url.toString(QUrl.FullyEncoded)))
|
||||
'PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
|
||||
'PDFView.open("{url}");\n'
|
||||
).format(url=webelem.javascript_escape(url.toString(QUrl.FullyEncoded)))
|
||||
|
||||
|
||||
def fix_urls(asset):
|
||||
@@ -101,8 +99,6 @@ SYSTEM_PDFJS_PATHS = [
|
||||
# Debian pdf.js-common
|
||||
# Arch Linux pdfjs (AUR)
|
||||
'/usr/share/pdf.js/',
|
||||
# Arch Linux pdf.js (AUR)
|
||||
'/usr/share/javascript/pdf.js/',
|
||||
# Debian libjs-pdf
|
||||
'/usr/share/javascript/pdf/',
|
||||
# fallback
|
||||
|
||||
@@ -1,540 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Download manager."""
|
||||
|
||||
import io
|
||||
import shutil
|
||||
import functools
|
||||
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
|
||||
|
||||
@attr.s
|
||||
class _RetryInfo:
|
||||
|
||||
request = attr.ib()
|
||||
manager = attr.ib()
|
||||
|
||||
|
||||
class DownloadItem(downloads.AbstractDownloadItem):
|
||||
|
||||
"""A single download currently running.
|
||||
|
||||
There are multiple ways the data can flow from the QNetworkReply to the
|
||||
disk.
|
||||
|
||||
If the filename/file object is known immediately when starting the
|
||||
download, QNetworkReply's readyRead writes to the target file directly.
|
||||
|
||||
If not, readyRead is ignored and with self._read_timer we periodically read
|
||||
into the self._buffer BytesIO slowly, so some broken servers don't close
|
||||
our connection.
|
||||
|
||||
As soon as we know the file object, we copy self._buffer over and the next
|
||||
readyRead will write to the real file object.
|
||||
|
||||
Class attributes:
|
||||
_MAX_REDIRECTS: The maximum redirection count.
|
||||
|
||||
Attributes:
|
||||
_retry_info: A _RetryInfo instance.
|
||||
_redirects: How many time we were redirected already.
|
||||
_buffer: A BytesIO object to buffer incoming data until we know the
|
||||
target file.
|
||||
_read_timer: A Timer which reads the QNetworkReply into self._buffer
|
||||
periodically.
|
||||
_manager: The DownloadManager which started this download
|
||||
_reply: The QNetworkReply associated with this download.
|
||||
_autoclose: Whether to close the associated file when the download is
|
||||
done.
|
||||
|
||||
Signals:
|
||||
adopt_download: Emitted when a download is retried and should be
|
||||
adopted by the QNAM if needed.
|
||||
arg 0: The new DownloadItem
|
||||
"""
|
||||
|
||||
_MAX_REDIRECTS = 10
|
||||
adopt_download = pyqtSignal(object) # DownloadItem
|
||||
|
||||
def __init__(self, reply, manager):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply to download.
|
||||
"""
|
||||
super().__init__(parent=manager)
|
||||
self.fileobj = None
|
||||
self.raw_headers = {}
|
||||
|
||||
self._autoclose = True
|
||||
self._manager = manager
|
||||
self._retry_info = None
|
||||
self._reply = None
|
||||
self._buffer = io.BytesIO()
|
||||
self._read_timer = usertypes.Timer(self, name='download-read-timer')
|
||||
self._read_timer.setInterval(500)
|
||||
self._read_timer.timeout.connect(self._on_read_timer_timeout)
|
||||
self._redirects = 0
|
||||
self._init_reply(reply)
|
||||
|
||||
def _create_fileobj(self):
|
||||
"""Create a file object using the internal filename."""
|
||||
try:
|
||||
fileobj = open(self._filename, 'wb')
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
else:
|
||||
self._set_fileobj(fileobj)
|
||||
|
||||
def _do_die(self):
|
||||
"""Abort the download and emit an error."""
|
||||
self._read_timer.stop()
|
||||
if self._reply is None:
|
||||
log.downloads.debug("Reply gone while dying")
|
||||
return
|
||||
self._reply.downloadProgress.disconnect()
|
||||
self._reply.finished.disconnect()
|
||||
self._reply.error.disconnect()
|
||||
self._reply.readyRead.disconnect()
|
||||
with log.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
|
||||
'problem, this method must only be called '
|
||||
'once.'):
|
||||
# See https://codereview.qt-project.org/#/c/107863/
|
||||
self._reply.abort()
|
||||
self._reply.deleteLater()
|
||||
self._reply = None
|
||||
if self.fileobj is not None:
|
||||
try:
|
||||
self.fileobj.close()
|
||||
except OSError:
|
||||
log.downloads.exception("Error while closing file object")
|
||||
|
||||
def _init_reply(self, reply):
|
||||
"""Set a new reply and connect its signals.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply to handle.
|
||||
"""
|
||||
self.done = False
|
||||
self.successful = False
|
||||
self._reply = reply
|
||||
reply.setReadBufferSize(16 * 1024 * 1024) # 16 MB
|
||||
reply.downloadProgress.connect(self.stats.on_download_progress)
|
||||
reply.finished.connect(self._on_reply_finished)
|
||||
reply.error.connect(self._on_reply_error)
|
||||
reply.readyRead.connect(self._on_ready_read)
|
||||
reply.metaDataChanged.connect(self._on_meta_data_changed)
|
||||
self._retry_info = _RetryInfo(request=reply.request(),
|
||||
manager=reply.manager())
|
||||
if not self.fileobj:
|
||||
self._read_timer.start()
|
||||
# We could have got signals before we connected slots to them.
|
||||
# Here no signals are connected to the DownloadItem yet, so we use a
|
||||
# singleShot QTimer to emit them after they are connected.
|
||||
if reply.error() != QNetworkReply.NoError:
|
||||
QTimer.singleShot(0, lambda: self._die(reply.errorString()))
|
||||
|
||||
def _do_cancel(self):
|
||||
if self._reply is not None:
|
||||
self._reply.finished.disconnect(self._on_reply_finished)
|
||||
self._reply.abort()
|
||||
self._reply.deleteLater()
|
||||
self._reply = None
|
||||
if self.fileobj is not None:
|
||||
self.fileobj.close()
|
||||
self.cancelled.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def retry(self):
|
||||
"""Retry a failed download."""
|
||||
assert self.done
|
||||
assert not self.successful
|
||||
new_reply = self._retry_info.manager.get(self._retry_info.request)
|
||||
new_download = self._manager.fetch(new_reply,
|
||||
suggested_filename=self.basename)
|
||||
self.adopt_download.emit(new_download)
|
||||
self.cancel()
|
||||
|
||||
def _get_open_filename(self):
|
||||
filename = self._filename
|
||||
if filename is None:
|
||||
filename = getattr(self.fileobj, 'name', None)
|
||||
return filename
|
||||
|
||||
def _ensure_can_set_filename(self, filename):
|
||||
if self.fileobj is not None: # pragma: no cover
|
||||
raise ValueError("fileobj was already set! filename: {}, "
|
||||
"existing: {}, fileobj {}".format(
|
||||
filename, self._filename, self.fileobj))
|
||||
|
||||
def _after_set_filename(self):
|
||||
self._create_fileobj()
|
||||
|
||||
def _ask_confirm_question(self, title, msg):
|
||||
no_action = functools.partial(self.cancel, remove_data=False)
|
||||
message.confirm_async(title=title, text=msg,
|
||||
yes_action=self._after_set_filename,
|
||||
no_action=no_action, cancel_action=no_action,
|
||||
abort_on=[self.cancelled, self.error])
|
||||
|
||||
def _set_fileobj(self, fileobj, *, autoclose=True):
|
||||
""""Set the file object to write the download to.
|
||||
|
||||
Args:
|
||||
fileobj: A file-like object.
|
||||
"""
|
||||
if self.fileobj is not None: # pragma: no cover
|
||||
raise ValueError("fileobj was already set! Old: {}, new: "
|
||||
"{}".format(self.fileobj, fileobj))
|
||||
self.fileobj = fileobj
|
||||
self._autoclose = autoclose
|
||||
try:
|
||||
self._read_timer.stop()
|
||||
log.downloads.debug("buffer: {} bytes".format(self._buffer.tell()))
|
||||
self._buffer.seek(0)
|
||||
shutil.copyfileobj(self._buffer, fileobj)
|
||||
self._buffer.close()
|
||||
if self._reply.isFinished():
|
||||
# Downloading to the buffer in RAM has already finished so we
|
||||
# write out the data and clean up now.
|
||||
self._on_reply_finished()
|
||||
else:
|
||||
# Since the buffer already might be full, on_ready_read might
|
||||
# not be called at all anymore, so we force it here to flush
|
||||
# the buffer and continue receiving new data.
|
||||
self._on_ready_read()
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
|
||||
def _set_tempfile(self, fileobj):
|
||||
self._set_fileobj(fileobj)
|
||||
|
||||
def _finish_download(self):
|
||||
"""Write buffered data to disk and finish the QNetworkReply."""
|
||||
log.downloads.debug("Finishing download...")
|
||||
if self._reply.isOpen():
|
||||
self.fileobj.write(self._reply.readAll())
|
||||
if self._autoclose:
|
||||
self.fileobj.close()
|
||||
self.successful = self._reply.error() == QNetworkReply.NoError
|
||||
self._reply.close()
|
||||
self._reply.deleteLater()
|
||||
self._reply = None
|
||||
self.finished.emit()
|
||||
self.done = True
|
||||
log.downloads.debug("Download {} finished".format(self.basename))
|
||||
self.data_changed.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_reply_finished(self):
|
||||
"""Clean up when the download was finished.
|
||||
|
||||
Note when this gets called, only the QNetworkReply has finished. This
|
||||
doesn't mean the download (i.e. writing data to the disk) is finished
|
||||
as well. Therefore, we can't close() the QNetworkReply in here yet.
|
||||
"""
|
||||
if self._reply is None:
|
||||
return
|
||||
self._read_timer.stop()
|
||||
self.stats.finish()
|
||||
is_redirected = self._handle_redirect()
|
||||
if is_redirected:
|
||||
return
|
||||
log.downloads.debug("Reply finished, fileobj {}".format(self.fileobj))
|
||||
if self.fileobj is not None:
|
||||
# We can do a "delayed" write immediately to empty the buffer and
|
||||
# clean up.
|
||||
self._finish_download()
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ready_read(self):
|
||||
"""Read available data and save file when ready to read."""
|
||||
if self.fileobj is None or self._reply is None:
|
||||
# No filename has been set yet (so we don't empty the buffer) or we
|
||||
# got a readyRead after the reply was finished (which happens on
|
||||
# qute://log for example).
|
||||
return
|
||||
if not self._reply.isOpen():
|
||||
raise OSError("Reply is closed!")
|
||||
try:
|
||||
self.fileobj.write(self._reply.readAll())
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
|
||||
@pyqtSlot('QNetworkReply::NetworkError')
|
||||
def _on_reply_error(self, code):
|
||||
"""Handle QNetworkReply errors."""
|
||||
if code == QNetworkReply.OperationCanceledError:
|
||||
return
|
||||
else:
|
||||
self._die(self._reply.errorString())
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_read_timer_timeout(self):
|
||||
"""Read some bytes from the QNetworkReply periodically."""
|
||||
if not self._reply.isOpen():
|
||||
raise OSError("Reply is closed!")
|
||||
data = self._reply.read(1024)
|
||||
if data is not None:
|
||||
self._buffer.write(data)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_meta_data_changed(self):
|
||||
"""Update the download's metadata."""
|
||||
if self._reply is None:
|
||||
return
|
||||
self.raw_headers = {}
|
||||
for key, value in self._reply.rawHeaderPairs():
|
||||
self.raw_headers[bytes(key)] = bytes(value)
|
||||
|
||||
def _handle_redirect(self):
|
||||
"""Handle an HTTP redirect.
|
||||
|
||||
Return:
|
||||
True if the download was redirected, False otherwise.
|
||||
"""
|
||||
redirect = self._reply.attribute(
|
||||
QNetworkRequest.RedirectionTargetAttribute)
|
||||
if redirect is None or redirect.isEmpty():
|
||||
return False
|
||||
new_url = self._reply.url().resolved(redirect)
|
||||
new_request = self._reply.request()
|
||||
if new_url == new_request.url():
|
||||
return False
|
||||
|
||||
if self._redirects > self._MAX_REDIRECTS:
|
||||
self._die("Maximum redirection count reached!")
|
||||
self.delete()
|
||||
return True # so on_reply_finished aborts
|
||||
|
||||
log.downloads.debug("{}: Handling redirect".format(self))
|
||||
self._redirects += 1
|
||||
new_request.setUrl(new_url)
|
||||
old_reply = self._reply
|
||||
old_reply.finished.disconnect(self._on_reply_finished)
|
||||
self._read_timer.stop()
|
||||
self._reply = None
|
||||
if self.fileobj is not None:
|
||||
self.fileobj.seek(0)
|
||||
|
||||
log.downloads.debug("redirected: {} -> {}".format(
|
||||
old_reply.url(), new_request.url()))
|
||||
new_reply = old_reply.manager().get(new_request)
|
||||
self._init_reply(new_reply)
|
||||
|
||||
old_reply.deleteLater()
|
||||
return True
|
||||
|
||||
def _uses_nam(self, nam):
|
||||
"""Check if this download uses the given QNetworkAccessManager."""
|
||||
running_nam = self._reply is not None and self._reply.manager() is nam
|
||||
# user could request retry after tab is closed.
|
||||
retry_nam = (self.done and (not self.successful) and
|
||||
self._retry_info.manager is nam)
|
||||
return running_nam or retry_nam
|
||||
|
||||
|
||||
class DownloadManager(downloads.AbstractDownloadManager):
|
||||
|
||||
"""Manager for currently running downloads.
|
||||
|
||||
Attributes:
|
||||
_networkmanager: A NetworkManager for generic downloads.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id=win_id, tab_id=None,
|
||||
private=config.val.content.private_browsing, parent=self)
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def get(self, url, *, user_agent=None, **kwargs):
|
||||
"""Start a download with a link URL.
|
||||
|
||||
Args:
|
||||
url: The URL to get, as QUrl
|
||||
user_agent: The UA to set for the request, or None.
|
||||
**kwargs: passed to get_request().
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
"""
|
||||
if not url.isValid():
|
||||
urlutils.invalid_url_error(url, "start download")
|
||||
return
|
||||
req = QNetworkRequest(url)
|
||||
if user_agent is not None:
|
||||
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
||||
return self.get_request(req, **kwargs)
|
||||
|
||||
def get_mhtml(self, tab, target):
|
||||
"""Download the given tab as mhtml to the given DownloadTarget."""
|
||||
assert tab.backend == usertypes.Backend.QtWebKit
|
||||
from qutebrowser.browser.webkit import mhtml
|
||||
|
||||
if target is not None:
|
||||
mhtml.start_download_checked(target, tab=tab)
|
||||
return
|
||||
|
||||
suggested_fn = utils.sanitize_filename(tab.title() + ".mhtml")
|
||||
|
||||
filename = downloads.immediate_download_path()
|
||||
if filename is not None:
|
||||
target = downloads.FileDownloadTarget(filename)
|
||||
mhtml.start_download_checked(target, tab=tab)
|
||||
else:
|
||||
question = downloads.get_filename_question(
|
||||
suggested_filename=suggested_fn, url=tab.url(), parent=tab)
|
||||
question.answered.connect(functools.partial(
|
||||
mhtml.start_download_checked, tab=tab))
|
||||
message.global_bridge.ask(question, blocking=False)
|
||||
|
||||
def get_request(self, request, *, target=None,
|
||||
suggested_fn=None, **kwargs):
|
||||
"""Start a download with a QNetworkRequest.
|
||||
|
||||
Args:
|
||||
request: The QNetworkRequest to download.
|
||||
target: Where to save the download as downloads.DownloadTarget.
|
||||
**kwargs: Passed to _fetch_request.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
"""
|
||||
# WORKAROUND for Qt corrupting data loaded from cache:
|
||||
# https://bugreports.qt.io/browse/QTBUG-42757
|
||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||
QNetworkRequest.AlwaysNetwork)
|
||||
|
||||
if suggested_fn is not None:
|
||||
pass
|
||||
elif request.url().scheme().lower() != 'data':
|
||||
suggested_fn = urlutils.filename_from_url(request.url())
|
||||
else:
|
||||
# We might be downloading a binary blob embedded on a page or even
|
||||
# generated dynamically via javascript. We try to figure out a more
|
||||
# sensible name than the base64 content of the data.
|
||||
origin = request.originatingObject()
|
||||
try:
|
||||
origin_url = origin.url()
|
||||
except AttributeError:
|
||||
# Raised either if origin is None or some object that doesn't
|
||||
# have its own url. We're probably fine with a default fallback
|
||||
# then.
|
||||
suggested_fn = 'binary blob'
|
||||
else:
|
||||
# Use the originating URL as a base for the filename (works
|
||||
# e.g. for pdf.js).
|
||||
suggested_fn = urlutils.filename_from_url(origin_url)
|
||||
|
||||
if suggested_fn is None:
|
||||
suggested_fn = 'qutebrowser-download'
|
||||
|
||||
return self._fetch_request(request,
|
||||
target=target,
|
||||
suggested_filename=suggested_fn,
|
||||
**kwargs)
|
||||
|
||||
def _fetch_request(self, request, *, qnam=None, **kwargs):
|
||||
"""Download a QNetworkRequest to disk.
|
||||
|
||||
Args:
|
||||
request: The QNetworkRequest to download.
|
||||
qnam: The QNetworkAccessManager to use.
|
||||
**kwargs: passed to fetch().
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
"""
|
||||
if qnam is None:
|
||||
qnam = self._networkmanager
|
||||
reply = qnam.get(request)
|
||||
return self.fetch(reply, **kwargs)
|
||||
|
||||
@pyqtSlot('QNetworkReply')
|
||||
def fetch(self, reply, *, target=None, auto_remove=False,
|
||||
suggested_filename=None, prompt_download_directory=None):
|
||||
"""Download a QNetworkReply to disk.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply to download.
|
||||
target: Where to save the download as downloads.DownloadTarget.
|
||||
auto_remove: Whether to remove the download even if
|
||||
downloads.remove_finished is set to -1.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
"""
|
||||
if not suggested_filename:
|
||||
try:
|
||||
suggested_filename = target.suggested_filename()
|
||||
except downloads.NoFilenameError:
|
||||
_, suggested_filename = http.parse_content_disposition(reply)
|
||||
log.downloads.debug("fetch: {} -> {}".format(reply.url(),
|
||||
suggested_filename))
|
||||
download = DownloadItem(reply, manager=self)
|
||||
self._init_item(download, auto_remove, suggested_filename)
|
||||
|
||||
if target is not None:
|
||||
download.set_target(target)
|
||||
return download
|
||||
|
||||
# Neither filename nor fileobj were given
|
||||
|
||||
filename = downloads.immediate_download_path(prompt_download_directory)
|
||||
if filename is not None:
|
||||
# User doesn't want to be asked, so just use the download_dir
|
||||
target = downloads.FileDownloadTarget(filename)
|
||||
download.set_target(target)
|
||||
return download
|
||||
|
||||
# Ask the user for a filename
|
||||
question = downloads.get_filename_question(
|
||||
suggested_filename=suggested_filename, url=reply.url(),
|
||||
parent=self)
|
||||
self._init_filename_question(question, download)
|
||||
message.global_bridge.ask(question, blocking=False)
|
||||
|
||||
return download
|
||||
|
||||
def has_downloads_with_nam(self, nam):
|
||||
"""Check if the DownloadManager has any downloads with the given QNAM.
|
||||
|
||||
Args:
|
||||
nam: The QNetworkAccessManager to check.
|
||||
|
||||
Return:
|
||||
A boolean.
|
||||
"""
|
||||
assert nam.adopted_downloads == 0
|
||||
for download in self.downloads:
|
||||
if download._uses_nam(nam): # pylint: disable=protected-access
|
||||
nam.adopt_download(download)
|
||||
return nam.adopted_downloads
|
||||
@@ -1,422 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Backend-independent qute://* code.
|
||||
|
||||
Module attributes:
|
||||
pyeval_output: The output of the last :pyeval command.
|
||||
_HANDLERS: The handlers registered via decorators.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib.parse
|
||||
import textwrap
|
||||
import pkg_resources
|
||||
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg)
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
pyeval_output = ":pyeval was never called"
|
||||
|
||||
|
||||
_HANDLERS = {}
|
||||
|
||||
|
||||
class NoHandlerFound(Exception):
|
||||
|
||||
"""Raised when no handler was found for the given URL."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class QuteSchemeOSError(Exception):
|
||||
|
||||
"""Called when there was an OSError inside a handler."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class QuteSchemeError(Exception):
|
||||
|
||||
"""Exception to signal that a handler should return an ErrorReply.
|
||||
|
||||
Attributes correspond to the arguments in
|
||||
networkreply.ErrorNetworkReply.
|
||||
|
||||
Attributes:
|
||||
errorstring: Error string to print.
|
||||
error: Numerical error value.
|
||||
"""
|
||||
|
||||
def __init__(self, errorstring, error):
|
||||
self.errorstring = errorstring
|
||||
self.error = error
|
||||
super().__init__(errorstring)
|
||||
|
||||
|
||||
class Redirect(Exception):
|
||||
|
||||
"""Exception to signal a redirect should happen.
|
||||
|
||||
Attributes:
|
||||
url: The URL to redirect to, as a QUrl.
|
||||
"""
|
||||
|
||||
def __init__(self, url):
|
||||
super().__init__(url.toDisplayString())
|
||||
self.url = url
|
||||
|
||||
|
||||
class add_handler: # pylint: disable=invalid-name
|
||||
|
||||
"""Decorator to register a qute://* URL handler.
|
||||
|
||||
Attributes:
|
||||
_name: The 'foo' part of qute://foo
|
||||
backend: Limit which backends the handler can run with.
|
||||
"""
|
||||
|
||||
def __init__(self, name, backend=None):
|
||||
self._name = name
|
||||
self._backend = backend
|
||||
self._function = None
|
||||
|
||||
def __call__(self, function):
|
||||
self._function = function
|
||||
_HANDLERS[self._name] = self.wrapper
|
||||
return function
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self._backend is not None and objects.backend != self._backend:
|
||||
return self.wrong_backend_handler(*args, **kwargs)
|
||||
else:
|
||||
return self._function(*args, **kwargs)
|
||||
|
||||
def wrong_backend_handler(self, url):
|
||||
"""Show an error page about using the invalid backend."""
|
||||
html = jinja.render('error.html',
|
||||
title="Error while opening qute://url",
|
||||
url=url.toDisplayString(),
|
||||
error='{} is not available with this '
|
||||
'backend'.format(url.toDisplayString()))
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def data_for_url(url):
|
||||
"""Get the data to show for the given URL.
|
||||
|
||||
Args:
|
||||
url: The QUrl to show.
|
||||
|
||||
Return:
|
||||
A (mimetype, data) tuple.
|
||||
"""
|
||||
path = url.path()
|
||||
host = url.host()
|
||||
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
|
||||
log.misc.debug("url: {}, path: {}, host {}".format(
|
||||
url.toDisplayString(), path, host))
|
||||
if path and not host:
|
||||
new_url = QUrl()
|
||||
new_url.setScheme('qute')
|
||||
new_url.setHost(path)
|
||||
new_url.setPath('/')
|
||||
if new_url.host(): # path was a valid host
|
||||
raise Redirect(new_url)
|
||||
|
||||
try:
|
||||
handler = _HANDLERS[host]
|
||||
except KeyError:
|
||||
raise NoHandlerFound(url)
|
||||
|
||||
try:
|
||||
mimetype, data = handler(url)
|
||||
except OSError as e:
|
||||
# FIXME:qtwebengine how to handle this?
|
||||
raise QuteSchemeOSError(e)
|
||||
except QuteSchemeError as e:
|
||||
raise
|
||||
|
||||
assert mimetype is not None, url
|
||||
if mimetype == 'text/html' and isinstance(data, str):
|
||||
# We let handlers return HTML as text
|
||||
data = data.encode('utf-8', errors='xmlcharrefreplace')
|
||||
|
||||
return mimetype, data
|
||||
|
||||
|
||||
@add_handler('bookmarks')
|
||||
def qute_bookmarks(_url):
|
||||
"""Handler for qute://bookmarks. Display all quickmarks / bookmarks."""
|
||||
bookmarks = sorted(objreg.get('bookmark-manager').marks.items(),
|
||||
key=lambda x: x[1]) # Sort by title
|
||||
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
|
||||
key=lambda x: x[0]) # Sort by name
|
||||
|
||||
html = jinja.render('bookmarks.html',
|
||||
title='Bookmarks',
|
||||
bookmarks=bookmarks,
|
||||
quickmarks=quickmarks)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def history_data(start_time, offset=None):
|
||||
"""Return history data.
|
||||
|
||||
Arguments:
|
||||
start_time: select history starting from this timestamp.
|
||||
offset: number of items to skip
|
||||
"""
|
||||
# history atimes are stored as ints, ensure start_time is not a float
|
||||
start_time = int(start_time)
|
||||
hist = objreg.get('web-history')
|
||||
if offset is not None:
|
||||
entries = hist.entries_before(start_time, limit=1000, offset=offset)
|
||||
else:
|
||||
# end is 24hrs earlier than start
|
||||
end_time = start_time - 24*60*60
|
||||
entries = hist.entries_between(end_time, start_time)
|
||||
|
||||
return [{"url": e.url, "title": e.title or e.url, "time": e.atime}
|
||||
for e in entries]
|
||||
|
||||
|
||||
@add_handler('history')
|
||||
def qute_history(url):
|
||||
"""Handler for qute://history. Display and serve history."""
|
||||
if url.path() == '/data':
|
||||
try:
|
||||
offset = QUrlQuery(url).queryItemValue("offset")
|
||||
offset = int(offset) if offset else None
|
||||
except ValueError as e:
|
||||
raise QuteSchemeError("Query parameter offset is invalid", e)
|
||||
# Use start_time in query or current time.
|
||||
try:
|
||||
start_time = QUrlQuery(url).queryItemValue("start_time")
|
||||
start_time = float(start_time) if start_time else time.time()
|
||||
except ValueError as e:
|
||||
raise QuteSchemeError("Query parameter start_time is invalid", e)
|
||||
|
||||
return 'text/html', json.dumps(history_data(start_time, offset))
|
||||
else:
|
||||
if not config.val.content.javascript.enabled:
|
||||
return 'text/plain', b'JavaScript is required for qute://history'
|
||||
return 'text/html', jinja.render(
|
||||
'history.html',
|
||||
title='History',
|
||||
gap_interval=config.val.history_gap_interval
|
||||
)
|
||||
|
||||
|
||||
@add_handler('javascript')
|
||||
def qute_javascript(url):
|
||||
"""Handler for qute://javascript.
|
||||
|
||||
Return content of file given as query parameter.
|
||||
"""
|
||||
path = url.path()
|
||||
if path:
|
||||
path = "javascript" + os.sep.join(path.split('/'))
|
||||
return 'text/html', utils.read_file(path, binary=False)
|
||||
else:
|
||||
raise QuteSchemeError("No file specified", ValueError())
|
||||
|
||||
|
||||
@add_handler('pyeval')
|
||||
def qute_pyeval(_url):
|
||||
"""Handler for qute://pyeval."""
|
||||
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('version')
|
||||
@add_handler('verizon')
|
||||
def qute_version(_url):
|
||||
"""Handler for qute://version."""
|
||||
html = jinja.render('version.html', title='Version info',
|
||||
version=version.version(),
|
||||
copyright=qutebrowser.__copyright__)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('plainlog')
|
||||
def qute_plainlog(url):
|
||||
"""Handler for qute://plainlog.
|
||||
|
||||
An optional query parameter specifies the minimum log level to print.
|
||||
For example, qute://log?level=warning prints warnings and errors.
|
||||
Level can be one of: vdebug, debug, info, warning, error, critical.
|
||||
"""
|
||||
if log.ram_handler is None:
|
||||
text = "Log output was disabled."
|
||||
else:
|
||||
try:
|
||||
level = urllib.parse.parse_qs(url.query())['level'][0]
|
||||
except KeyError:
|
||||
level = 'vdebug'
|
||||
text = log.ram_handler.dump_log(html=False, level=level)
|
||||
html = jinja.render('pre.html', title='log', content=text)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('log')
|
||||
def qute_log(url):
|
||||
"""Handler for qute://log.
|
||||
|
||||
An optional query parameter specifies the minimum log level to print.
|
||||
For example, qute://log?level=warning prints warnings and errors.
|
||||
Level can be one of: vdebug, debug, info, warning, error, critical.
|
||||
"""
|
||||
if log.ram_handler is None:
|
||||
html_log = None
|
||||
else:
|
||||
try:
|
||||
level = urllib.parse.parse_qs(url.query())['level'][0]
|
||||
except KeyError:
|
||||
level = 'vdebug'
|
||||
html_log = log.ram_handler.dump_log(html=True, level=level)
|
||||
|
||||
html = jinja.render('log.html', title='log', content=html_log)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('gpl')
|
||||
def qute_gpl(_url):
|
||||
"""Handler for qute://gpl. Return HTML content as string."""
|
||||
return 'text/html', utils.read_file('html/LICENSE.html')
|
||||
|
||||
|
||||
@add_handler('help')
|
||||
def qute_help(url):
|
||||
"""Handler for qute://help."""
|
||||
urlpath = url.path()
|
||||
if not urlpath or urlpath == '/':
|
||||
urlpath = 'index.html'
|
||||
else:
|
||||
urlpath = urlpath.lstrip('/')
|
||||
if not docutils.docs_up_to_date(urlpath):
|
||||
message.error("Your documentation is outdated! Please re-run "
|
||||
"scripts/asciidoc2html.py.")
|
||||
|
||||
path = 'html/doc/{}'.format(urlpath)
|
||||
if urlpath.endswith('.png'):
|
||||
return 'image/png', utils.read_file(path, binary=True)
|
||||
|
||||
try:
|
||||
data = utils.read_file(path)
|
||||
except OSError:
|
||||
# No .html around, let's see if we find the asciidoc
|
||||
asciidoc_path = path.replace('.html', '.asciidoc')
|
||||
if asciidoc_path.startswith('html/doc/'):
|
||||
asciidoc_path = asciidoc_path.replace('html/doc/', '../doc/help/')
|
||||
|
||||
try:
|
||||
asciidoc = utils.read_file(asciidoc_path)
|
||||
except OSError:
|
||||
asciidoc = None
|
||||
|
||||
if asciidoc is None:
|
||||
raise
|
||||
|
||||
preamble = textwrap.dedent("""
|
||||
There was an error loading the documentation!
|
||||
|
||||
This most likely means the documentation was not generated
|
||||
properly. If you are running qutebrowser from the git repository,
|
||||
please (re)run scripts/asciidoc2html.py and reload this page.
|
||||
|
||||
If you're running a released version this is a bug, please use
|
||||
:report to report it.
|
||||
|
||||
Falling back to the plaintext version.
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
||||
""")
|
||||
return 'text/plain', (preamble + asciidoc).encode('utf-8')
|
||||
else:
|
||||
return 'text/html', data
|
||||
|
||||
|
||||
@add_handler('backend-warning')
|
||||
def qute_backend_warning(_url):
|
||||
"""Handler for qute://backend-warning."""
|
||||
html = jinja.render('backend-warning.html',
|
||||
distribution=version.distribution(),
|
||||
Distribution=version.Distribution,
|
||||
version=pkg_resources.parse_version,
|
||||
title="Legacy backend warning")
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
def _qute_settings_set(url):
|
||||
"""Handler for qute://settings/set."""
|
||||
query = QUrlQuery(url)
|
||||
option = query.queryItemValue('option', QUrl.FullyDecoded)
|
||||
value = query.queryItemValue('value', QUrl.FullyDecoded)
|
||||
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/727
|
||||
if option == 'content.javascript.enabled' and value == 'false':
|
||||
msg = ("Refusing to disable javascript via qute://settings "
|
||||
"as it needs javascript support.")
|
||||
message.error(msg)
|
||||
return 'text/html', b'error: ' + msg.encode('utf-8')
|
||||
|
||||
try:
|
||||
config.instance.set_str(option, value, save_yaml=True)
|
||||
return 'text/html', b'ok'
|
||||
except configexc.Error as e:
|
||||
message.error(str(e))
|
||||
return 'text/html', b'error: ' + str(e).encode('utf-8')
|
||||
|
||||
|
||||
@add_handler('settings')
|
||||
def qute_settings(url):
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
if url.path() == '/set':
|
||||
return _qute_settings_set(url)
|
||||
|
||||
html = jinja.render('settings.html', title='settings',
|
||||
configdata=configdata,
|
||||
confget=config.instance.get_str)
|
||||
return 'text/html', html
|
||||
|
||||
|
||||
@add_handler('configdiff')
|
||||
def qute_configdiff(url):
|
||||
"""Handler for qute://configdiff."""
|
||||
if url.path() == '/old':
|
||||
try:
|
||||
return 'text/html', configdiff.get_diff()
|
||||
except OSError as e:
|
||||
error = (b'Failed to read old config: ' +
|
||||
str(e.strerror).encode('utf-8'))
|
||||
return 'text/plain', error
|
||||
else:
|
||||
data = config.instance.dump_userconfig().encode('utf-8')
|
||||
return 'text/plain', data
|
||||
@@ -1,262 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Various utilities shared between webpage/webview subclasses."""
|
||||
|
||||
import html
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
|
||||
|
||||
class CallSuper(Exception):
|
||||
|
||||
"""Raised when the caller should call the superclass instead."""
|
||||
|
||||
|
||||
def custom_headers():
|
||||
"""Get the combined custom headers."""
|
||||
headers = {}
|
||||
|
||||
dnt_config = config.val.content.headers.do_not_track
|
||||
if dnt_config is not None:
|
||||
dnt = b'1' if dnt_config else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
conf_headers = config.val.content.headers.custom
|
||||
for header, value in conf_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
|
||||
accept_language = config.val.content.headers.accept_language
|
||||
if accept_language is not None:
|
||||
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
||||
|
||||
return sorted(headers.items())
|
||||
|
||||
|
||||
def authentication_required(url, authenticator, abort_on):
|
||||
"""Ask a prompt for an authentication question."""
|
||||
realm = authenticator.realm()
|
||||
if realm:
|
||||
msg = '<b>{}</b> says:<br/>{}'.format(
|
||||
html.escape(url.toDisplayString()), html.escape(realm))
|
||||
else:
|
||||
msg = '<b>{}</b> needs authentication'.format(
|
||||
html.escape(url.toDisplayString()))
|
||||
answer = message.ask(title="Authentication required", text=msg,
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
abort_on=abort_on)
|
||||
if answer is not None:
|
||||
authenticator.setUser(answer.user)
|
||||
authenticator.setPassword(answer.password)
|
||||
return answer
|
||||
|
||||
|
||||
def javascript_confirm(url, js_msg, abort_on):
|
||||
"""Display a javascript confirm prompt."""
|
||||
log.js.debug("confirm: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
html.escape(js_msg))
|
||||
ans = message.ask('Javascript confirm', msg,
|
||||
mode=usertypes.PromptMode.yesno,
|
||||
abort_on=abort_on)
|
||||
return bool(ans)
|
||||
|
||||
|
||||
def javascript_prompt(url, js_msg, default, abort_on):
|
||||
"""Display a javascript prompt."""
|
||||
log.js.debug("prompt: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
if not config.val.content.javascript.prompt:
|
||||
return (False, "")
|
||||
|
||||
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
html.escape(js_msg))
|
||||
answer = message.ask('Javascript prompt', msg,
|
||||
mode=usertypes.PromptMode.text,
|
||||
default=default,
|
||||
abort_on=abort_on)
|
||||
|
||||
if answer is None:
|
||||
return (False, "")
|
||||
else:
|
||||
return (True, answer)
|
||||
|
||||
|
||||
def javascript_alert(url, js_msg, abort_on):
|
||||
"""Display a javascript alert."""
|
||||
log.js.debug("alert: {}".format(js_msg))
|
||||
if config.val.content.javascript.modal_dialog:
|
||||
raise CallSuper
|
||||
|
||||
if not config.val.content.javascript.alert:
|
||||
return
|
||||
|
||||
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
|
||||
html.escape(js_msg))
|
||||
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
|
||||
abort_on=abort_on)
|
||||
|
||||
|
||||
def javascript_log_message(level, source, line, msg):
|
||||
"""Display a JavaScript log message."""
|
||||
logstring = "[{}:{}] {}".format(source, line, msg)
|
||||
# Needs to line up with the values allowed for the
|
||||
# content.javascript.log setting.
|
||||
logmap = {
|
||||
'none': lambda arg: None,
|
||||
'debug': log.js.debug,
|
||||
'info': log.js.info,
|
||||
'warning': log.js.warning,
|
||||
'error': log.js.error,
|
||||
}
|
||||
logger = logmap[config.val.content.javascript.log[level.name]]
|
||||
logger(logstring)
|
||||
|
||||
|
||||
def ignore_certificate_errors(url, errors, abort_on):
|
||||
"""Display a certificate error question.
|
||||
|
||||
Args:
|
||||
url: The URL the errors happened in
|
||||
errors: A list of QSslErrors or QWebEngineCertificateErrors
|
||||
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.val.content.ssl_strict
|
||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
|
||||
for error in errors:
|
||||
assert error.is_overridable(), repr(error)
|
||||
|
||||
if ssl_strict == 'ask':
|
||||
err_template = jinja.environment.from_string("""
|
||||
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
|
||||
<ul>
|
||||
{% for err in errors %}
|
||||
<li>{{err}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
""".strip())
|
||||
msg = err_template.render(url=url, errors=errors)
|
||||
|
||||
ignore = message.ask(title="Certificate errors - continue?", text=msg,
|
||||
mode=usertypes.PromptMode.yesno, default=False,
|
||||
abort_on=abort_on)
|
||||
if ignore is None:
|
||||
# prompt aborted
|
||||
ignore = False
|
||||
return ignore
|
||||
elif ssl_strict is False:
|
||||
log.webview.debug("ssl_strict is False, only warning about errors")
|
||||
for err in errors:
|
||||
# FIXME we might want to use warn here (non-fatal error)
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/114
|
||||
message.error('Certificate error: {}'.format(err))
|
||||
return True
|
||||
elif ssl_strict is True:
|
||||
return False
|
||||
else:
|
||||
raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
|
||||
raise AssertionError("Not reached")
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
"""Handle a feature permission request.
|
||||
|
||||
Args:
|
||||
url: The URL the request was done for.
|
||||
option: An option name to check.
|
||||
msg: A string like "show notifications"
|
||||
yes_action: A callable to call if the request was approved
|
||||
no_action: A callable to call if the request was denied
|
||||
abort_on: A list of signals which interrupt the question.
|
||||
|
||||
Return:
|
||||
The Question object if a question was asked, None otherwise.
|
||||
"""
|
||||
config_val = config.instance.get(option)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
text = "Allow the website at <b>{}</b> to {}?".format(
|
||||
html.escape(url.toDisplayString()), msg)
|
||||
else:
|
||||
text = "Allow the website to {}?".format(msg)
|
||||
|
||||
return message.confirm_async(
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
cancel_action=no_action, abort_on=abort_on,
|
||||
title='Permission request', text=text)
|
||||
elif config_val:
|
||||
yes_action()
|
||||
return None
|
||||
else:
|
||||
no_action()
|
||||
return None
|
||||
|
||||
|
||||
def get_tab(win_id, target):
|
||||
"""Get a tab widget for the given usertypes.ClickTarget.
|
||||
|
||||
Args:
|
||||
win_id: The window ID to open new tabs in
|
||||
target: A usertypes.ClickTarget
|
||||
"""
|
||||
if target == usertypes.ClickTarget.tab:
|
||||
win_id = win_id
|
||||
bg_tab = False
|
||||
elif target == usertypes.ClickTarget.tab_bg:
|
||||
win_id = win_id
|
||||
bg_tab = True
|
||||
elif target == usertypes.ClickTarget.window:
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
||||
window.show()
|
||||
win_id = window.win_id
|
||||
bg_tab = False
|
||||
else:
|
||||
raise ValueError("Invalid ClickTarget {}".format(target))
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
return tabbed_browser.tabopen(url=None, background=bg_tab)
|
||||
|
||||
|
||||
def get_user_stylesheet():
|
||||
"""Get the combined user-stylesheet."""
|
||||
css = ''
|
||||
stylesheets = config.val.content.user_stylesheets
|
||||
|
||||
for filename in stylesheets:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
css += f.read()
|
||||
|
||||
if not config.val.scrolling.bar:
|
||||
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
|
||||
|
||||
return css
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2017 Antoni Boucher <bouanto@zoho.com>
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2015-2016 Antoni Boucher <bouanto@zoho.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -26,7 +26,6 @@ to a file on shutdown, so it makes sense to keep them as strings here.
|
||||
"""
|
||||
|
||||
import os
|
||||
import html
|
||||
import os.path
|
||||
import functools
|
||||
import collections
|
||||
@@ -34,7 +33,7 @@ import collections
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, QObject
|
||||
|
||||
from qutebrowser.utils import (message, usertypes, qtutils, urlutils,
|
||||
standarddir, objreg, log)
|
||||
standarddir, objreg)
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.misc import lineparser
|
||||
|
||||
@@ -73,19 +72,28 @@ class UrlMarkManager(QObject):
|
||||
|
||||
Attributes:
|
||||
marks: An OrderedDict of all quickmarks/bookmarks.
|
||||
_lineparser: The LineParser used for the marks
|
||||
_lineparser: The LineParser used for the marks, or None
|
||||
(when qutebrowser is started with -c '').
|
||||
|
||||
Signals:
|
||||
changed: Emitted when anything changed.
|
||||
added: Emitted when a new quickmark/bookmark was added.
|
||||
removed: Emitted when an existing quickmark/bookmark was removed.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal()
|
||||
added = pyqtSignal(str, str)
|
||||
removed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Initialize and read quickmarks."""
|
||||
super().__init__(parent)
|
||||
|
||||
self.marks = collections.OrderedDict()
|
||||
self._lineparser = None
|
||||
|
||||
if standarddir.config() is None:
|
||||
return
|
||||
|
||||
self._init_lineparser()
|
||||
for line in self._lineparser:
|
||||
@@ -106,8 +114,10 @@ class UrlMarkManager(QObject):
|
||||
|
||||
def save(self):
|
||||
"""Save the marks to disk."""
|
||||
self._lineparser.data = [' '.join(tpl) for tpl in self.marks.items()]
|
||||
self._lineparser.save()
|
||||
if self._lineparser is not None:
|
||||
self._lineparser.data = [' '.join(tpl)
|
||||
for tpl in self.marks.items()]
|
||||
self._lineparser.save()
|
||||
|
||||
def delete(self, key):
|
||||
"""Delete a quickmark/bookmark.
|
||||
@@ -117,6 +127,7 @@ class UrlMarkManager(QObject):
|
||||
"""
|
||||
del self.marks[key]
|
||||
self.changed.emit()
|
||||
self.removed.emit(key)
|
||||
|
||||
|
||||
class QuickmarkManager(UrlMarkManager):
|
||||
@@ -128,6 +139,7 @@ class QuickmarkManager(UrlMarkManager):
|
||||
- self.marks maps names to URLs.
|
||||
- changed gets emitted with the name as first argument and the URL as
|
||||
second argument.
|
||||
- removed gets emitted with the name as argument.
|
||||
"""
|
||||
|
||||
def _init_lineparser(self):
|
||||
@@ -143,56 +155,56 @@ class QuickmarkManager(UrlMarkManager):
|
||||
try:
|
||||
key, url = line.rsplit(maxsplit=1)
|
||||
except ValueError:
|
||||
message.error("Invalid quickmark '{}'".format(line))
|
||||
message.error('current', "Invalid quickmark '{}'".format(line))
|
||||
else:
|
||||
self.marks[key] = url
|
||||
|
||||
def prompt_save(self, url):
|
||||
def prompt_save(self, win_id, url):
|
||||
"""Prompt for a new quickmark name to be added and add it.
|
||||
|
||||
Args:
|
||||
win_id: The current window ID.
|
||||
url: The quickmark url as a QUrl.
|
||||
"""
|
||||
if not url.isValid():
|
||||
urlutils.invalid_url_error(url, "save quickmark")
|
||||
urlutils.invalid_url_error(win_id, url, "save quickmark")
|
||||
return
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
message.ask_async(
|
||||
"Add quickmark:", usertypes.PromptMode.text,
|
||||
functools.partial(self.quickmark_add, urlstr),
|
||||
text="Please enter a quickmark name for<br/><b>{}</b>".format(
|
||||
html.escape(url.toDisplayString())))
|
||||
win_id, "Add quickmark:", usertypes.PromptMode.text,
|
||||
functools.partial(self.quickmark_add, win_id, urlstr))
|
||||
|
||||
@cmdutils.register(instance='quickmark-manager')
|
||||
def quickmark_add(self, url, name):
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def quickmark_add(self, win_id, url, name):
|
||||
"""Add a new quickmark.
|
||||
|
||||
You can view all saved quickmarks on the
|
||||
link:qute://bookmarks[bookmarks page].
|
||||
|
||||
Args:
|
||||
win_id: The window ID to display the errors in.
|
||||
url: The url to add as quickmark.
|
||||
name: The name for the new quickmark.
|
||||
"""
|
||||
# We don't raise cmdexc.CommandError here as this can be called async
|
||||
# via prompt_save.
|
||||
if not name:
|
||||
message.error("Can't set mark with empty name!")
|
||||
message.error(win_id, "Can't set mark with empty name!")
|
||||
return
|
||||
if not url:
|
||||
message.error("Can't set mark with empty URL!")
|
||||
message.error(win_id, "Can't set mark with empty URL!")
|
||||
return
|
||||
|
||||
def set_mark():
|
||||
"""Really set the quickmark."""
|
||||
self.marks[name] = url
|
||||
self.changed.emit()
|
||||
log.misc.debug("Added quickmark {} for {}".format(name, url))
|
||||
self.added.emit(name, url)
|
||||
|
||||
if name in self.marks:
|
||||
message.confirm_async(
|
||||
title="Override existing quickmark?",
|
||||
yes_action=set_mark, default=True)
|
||||
win_id, "Override existing quickmark?", set_mark, default=True)
|
||||
else:
|
||||
set_mark()
|
||||
|
||||
@@ -236,6 +248,7 @@ class BookmarkManager(UrlMarkManager):
|
||||
- self.marks maps URLs to titles.
|
||||
- changed gets emitted with the URL as first argument and the title as
|
||||
second argument.
|
||||
- removed gets emitted with the URL as argument.
|
||||
"""
|
||||
|
||||
def _init_lineparser(self):
|
||||
@@ -259,18 +272,12 @@ class BookmarkManager(UrlMarkManager):
|
||||
elif len(parts) == 1:
|
||||
self.marks[parts[0]] = ''
|
||||
|
||||
def add(self, url, title, *, toggle=False):
|
||||
def add(self, url, title):
|
||||
"""Add a new bookmark.
|
||||
|
||||
Args:
|
||||
url: The url to add as bookmark.
|
||||
title: The title for the new bookmark.
|
||||
toggle: remove the bookmark instead of raising an error if it
|
||||
already exists.
|
||||
|
||||
Return:
|
||||
True if the bookmark was added, and False if it was
|
||||
removed (only possible if toggle is True).
|
||||
"""
|
||||
if not url.isValid():
|
||||
errstr = urlutils.get_errstring(url)
|
||||
@@ -279,12 +286,8 @@ class BookmarkManager(UrlMarkManager):
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
|
||||
if urlstr in self.marks:
|
||||
if toggle:
|
||||
del self.marks[urlstr]
|
||||
return False
|
||||
else:
|
||||
raise AlreadyExistsError("Bookmark already exists!")
|
||||
raise AlreadyExistsError("Bookmark already exists!")
|
||||
else:
|
||||
self.marks[urlstr] = title
|
||||
self.changed.emit()
|
||||
return True
|
||||
self.added.emit(title, urlstr)
|
||||
|
||||
@@ -1,423 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Generic web element related code.
|
||||
|
||||
Module attributes:
|
||||
Group: Enum for different kinds of groups.
|
||||
SELECTORS: CSS selectors for different groups of elements.
|
||||
"""
|
||||
|
||||
import collections.abc
|
||||
|
||||
from PyQt5.QtCore import QUrl, Qt, QEvent, QTimer
|
||||
from PyQt5.QtGui import QMouseEvent
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
||||
|
||||
|
||||
Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
|
||||
|
||||
|
||||
SELECTORS = {
|
||||
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
|
||||
'frame, iframe, link, [onclick], [onmousedown], [role=link], '
|
||||
'[role=option], [role=button], img'),
|
||||
Group.links: 'a[href], area[href], link[href], [role=link][href]',
|
||||
Group.images: 'img',
|
||||
Group.url: '[src], [href]',
|
||||
Group.inputs: ('input[type=text], input[type=email], input[type=url], '
|
||||
'input[type=tel], input[type=number], '
|
||||
'input[type=password], input[type=search], '
|
||||
'input:not([type]), textarea'),
|
||||
}
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
"""Base class for WebElement errors."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AbstractWebElement(collections.abc.MutableMapping):
|
||||
|
||||
"""A wrapper around QtWebKit/QtWebEngine web element.
|
||||
|
||||
Attributes:
|
||||
tab: The tab associated with this element.
|
||||
"""
|
||||
|
||||
def __init__(self, tab):
|
||||
self._tab = tab
|
||||
|
||||
def __eq__(self, other):
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __getitem__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
raise NotImplementedError
|
||||
|
||||
def __delitem__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __iter__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __len__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
html = utils.compact_text(self.outer_xml(), 500)
|
||||
except Error:
|
||||
html = None
|
||||
return utils.get_repr(self, html=html)
|
||||
|
||||
def has_frame(self):
|
||||
"""Check if this element has a valid frame attached."""
|
||||
raise NotImplementedError
|
||||
|
||||
def geometry(self):
|
||||
"""Get the geometry for this element."""
|
||||
raise NotImplementedError
|
||||
|
||||
def classes(self):
|
||||
"""Get a list of classes assigned to this element."""
|
||||
raise NotImplementedError
|
||||
|
||||
def tag_name(self):
|
||||
"""Get the tag name of this element.
|
||||
|
||||
The returned name will always be lower-case.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def outer_xml(self):
|
||||
"""Get the full HTML representation of this element."""
|
||||
raise NotImplementedError
|
||||
|
||||
def value(self):
|
||||
"""Get the value attribute for this element, or None."""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_value(self, value):
|
||||
"""Set the element value."""
|
||||
raise NotImplementedError
|
||||
|
||||
def insert_text(self, text):
|
||||
"""Insert the given text into the element."""
|
||||
raise NotImplementedError
|
||||
|
||||
def rect_on_view(self, *, elem_geometry=None, no_js=False):
|
||||
"""Get the geometry of the element relative to the webview.
|
||||
|
||||
Uses the getClientRects() JavaScript method to obtain the collection of
|
||||
rectangles containing the element and returns the first rectangle which
|
||||
is large enough (larger than 1px times 1px). If all rectangles returned
|
||||
by getClientRects() are too small, falls back to elem.rect_on_view().
|
||||
|
||||
Skipping of small rectangles is due to <a> elements containing other
|
||||
elements with "display:block" style, see
|
||||
https://github.com/qutebrowser/qutebrowser/issues/1298
|
||||
|
||||
Args:
|
||||
elem_geometry: The geometry of the element, or None.
|
||||
Calling QWebElement::geometry is rather expensive so
|
||||
we want to avoid doing it twice.
|
||||
no_js: Fall back to the Python implementation
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_writable(self):
|
||||
"""Check whether an element is writable."""
|
||||
return not ('disabled' in self or 'readonly' in self)
|
||||
|
||||
def is_content_editable(self):
|
||||
"""Check if an element has a contenteditable attribute.
|
||||
|
||||
Args:
|
||||
elem: The QWebElement to check.
|
||||
|
||||
Return:
|
||||
True if the element has a contenteditable attribute,
|
||||
False otherwise.
|
||||
"""
|
||||
try:
|
||||
return self['contenteditable'].lower() not in ['false', 'inherit']
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def _is_editable_object(self):
|
||||
"""Check if an object-element is editable."""
|
||||
if 'type' not in self:
|
||||
log.webelem.debug("<object> without type clicked...")
|
||||
return False
|
||||
objtype = self['type'].lower()
|
||||
if objtype.startswith('application/') or 'classid' in self:
|
||||
# Let's hope flash/java stuff has an application/* mimetype OR
|
||||
# at least a classid attribute. Oh, and let's hope images/...
|
||||
# DON'T have a classid attribute. HTML sucks.
|
||||
log.webelem.debug("<object type='{}'> clicked.".format(objtype))
|
||||
return config.val.input.insert_mode.plugins
|
||||
else:
|
||||
# Image/Audio/...
|
||||
return False
|
||||
|
||||
def _is_editable_input(self):
|
||||
"""Check if an input-element is editable.
|
||||
|
||||
Return:
|
||||
True if the element is editable, False otherwise.
|
||||
"""
|
||||
try:
|
||||
objtype = self['type'].lower()
|
||||
except KeyError:
|
||||
return self.is_writable()
|
||||
else:
|
||||
if objtype in ['text', 'email', 'url', 'tel', 'number', 'password',
|
||||
'search']:
|
||||
return self.is_writable()
|
||||
else:
|
||||
return False
|
||||
|
||||
def _is_editable_classes(self):
|
||||
"""Check if an element is editable based on its classes.
|
||||
|
||||
Return:
|
||||
True if the element is editable, False otherwise.
|
||||
"""
|
||||
# Beginnings of div-classes which are actually some kind of editor.
|
||||
classes = {
|
||||
'div': ['CodeMirror', # Javascript editor over a textarea
|
||||
'kix-', # Google Docs editor
|
||||
'ace_'], # http://ace.c9.io/
|
||||
'pre': ['CodeMirror'],
|
||||
}
|
||||
relevant_classes = classes[self.tag_name()]
|
||||
for klass in self.classes():
|
||||
if any([klass.strip().startswith(e) for e in relevant_classes]):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_editable(self, strict=False):
|
||||
"""Check whether we should switch to insert mode for this element.
|
||||
|
||||
Args:
|
||||
strict: Whether to do stricter checking so only fields where we can
|
||||
get the value match, for use with the :editor command.
|
||||
|
||||
Return:
|
||||
True if we should switch to insert mode, False otherwise.
|
||||
"""
|
||||
roles = ('combobox', 'textbox')
|
||||
log.webelem.debug("Checking if element is editable: {}".format(
|
||||
repr(self)))
|
||||
tag = self.tag_name()
|
||||
if self.is_content_editable() and self.is_writable():
|
||||
return True
|
||||
elif self.get('role', None) in roles and self.is_writable():
|
||||
return True
|
||||
elif tag == 'input':
|
||||
return self._is_editable_input()
|
||||
elif tag == 'textarea':
|
||||
return self.is_writable()
|
||||
elif tag in ['embed', 'applet']:
|
||||
# Flash/Java/...
|
||||
return config.val.input.insert_mode.plugins and not strict
|
||||
elif tag == 'object':
|
||||
return self._is_editable_object() and not strict
|
||||
elif tag in ['div', 'pre']:
|
||||
return self._is_editable_classes() and not strict
|
||||
return False
|
||||
|
||||
def is_text_input(self):
|
||||
"""Check if this element is some kind of text box."""
|
||||
roles = ('combobox', 'textbox')
|
||||
tag = self.tag_name()
|
||||
return self.get('role', None) in roles or tag in ['input', 'textarea']
|
||||
|
||||
def remove_blank_target(self):
|
||||
"""Remove target from link."""
|
||||
raise NotImplementedError
|
||||
|
||||
def resolve_url(self, baseurl):
|
||||
"""Resolve the URL in the element's src/href attribute.
|
||||
|
||||
Args:
|
||||
baseurl: The URL to base relative URLs on as QUrl.
|
||||
|
||||
Return:
|
||||
A QUrl with the absolute URL, or None.
|
||||
"""
|
||||
if baseurl.isRelative():
|
||||
raise ValueError("Need an absolute base URL!")
|
||||
|
||||
for attr in ['href', 'src']:
|
||||
if attr in self:
|
||||
text = self[attr].strip()
|
||||
break
|
||||
else:
|
||||
return None
|
||||
|
||||
url = QUrl(text)
|
||||
if not url.isValid():
|
||||
return None
|
||||
if url.isRelative():
|
||||
url = baseurl.resolved(url)
|
||||
qtutils.ensure_valid(url)
|
||||
return url
|
||||
|
||||
def is_link(self):
|
||||
"""Return True if this AbstractWebElement is a link."""
|
||||
href_tags = ['a', 'area', 'link']
|
||||
return self.tag_name() in href_tags and 'href' in self
|
||||
|
||||
def _mouse_pos(self):
|
||||
"""Get the position to click/hover."""
|
||||
# Click the center of the largest square fitting into the top/left
|
||||
# corner of the rectangle, this will help if part of the <a> element
|
||||
# is hidden behind other elements
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/1005
|
||||
rect = self.rect_on_view()
|
||||
if rect.width() > rect.height():
|
||||
rect.setWidth(rect.height())
|
||||
else:
|
||||
rect.setHeight(rect.width())
|
||||
pos = rect.center()
|
||||
if pos.x() < 0 or pos.y() < 0:
|
||||
raise Error("Element position is out of view!")
|
||||
return pos
|
||||
|
||||
def _move_text_cursor(self):
|
||||
"""Move cursor to end after clicking."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _click_fake_event(self, click_target):
|
||||
"""Send a fake click event to the element."""
|
||||
pos = self._mouse_pos()
|
||||
|
||||
log.webelem.debug("Sending fake click to {!r} at position {} with "
|
||||
"target {}".format(self, pos, click_target))
|
||||
|
||||
modifiers = {
|
||||
usertypes.ClickTarget.normal: Qt.NoModifier,
|
||||
usertypes.ClickTarget.window: Qt.AltModifier | Qt.ShiftModifier,
|
||||
usertypes.ClickTarget.tab: Qt.ControlModifier,
|
||||
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
|
||||
}
|
||||
if config.val.tabs.background:
|
||||
modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
|
||||
else:
|
||||
modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
|
||||
|
||||
events = [
|
||||
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
||||
Qt.NoModifier),
|
||||
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
|
||||
Qt.LeftButton, modifiers[click_target]),
|
||||
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
|
||||
Qt.NoButton, modifiers[click_target]),
|
||||
]
|
||||
|
||||
for evt in events:
|
||||
self._tab.send_event(evt)
|
||||
|
||||
QTimer.singleShot(0, self._move_text_cursor)
|
||||
|
||||
def _click_editable(self, click_target):
|
||||
"""Fake a click on an editable input field."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _click_js(self, click_target):
|
||||
"""Fake a click by using the JS .click() method."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _click_href(self, click_target):
|
||||
"""Fake a click on an element with a href by opening the link."""
|
||||
baseurl = self._tab.url()
|
||||
url = self.resolve_url(baseurl)
|
||||
if url is None:
|
||||
self._click_fake_event(click_target)
|
||||
return
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._tab.win_id)
|
||||
|
||||
if click_target in [usertypes.ClickTarget.tab,
|
||||
usertypes.ClickTarget.tab_bg]:
|
||||
background = click_target == usertypes.ClickTarget.tab_bg
|
||||
tabbed_browser.tabopen(url, background=background)
|
||||
elif click_target == usertypes.ClickTarget.window:
|
||||
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
||||
window.show()
|
||||
window.tabbed_browser.tabopen(url)
|
||||
else:
|
||||
raise ValueError("Unknown ClickTarget {}".format(click_target))
|
||||
|
||||
def click(self, click_target, *, force_event=False):
|
||||
"""Simulate a click on the element.
|
||||
|
||||
Args:
|
||||
click_target: A usertypes.ClickTarget member, what kind of click
|
||||
to simulate.
|
||||
force_event: Force generating a fake mouse event.
|
||||
"""
|
||||
log.webelem.debug("Clicking {!r} with click_target {}, force_event {}"
|
||||
.format(self, click_target, force_event))
|
||||
|
||||
if force_event:
|
||||
self._click_fake_event(click_target)
|
||||
return
|
||||
|
||||
if click_target == usertypes.ClickTarget.normal:
|
||||
if self.is_link():
|
||||
log.webelem.debug("Clicking via JS click()")
|
||||
self._click_js(click_target)
|
||||
elif self.is_editable(strict=True):
|
||||
log.webelem.debug("Clicking via JS focus()")
|
||||
self._click_editable(click_target)
|
||||
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
|
||||
'clicking input')
|
||||
else:
|
||||
self._click_fake_event(click_target)
|
||||
elif click_target in [usertypes.ClickTarget.tab,
|
||||
usertypes.ClickTarget.tab_bg,
|
||||
usertypes.ClickTarget.window]:
|
||||
if self.is_link():
|
||||
self._click_href(click_target)
|
||||
else:
|
||||
self._click_fake_event(click_target)
|
||||
else:
|
||||
raise ValueError("Unknown ClickTarget {}".format(click_target))
|
||||
|
||||
def hover(self):
|
||||
"""Simulate a mouse hover over the element."""
|
||||
pos = self._mouse_pos()
|
||||
event = QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
||||
Qt.NoModifier)
|
||||
self._tab.send_event(event)
|
||||