Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9d5b2df0c | ||
|
|
d6fd5a817e | ||
|
|
301186d407 | ||
|
|
ab121a98da | ||
|
|
a463038834 | ||
|
|
22761b4373 | ||
|
|
78f6f3a0e1 | ||
|
|
6166ea51e2 | ||
|
|
4d4065dfac | ||
|
|
81f350ee99 | ||
|
|
4b98e6e9ce | ||
|
|
11f8ab1f85 | ||
|
|
0449da048f | ||
|
|
c78e938dea | ||
|
|
99fb8a5d87 | ||
|
|
b1e0b8f119 | ||
|
|
8d49e001e9 | ||
|
|
965c176acf | ||
|
|
896da1c27e | ||
|
|
8f33fcfc52 | ||
|
|
91b0a33ab0 | ||
|
|
b059f4058f | ||
|
|
b63ce438b4 |
@@ -1,17 +0,0 @@
|
||||
shallow_clone: true
|
||||
version: '{branch}-{build}'
|
||||
cache:
|
||||
- C:\projects\qutebrowser\.cache
|
||||
build: off
|
||||
environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
matrix:
|
||||
- TESTENV: py34
|
||||
- TESTENV: unittests-frozen
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
- C:\Python27\python -u scripts\dev\ci\appveyor_install.py
|
||||
|
||||
test_script:
|
||||
- C:\Python34\Scripts\tox -e %TESTENV%
|
||||
18
.coveragerc
@@ -1,18 +0,0 @@
|
||||
[run]
|
||||
source = qutebrowser
|
||||
branch = true
|
||||
omit =
|
||||
qutebrowser/__main__.py
|
||||
*/__init__.py
|
||||
qutebrowser/resources.py
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
def __repr__
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
if __name__ == ["']__main__["']:
|
||||
|
||||
[xml]
|
||||
output=coverage.xml
|
||||
@@ -1,17 +0,0 @@
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
max_line_length = 79
|
||||
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
||||
[*.feature]
|
||||
max_line_length = 9999
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
qutebrowser/3rdparty/pdfjs/*
|
||||
49
.eslintrc
@@ -1,49 +0,0 @@
|
||||
# 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
|
||||
61
.flake8
@@ -1,48 +1,19 @@
|
||||
# vim: ft=dosini fileencoding=utf-8:
|
||||
|
||||
[flake8]
|
||||
exclude = .*,__pycache__,resources.py
|
||||
# E128: continuation line under-indented for visual indent
|
||||
# E226: missing whitespace around arithmetic operator
|
||||
# E241: Multiple spaces after ,
|
||||
# E265: Block comment should start with '#'
|
||||
# E501: Line too long
|
||||
# E402: module level import not at top of file
|
||||
# E266: too many leading '#' for block comment
|
||||
# checked by pylint:
|
||||
# F401: Unused import
|
||||
# N802: function name should be lowercase
|
||||
# P101: format string does contain unindexed parameters
|
||||
# P102: docstring does contain unindexed parameters
|
||||
# P103: other string does contain unindexed parameters
|
||||
# D102: Missing docstring in public method (will be handled by others)
|
||||
# D103: Missing docstring in public function (will be handled by others)
|
||||
# D104: Missing docstring in public package (will be handled by others)
|
||||
# D105: Missing docstring in magic method (will be handled by others)
|
||||
# D209: Blank line before closing """ (removed from PEP257)
|
||||
# D211: No blank lines allowed before class docstring
|
||||
# (PEP257 got changed, but let's stick to the old standard)
|
||||
# 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,
|
||||
F401,
|
||||
N802,
|
||||
P101,P102,P103,
|
||||
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/unit/browser/webkit/http/test_content_disposition.py : +D400
|
||||
scripts/dev/ci/appveyor_install.py : +FI53
|
||||
copyright-check = True
|
||||
copyright-regexp = # Copyright [\d-]+ .*
|
||||
copyright-min-file-size = 110
|
||||
# E501: Line too long
|
||||
# F821: undefined name
|
||||
# F841: unused variable
|
||||
# E222: Multiple spaces after operator
|
||||
# F811: Redifiniton
|
||||
# W292: No newline at end of file
|
||||
# E701: multiple statements on one line
|
||||
# E702: multiple statements on one line
|
||||
# E225: missing whitespace around operator
|
||||
ignore=E241,E265,F401,E501,F821,F841,E222,F811,W292,E701,E702,E225
|
||||
max_complexity = 12
|
||||
exclude = ez_setup.py
|
||||
|
||||
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,4 +0,0 @@
|
||||
Please remember to mention your version info (qutebrowser, Qt, PyQt,
|
||||
OS/distribution) from the `qute:version` page or `qutebrowser --version`
|
||||
|
||||
---
|
||||
24
.gitignore
vendored
@@ -1,7 +1,5 @@
|
||||
__pycache__
|
||||
*.py~
|
||||
*.pyc
|
||||
*.swp
|
||||
/build
|
||||
/dist
|
||||
/qutebrowser.egg-info
|
||||
@@ -12,28 +10,6 @@ __pycache__
|
||||
/setuptools-*.egg
|
||||
/setuptools-*.zip
|
||||
/qutebrowser/git-commit-id
|
||||
/qutebrowser/3rdparty
|
||||
/doc/*.html
|
||||
/README.html
|
||||
/CHANGELOG.html
|
||||
/CONTRIBUTING.html
|
||||
/FAQ.html
|
||||
/INSTALL.html
|
||||
/qutebrowser/html/doc/
|
||||
/.venv
|
||||
/.coverage
|
||||
/htmlcov
|
||||
/coverage.xml
|
||||
/.coverage.*
|
||||
/.tox
|
||||
/testresults.html
|
||||
/.cache
|
||||
/.testmondata
|
||||
/.hypothesis
|
||||
/prof
|
||||
/venv
|
||||
TODO
|
||||
/scripts/testbrowser_cpp/Makefile
|
||||
/scripts/testbrowser_cpp/main.o
|
||||
/scripts/testbrowser_cpp/testbrowser
|
||||
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
[pydocstyle]
|
||||
# Disabled checks:
|
||||
# D102: Missing docstring in public method (will be handled by others)
|
||||
# D103: Missing docstring in public function (will be handled by others)
|
||||
# D104: Missing docstring in public package (will be handled by others)
|
||||
# D105: Missing docstring in magic method (will be handled by others)
|
||||
# D209: Blank line before closing """ (removed from PEP257)
|
||||
# D211: No blank lines allowed before class docstring
|
||||
# (PEP257 got changed, but let's stick to the old standard)
|
||||
# D402: First line should not be function's signature (false-positives)
|
||||
ignore = D102,D103,D104,D105,D209,D211,D402
|
||||
match = (?!resources|test_*).*\.py
|
||||
inherit = false
|
||||
60
.pylintrc
@@ -1,59 +1,44 @@
|
||||
# vim: ft=dosini fileencoding=utf-8:
|
||||
|
||||
[MASTER]
|
||||
ignore=resources.py
|
||||
extension-pkg-whitelist=PyQt5,sip
|
||||
load-plugins=qute_pylint.config,
|
||||
qute_pylint.modeline,
|
||||
qute_pylint.openencoding,
|
||||
qute_pylint.settrace,
|
||||
pylint.extensions.bad_builtin,
|
||||
pylint.extensions.docstyle
|
||||
persistent=n
|
||||
ignore=ez_setup.py
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
enable=all
|
||||
disable=no-self-use,
|
||||
super-on-old-class,
|
||||
old-style-class,
|
||||
abstract-class-little-used,
|
||||
bad-builtin,
|
||||
star-args,
|
||||
fixme,
|
||||
global-statement,
|
||||
no-init,
|
||||
locally-disabled,
|
||||
locally-enabled,
|
||||
too-many-ancestors,
|
||||
too-few-public-methods,
|
||||
too-many-public-methods,
|
||||
cyclic-import,
|
||||
bad-option-value,
|
||||
bad-continuation,
|
||||
too-many-instance-attributes,
|
||||
unnecessary-lambda,
|
||||
blacklisted-name,
|
||||
too-many-lines,
|
||||
logging-format-interpolation,
|
||||
broad-except,
|
||||
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
|
||||
too-many-lines
|
||||
|
||||
[BASIC]
|
||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||
module-rgx=(__)?[a-z][a-z0-9_]*(__)?$
|
||||
function-rgx=([a-z_][a-z0-9_]{2,30}|setUpModule|tearDownModule)$
|
||||
const-rgx=[A-Za-z_][A-Za-z0-9_]{0,30}$
|
||||
method-rgx=[a-z_][A-Za-z0-9_]{1,50}$
|
||||
method-rgx=[a-z_][A-Za-z0-9_]{2,40}$
|
||||
attr-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
argument-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||
docstring-min-length=3
|
||||
no-docstring-rgx=(^_|^main$)
|
||||
class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$
|
||||
inlinevar-rgx=[a-z_][a-z0-9_]*$
|
||||
|
||||
[FORMAT]
|
||||
max-line-length=79
|
||||
ignore-long-lines=(<?https?://|^# Copyright 201\d|# (pylint|flake8): disable=)
|
||||
expected-line-ending-format=LF
|
||||
ignore-long-lines=<?https?://
|
||||
|
||||
[SIMILARITIES]
|
||||
min-similarity-lines=8
|
||||
@@ -61,16 +46,11 @@ min-similarity-lines=8
|
||||
[VARIABLES]
|
||||
dummy-variables-rgx=_.*
|
||||
|
||||
[CLASSES]
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
[DESIGN]
|
||||
max-args=10
|
||||
|
||||
[CLASSES]
|
||||
valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
[TYPECHECK]
|
||||
# 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
|
||||
ignored-classes=WebElementWrapper,AnsiCodes,UnsetObject
|
||||
|
||||
20
.run_checks
Normal file
@@ -0,0 +1,20 @@
|
||||
# vim: ft=dosini
|
||||
|
||||
[DEFAULT]
|
||||
targets=qutebrowser,scripts
|
||||
|
||||
[pep257]
|
||||
# D102: Docstring missing, will be handled by others
|
||||
# D209: Blank line before closing """ (removed from PEP257)
|
||||
# D402: First line should not be function's signature (false-positives)
|
||||
disable=D102,D209,D402
|
||||
exclude=test_.*
|
||||
|
||||
[pylint]
|
||||
args=--output-format=colorized,--reports=no,--rcfile=.pylintrc
|
||||
plugins=config,crlf,modeline,settrace,openencoding
|
||||
exclude=resources.py
|
||||
|
||||
[flake8]
|
||||
args=--config=.flake8
|
||||
exclude=resources.py
|
||||
69
.travis.yml
@@ -1,69 +0,0 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
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=ubuntu-xenial
|
||||
services: docker
|
||||
- os: osx
|
||||
env: TESTENV=py35
|
||||
- os: linux
|
||||
env: TESTENV=pylint
|
||||
- os: linux
|
||||
env: TESTENV=flake8
|
||||
- os: linux
|
||||
env: TESTENV=docs
|
||||
- os: linux
|
||||
env: TESTENV=vulture
|
||||
- os: linux
|
||||
env: TESTENV=misc
|
||||
- os: linux
|
||||
env: TESTENV=pyroma
|
||||
- os: linux
|
||||
env: TESTENV=check-manifest
|
||||
- os: linux
|
||||
env: TESTENV=eslint
|
||||
allow_failures:
|
||||
- os: osx
|
||||
env: TESTENV=py35
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $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
|
||||
|
||||
script:
|
||||
- bash scripts/dev/ci/travis_run.sh
|
||||
|
||||
after_success:
|
||||
- '[[ $TESTENV == *-cov ]] && codecov -e TESTENV -X gcov'
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
- https://buildtimetrend.herokuapp.com/travis
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#qutebrowser-dev"
|
||||
on_success: always
|
||||
on_failure: always
|
||||
skip_join: true
|
||||
template:
|
||||
- "%{repository}#%{build_number} (%{branch} - %{commit} : %{author}): %{message}"
|
||||
- "%{compare_url} - %{build_url}"
|
||||
@@ -1,928 +0,0 @@
|
||||
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].
|
||||
|
||||
// tags:
|
||||
// `Added` for new features.
|
||||
// `Changed` for changes in existing functionality.
|
||||
// `Deprecated` for once-stable features removed in upcoming releases.
|
||||
// `Removed` for deprecated features removed in this release.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v0.8.2
|
||||
------
|
||||
|
||||
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
|
||||
- Fixed local storage not working (and possible other bugs) when using a
|
||||
relative path with `--basedir`.
|
||||
- Fixed crash when deleting a quickmark with Ctrl-D
|
||||
- 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 crash when cancelling a download after doing `:prompt-open-download`
|
||||
- Fixed crash when writing a download to disk fails with
|
||||
`:prompt-open-download`.
|
||||
- Fixed HTML5 video playback on Windows
|
||||
|
||||
v0.8.1
|
||||
------
|
||||
|
||||
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 OS X .app
|
||||
|
||||
v0.8.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New `:repeat-command` command (mapped to `.`) to repeat the last command.
|
||||
Note that two former default bundings conflict with that binding, unbinding
|
||||
them via `:unbind .i` and `:unbind .o` is recommended.
|
||||
- New `qute:bookmarks` page which displays all bookmarks and quickmarks.
|
||||
- New `:prompt-open-download` (bound to `Ctrl-X`) which can be used to open a
|
||||
download directly when getting the filename prompt.
|
||||
- New `{host}` replacement for tab- and window titles which evaluates
|
||||
to the current host.
|
||||
- New default binding `;t` for `:hint input`.
|
||||
- New variables `$QUTE_CONFIG_DIR`, `$QUTE_DATA_DIR` and
|
||||
`$QUTE_DOWNLOAD_DIR` available for userscripts.
|
||||
- New option `ui` -> `status-position` to configure the position of the
|
||||
status bar (top/bottom).
|
||||
- New `--pdf <filename>` argument for `:print` WHICH can be used to generate a
|
||||
PDF without a dialog.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- `:scroll-perc` now prefers a count over the argument given to it, which means
|
||||
`gg` can be used with a count.
|
||||
- Aliases can now use `;;` to have an alias which executed multiple commands.
|
||||
- `:edit-url` now does nothing if the URL isn't changed in the spawned editor.
|
||||
- `:bookmark-add` can now be passed a URL and title to add that as a bookmark
|
||||
rather than the current page.
|
||||
- New `taskadd` userscript to add a taskwarrior task annotated with the
|
||||
current URL.
|
||||
- `:bookmark-del` and `:quickmark-del` now delete the current page's URL if none
|
||||
is given.
|
||||
|
||||
Fixed
|
||||
-----
|
||||
|
||||
- Compatibility with PyQt 5.7
|
||||
- Fixed some configuration values being lost when a config option gets removed
|
||||
from qutebrowser's code.
|
||||
- Fix crash when downloading with a full disk
|
||||
- Using `:jump-mark` (e.g. `''`) when the current URL is invalid doesn't crash
|
||||
anymore.
|
||||
|
||||
Removed
|
||||
-------
|
||||
|
||||
- The ability to display status messages from webpages, as well as the related
|
||||
`ui -> display-statusbar-messages` setting.
|
||||
- The `general -> wrap-search` setting as searches now always wrap.
|
||||
According to a quick straw poll and prior crash logs, almost nobody is using
|
||||
`wrap-search = false`, and turning off wrapping is not possible with
|
||||
QtWebEngine.
|
||||
- `:edit-url` now doesn't accept a count anymore as its behavior was confusing
|
||||
and it doesn't make much sense to add a count.
|
||||
|
||||
v0.7.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New `:edit-url` command to edit the URL in an external editor.
|
||||
- New `network -> custom-headers` setting to send custom headers with every request.
|
||||
- New `{url:pretty}` commandline replacement which gets replaced by the decoded URL.
|
||||
- New marks to remember a scroll position:
|
||||
- New `:jump-mark` command to jump to a mark, bound to `'`
|
||||
- New `:set-mark` command to set a mark, bound to ```(backtick)
|
||||
- The `'` mark gets set when moving away (hinting link with anchor, searching, etc.) so you can move back with `''`
|
||||
- New `--force-color` argument to force colored logging even if stdout is not a
|
||||
terminal
|
||||
- New `:messages` command to show error messages
|
||||
- New pop-up showing possible keybinding when the first key of a keychain is
|
||||
pressed. This can be turned off using `:set ui keyhint-blacklist *`.
|
||||
- New `hints -> auto-follow-timeout` setting to ignore keypresses after
|
||||
following a hint when filtering in number mode.
|
||||
- New `:history-clear` command to clear the entire history
|
||||
- New `hints -> find-implementation` to select which implementation (JS/Python)
|
||||
should be used to find hints on a page. The `javascript` implementation is
|
||||
better, but slower.
|
||||
- New `inputs` group for `:hint` to hint text input fields.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- qutebrowser got a new (slightly updated) logo
|
||||
- `:tab-focus` can now take a negative index to focus the nth tab counted from
|
||||
the right.
|
||||
- `:yank` can now yank the pretty/decoded URL by adding `--pretty`
|
||||
- `: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.
|
||||
- Show favicons as window icon with `tabs-are-windows` set.
|
||||
- `:bind <key>` without a command now shows the existing binding
|
||||
- The optional `colorlog` dependency got removed, as qutebrowser now displays
|
||||
colored logs without it.
|
||||
- URLs are now shown decoded when hovering.
|
||||
- Keybindings are now shown in the command completion
|
||||
- Improved behavior when pasting multiple lines
|
||||
- Rapid hints can now also be used for the `normal` hint target, which can be
|
||||
useful with javascript click handlers or checkboxes which don't actually open
|
||||
a new page.
|
||||
- `:zoom-in` or `:zoom-out` (`+`/`-`) with a too large count now zooms to the
|
||||
smallest/largest zoom instead of doing nothing.
|
||||
- The commandline now accepts partially typed commands if they're unique.
|
||||
- Number hints are now kept filtered after following a hint in rapid mode.
|
||||
- Number hints are now renumbered after filtering
|
||||
- Number hints can now be filtered with multiple space-separated search terms
|
||||
- `hints -> scatter` is now ignored for number hints
|
||||
- Better history implementation which also stores titles.
|
||||
As a consequence, URLs which redirect to another URL are now added to the
|
||||
history too, marked with a `-r` suffix to the timestamp field.
|
||||
|
||||
Fixed
|
||||
-----
|
||||
|
||||
- Fixed using `:hint links spawn` with flags - you can now use things like the
|
||||
`-v` argument for `:spawn` or pass flags to the spawned commands.
|
||||
- Various fixes for hinting corner-cases where following a link didn't work or
|
||||
the hint was drawn at the wrong position.
|
||||
- Fixed crash when downloading from a URL with SSL errors
|
||||
- Close file handles correctly when a download failed
|
||||
- Fixed crash when using `;Y` (`:hint links yank-primary`) on a system without
|
||||
primary selection
|
||||
- Don't display quit confirmation with finished downloads
|
||||
- Fixed updating the tab index in the statusbar when opening a background tab
|
||||
- Fixed a crash when entering `:-- ` in the commandline
|
||||
- Fixed `:debug-console` with PyQt 5.6
|
||||
- Fixed qutebrowser not starting when `sys.stderr` is `None`
|
||||
- Fixed crash when cancelling a download which belongs to an MHTML download
|
||||
- Fixed rebinding of keybindings being case-sensitive
|
||||
- Fix for tab indicators getting lost when moving tabs
|
||||
- Fixed handling of backspace in number hinting mode
|
||||
- Fixed `FileNotFoundError` when starting in some cases on old Qt versions
|
||||
- Fixed sharing of cookies between tabs when `private-browsing` is enabled
|
||||
- Toggling values with `:set` now uses lower-case values
|
||||
- Hints now work with (non-standard) links with spaces around the URL
|
||||
- Strip off trailing spaces for history entries with no title
|
||||
|
||||
v0.6.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed crash when using `:tab-{prev,next,focus}` right after closing the last
|
||||
tab with `last-close` set to `close`.
|
||||
- Fixed crash when doing `:undo` in a new instance with `tabs -> last-close` set
|
||||
to `default-page`.
|
||||
- Fixed crash when starting with --cachedir=""
|
||||
- Fixed crash in some circumstances when using dictionary hints
|
||||
- Fixed various crashes related to PyQt 5.6
|
||||
|
||||
v0.6.1
|
||||
-----
|
||||
|
||||
Fixed
|
||||
~~~~~~
|
||||
|
||||
- Fixed broken cheatsheet image which was missing from package
|
||||
- Fixed occasional crash when switching/disconnecting monitors
|
||||
- Fixed crash when downloading non-ascii files with a broken locale (`LC_ALL=C`)
|
||||
- Added workaround for a Qt/PyQt bug which is too weird to describe here
|
||||
|
||||
v0.6.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New `:buffer` command to easily switch tabs by name. This is not bound to a
|
||||
key by default for existing users due to a conflict with the `gt`/`gT`
|
||||
bindings (which are now removed from the default bindings).
|
||||
You can bind it by hand by running `:bind -f gt set-cmd-text -s :buffer`.
|
||||
- New `--quiet` argument for the `:debug-pyeval` command to not open a tab with
|
||||
the results. Note `:debug-pyeval` is still only intended for debugging.
|
||||
- The completion now matches each entered word separately.
|
||||
- A new command `:paste-primary` got added to paste the primary selection, and
|
||||
`<Shift-Insert>` got added as a binding so it pastes primary rather than
|
||||
clipboard.
|
||||
- New mode `word` for `hints -> mode` which uses a dictionary and link-texts
|
||||
for hints instead of single characters.
|
||||
- New `--all` argument for `:download-cancel` to cancel all running downloads.
|
||||
- New `password_fill` userscript to fill passwords using the `pass` executable.
|
||||
- New `current` hinting mode which forces opening hints in the current tab
|
||||
(even with `target="_blank"`)
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Pasting multiple lines via `:paste` now opens each line in a new tab.
|
||||
- `:navigate increment/decrement` now preserves leading zeroes in URLs.
|
||||
- `general -> editor` can now also handle `{}` inside another argument (e.g. to open `vim` via `termite`)
|
||||
- Improved performance when scrolling with many tabs open.
|
||||
- Shift-Insert now also pastes primary selection for prompts.
|
||||
- `:download-remove --all` got un-deprecated to provide symmetry with
|
||||
`:download-cancel --all`. It does the same as `:download-clear`.
|
||||
- Improved detection of URLs/search terms when pasting multiple lines.
|
||||
- Don't remove `qutebrowser-editor-*` temporary file if editor subprocess crashed
|
||||
- Userscripts are also searched in `/usr/share/qutebrowser/userscripts`.
|
||||
- Blocked hosts are now also read from a `blocked-hosts` file in the config dir
|
||||
(e.g. `~/.config/qutebrowser/blocked-hosts`).
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed starting with -c "".
|
||||
- Fixed crash when a tab is closed twice via javascript (e.g. Dropbox
|
||||
authentication dialogs)
|
||||
- Fixed crash when a notification/geolocation prompt is answered after closing
|
||||
the tab it belongs to.
|
||||
- Fixed crash when downloading a file without any path information (e.g a
|
||||
magnet link).
|
||||
- Fixed crashes when opening an empty URL (e.g. via pasting).
|
||||
- Fixed validation of duplicate values in `hints -> chars`.
|
||||
- Fixed crash when PDF.js was partially installed.
|
||||
- Fixed crash when XDG_DOWNLOAD_DIR was not an absolute path.
|
||||
- Fixed very long filenames when downloading `data://`-URLs.
|
||||
- Fixed ugly UI fonts on Windows when Liberation Mono is installed
|
||||
- Fixed crash when unbinding key from a section which doesn't exist in the config
|
||||
- Fixed report window after a segfault
|
||||
- Fixed some directory browser issues on Windows
|
||||
- Fixed crash when closing a window with a finished download and delayed
|
||||
`remove-finished-downloads` setting.
|
||||
- Fixed crash when hitting `<Tab>` then `<Ctrl-C>` on pages without keyboard
|
||||
focus.
|
||||
- Fixed "Frame load interrupted by policy change" error showing up when
|
||||
downloading files with Qt 5.6.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The `gt`/`gT` bindings (luakit-like alternatives to `J`/`K`) were removed
|
||||
(except for existing configs) to make room for the `gt` binding to show
|
||||
buffers.
|
||||
|
||||
v0.5.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed completion for various config values when using `:set`.
|
||||
- Fixed config validation for various config values.
|
||||
- Prevented an error being logged when a website with HTTP authentication was
|
||||
opened on Windows.
|
||||
|
||||
v0.5.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Ability to preview PDFs using pdf.js in the browser if it's installed. This
|
||||
is disabled by default and can be enabled using the
|
||||
`content -> pdfjs-enabled` setting.
|
||||
- New setting `ui -> hide-wayland-decoration` to hide the window decoration
|
||||
when using wayland.
|
||||
- New userscripts in `misc/userscripts`:
|
||||
- `open_download` to easily open a file in your downloads folder.
|
||||
- `view_in_mpv` to open a video in mpv and remove it from the page.
|
||||
- `qutedmenu` and `dmenu_qutebrowser` to select URLs via dmenu
|
||||
- New setting `content -> host-blocking-whitelist` to whitelist certain domains
|
||||
from the adblocker.
|
||||
- `{scroll_pos}` can now be used in `ui -> window-title-format` and
|
||||
`tabs -> title-format`.
|
||||
- New setting `general -> url-incdec-segments` to configure which segments of
|
||||
the URL should be affected by `:navigate increment/decrement`.
|
||||
- New `--target` argument to specify how URLs should be opened in an existing
|
||||
instance.
|
||||
- New setting `statusbar.url.fg.success.https` to set the foreground color for
|
||||
the URL when a page was loaded via HTTPS.
|
||||
- The scrollbar in the completion is now styled, and the following new options
|
||||
got added:
|
||||
* `completion -> scrollbar-width`
|
||||
* `completion -> scrollbar-padding`
|
||||
* `colors -> completion.scrollbar.fg`
|
||||
* `colors -> completion.scrollbar.bg`
|
||||
- New value `none` for options taking a color system so they don't display a
|
||||
gradient:
|
||||
* `colors -> tabs.indicator.system`
|
||||
* `colors -> downloads.fg.system`
|
||||
* `colors -> downloads.bg.system`
|
||||
- New command `:download-retry` to retry a failed download.
|
||||
- New command `:download-clear` which replaces `:download-remove --all`.
|
||||
- `:set-cmd-text` has a new `--append` argument to append to the current
|
||||
statusbar text.
|
||||
- qutebrowser now uses `~/.netrc` if available to authenticate via HTTP.
|
||||
- New `:fake-key` command to send a fake keypress to a website or to
|
||||
qutebrowser.
|
||||
- New `--mhtml` argument for `:download` to download a page including all
|
||||
ressources as MHTML file.
|
||||
- New option `tabs -> title-alignment` to change the alignment of tab titles.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The `colors -> tabs.bg/fg.selected` option got split into
|
||||
`tabs.bg/fg.selected.odd/even`.
|
||||
- `:spawn --userscript` and `:hint` with the `userscript` target now look up
|
||||
relative paths in `~/.local/share/qutebrowser/userscripts` or
|
||||
`$XDG_DATA_DIR`. Using a binary in `$PATH` won't work anymore with
|
||||
`--userscript`.
|
||||
- New design for error pages
|
||||
- Link filtering for hints now checks if the text is contained anywhere in
|
||||
the link, and matches case-insensitively.
|
||||
- The `ui -> remove-finished-downloads` option got changed to an integer and
|
||||
now takes a time (in milliseconds) to keep the download around after it's
|
||||
finished. When set to `-1`, downloads are never removed.
|
||||
- The `:follow-hint` command now optionally takes the keystring of a hint to
|
||||
follow.
|
||||
- `:scroll-px` now doesn't take floats anymore, which made little sense.
|
||||
- Updated the user agent list for the `:set network user-agent` completion.
|
||||
- Starting with `--debug` doesn't log `VDEBUG` messages anymore (add
|
||||
`--loglevel VDEBUG` to get them).
|
||||
- `:debug-console` now hides the console if it's already shown.
|
||||
- `:yank-selected` now doesn't log the selected text anymore.
|
||||
- `general -> log-javascript-console` got changed from a boolean to an option
|
||||
taking a loglevel (`none`, `info`, `debug`).
|
||||
- `:tab-move +/-` now wraps around if `tabs -> wrap` is `true`.
|
||||
- When a subprocess (like launched by `:spawn`) fails, its stdout/stderr is now
|
||||
logged to the console.
|
||||
- A search engine name can now contain any non-space character, like dashes.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- `:download-remove --all` is now deprecated and `:download-clear` should be
|
||||
used instead.
|
||||
- `:download <url> <destination>` is now deprecated and
|
||||
`:download --dest <destination> <url>` should be used instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- `:scroll` with two pixel-arguments (deprecated in v0.3.0)
|
||||
- The `:run-userscript` command (deprecated in v0.2.0)
|
||||
- The `rapid` and `rapid-win` targets for `:hint` (deprecated in v0.2.0)
|
||||
- The `:cancel-download` command (deprecated in v0.2.0)
|
||||
- The `:download-page` command (deprecated in v0.2.0)
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed retrying of downloads which were started in a now closed tab.
|
||||
- Fixed displaying of web history if `web-history-max-items` is set to -1.
|
||||
- Cloned tabs now don't display favicons anymore if show-favicons is False.
|
||||
- Fixed a crash when clicking a bookmark name and pressing `Ctrl-D`.
|
||||
- Fixed a crash when a website presents a very small favicon.
|
||||
- Fixed prompting for download directory when
|
||||
`storage -> prompt-download-directory` was unset.
|
||||
- Fixed crash when using `:follow-hint` outside of hint mode.
|
||||
- Fixed crash when using `:set foo bar?` with invalid section/option.
|
||||
- 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 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
|
||||
`forward-unbound-keys` set to `auto`.
|
||||
- Fixed restarting after a crash is reported.
|
||||
- Removed `.pyc` files accidentally contained in source releases.
|
||||
|
||||
v0.4.1
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Adjusted AppArmor config for the IPC changes in v0.4.0.
|
||||
- Fixed atime update frequency for IPC file.
|
||||
- Worked around a Qt issue where middle-clicking caused scrolling with a
|
||||
touchpad to restart at the beginning of the page.
|
||||
- The `completion -> web-history-max-items` setting is now also respected for
|
||||
items added after starting qutebrowser.
|
||||
- Search terms are now shared between different tabs again
|
||||
- Tests (a reduced subset of them) now run correctly again when DISPLAY is not
|
||||
set.
|
||||
- Fixed an issue causing qutebrowser to crash with Python 3.5 as soon as an ad
|
||||
was blocked.
|
||||
- Fixed an issue causing qutebrowser to not start with more recent Python 3.4
|
||||
versions (e.g. on Debian experimental).
|
||||
- Fixed various `PendingDeprecationWarnings` shown with Python 3.5.
|
||||
|
||||
v0.4.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New bookmark functionality (similar to quickmarks without a name).
|
||||
* New command `:bookmark-add` to bookmark the current page (bound to `M`).
|
||||
* New command `:bookmark-load` to load a bookmark (bound to `gb`/`gB`/`wB`).
|
||||
- New (hidden) command `:completion-item-del` (bound to `<Ctrl-D>`) to delete
|
||||
the current item in the completion (for quickmarks/bookmarks).
|
||||
- New settings `tabs -> padding` and `tabs -> indicator-tabbing` to control the
|
||||
size/padding of the tabbar.
|
||||
- New setting `ui -> statusbar-padding` to control the size/padding of the
|
||||
status bar.
|
||||
- New setting `network -> referer-header` to configure when the referer should
|
||||
be sent (by default it's only sent while on the same domain).
|
||||
- New setting `tabs -> show` which supersedes the old `tabs -> hide-*` options
|
||||
and has an additional `switching` option which shows tab while switching
|
||||
them. There's also a new `show-switching` option to configure the timeout.
|
||||
- New setting `storage -> remember-download-directory` to remember the last
|
||||
used download directory.
|
||||
- New setting `storage -> prompt-download-directory` to download all downloads
|
||||
without asking.
|
||||
- Rapid hinting is now also possible for downloads.
|
||||
- Directory browsing via `file://` is now supported.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Some developer scripts got moved to `scripts/dev/`
|
||||
- When downloading to a FIFO or special file, a confirmation is displayed as
|
||||
this might cause qutebrowser to hang.
|
||||
- The `:yank-selected` command now works in all modes instead of just caret
|
||||
mode and is not hidden anymore.
|
||||
- `minimal_webkit_testbrowser.py` now has a `--webengine` switch to test
|
||||
QtWebEngine if it's installed.
|
||||
- The column width percentages for the completion view now depend on the
|
||||
completion model.
|
||||
- The values for `tabs -> position` and `ui -> downloads-position` got changed
|
||||
from `north`/`south`/`west/`east` to `top`/`bottom`/`left`/`right`. Existing
|
||||
configs should be adjusted automatically.
|
||||
- `:tab-focus`/`gt` now behaves like `:tab-next` if no count/index is given.
|
||||
- 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 OS X are now hidden.
|
||||
- Better support for Qt 5.5 and Python 3.5.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed a bug where cookies were saved despite qutebrowser being started in
|
||||
private browsing mode.
|
||||
- The local socket used for inter-process communication (opening new instances)
|
||||
is now ensured to only be accessible by the user on all operating systems.
|
||||
- Various corner cases for inter-process communication issues got fixed.
|
||||
- `link_pyqt.py` now should work better on untested distributions.
|
||||
- Fixed various corner-cases with crashes when reading invalid config values
|
||||
and the history file.
|
||||
- Fixed various corner-cases when setting text via an external editor.
|
||||
- Fixed potential crash when hinting a text field.
|
||||
- Fixed entering of insert mode when certain disabled text fields were clicked.
|
||||
- Fixed a crash when using `:set` with `-p` and `!` (invert value)
|
||||
- Downloads with unknown size are now handled correctly.
|
||||
- `:navigate increment/decrement` (`<Ctrl-A>`/`<Ctrl-X>`) now handles some
|
||||
corner-cases better.
|
||||
- Fixed a bug where the completion got affected by another window's completion
|
||||
if it was open in both windows.
|
||||
- Fixed a performance issue with large histories when opening previously
|
||||
unvisited websites.
|
||||
- The progress bar now doesn't cause the statusbar to change it's height
|
||||
anymore.
|
||||
- `~` is now always expanded when spawning a script.
|
||||
- Fixed various corner cases when opening links in an existing instance.
|
||||
- Fixed a race-condition causing an exception when starting qutebrowser.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The `tabs -> indicator-space` setting got removed as the new padding settings
|
||||
should be used instead.
|
||||
- The `tabs -> hide-always` and `tabs -> hide-auto` settings got merged into
|
||||
the new `tabs -> show` setting.
|
||||
|
||||
v0.3.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- New commands `:message-info`, `:message-error` and `:message-warning` to show messages in the statusbar, e.g. from a userscript.
|
||||
- New command `:scroll-px` which replaces `:scroll` for pixel-exact scrolling.
|
||||
- New command `:jseval` to run a javascript snippet on the current page.
|
||||
- New (hidden) command `:follow-selected` (bound to `Enter`/`Ctrl-Enter` by default) to follow the link which is currently selected (e.g. after searching via `/`).
|
||||
- New (hidden) command `:clear-keychain` to clear a partially entered keychain (bound to `<Escape>` by default, in addition to clearing search).
|
||||
- New setting `ui -> smooth-scrolling`.
|
||||
- New setting `content -> webgl` to enable/disable https://www.khronos.org/webgl/[WebGL].
|
||||
- New setting `content -> css-regions` to enable/disable support for http://dev.w3.org/csswg/css-regions/[CSS Regions].
|
||||
- New setting `content -> hyperlink-auditing` to enable/disable support for https://html.spec.whatwg.org/multipage/semantics.html#hyperlink-auditing[hyperlink auditing].
|
||||
- New setting `tabs -> mousewheel-tab-switching` to control mousewheel behavior on the tab bar.
|
||||
- New arguments `--datadir` and `--cachedir` to set the data/cache location.
|
||||
- New arguments `--basedir` and `--temp-basedir` (intended for debugging) to set a different base directory for all data, which allows multiple invocations.
|
||||
- New argument `--no-err-windows` to suppress all error windows.
|
||||
- New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom).
|
||||
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
|
||||
- New flag `-v`/`--verbose` for `:spawn` to print informations when the process started/exited successfully.
|
||||
- Many new color settings (foreground setting for every background setting).
|
||||
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
|
||||
- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one.
|
||||
- New setting `completion -> auto-open` to only open the completion when tab is pressed (if set to false).
|
||||
- New visual/caret mode (bound to `v`) to select text by keyboard.
|
||||
- There are now some example userscripts in `misc/userscripts`.
|
||||
- Support for Qt 5.5 and tox 2.0
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- *Breaking change for userscripts:* `QUTE_HTML` and `QUTE_TEXT` for userscripts now don't store the contents directly, and instead contain a filename.
|
||||
- The `content -> geolocation` and `notifications` settings now support a `true` value to always allow those. However, this is *not recommended*.
|
||||
- New bindings `<Ctrl-R>` (rapid), `<Ctrl-F>` (foreground) and `<Ctrl-B>` (background) to switch hint modes while hinting.
|
||||
- `<Ctrl-M>` and numpad-enter are now bound by default for bindings where `<Return>` was bound.
|
||||
- `:hint tab` and `F` now respect the `background-tabs` setting. To enforce a foreground tab (what `F` did before), use `:hint tab-fg` or `;f`.
|
||||
- `:scroll` now takes a direction argument (`up`/`down`/`left`/`right`/`top`/`bottom`/`page-up`/`page-down`) instead of two pixel arguments (`dx`/`dy`). The old form still works but is deprecated.
|
||||
- The `ui -> user-stylesheet` setting now also takes file paths relative to the config directory.
|
||||
- The `content -> cookies-accept` setting now has new `no-3rdparty` (default) and `no-unknown-3rdparty` values to block third-party cookies. The `default` value got renamed to `all`.
|
||||
- Improved startup time by reading the webpage history while qutebrowser is open.
|
||||
- The way `:spawn` splits its commandline has been changed slightly to allow commands with flags.
|
||||
- The default for the `new-instance-open-target` setting has been changed to `tab`.
|
||||
- Sessions now store zoom/scroll-position separately for each entry.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- `:scroll` with two pixel-arguments is now deprecated - `:scroll-px` should be used instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- The `--no-crash-dialog` argument which was intended for debugging only was removed as it's replaced by `--no-err-windows` which suppresses all error windows.
|
||||
- Support for Qt installations without SSL support was dropped.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Scrolling should now work more reliably on some pages where arrow keys worked but `hjkl` didn't.
|
||||
- Small improvements when checking if an input is a URL or not.
|
||||
- Fixed wrong cursor position when completing the first item in the completion.
|
||||
- Fixed exception when using search engines with {foo} in their name.
|
||||
- Fixed a bug where the same title was shown for all tabs on some systems.
|
||||
- Don't install the scripts package when installing qutebrowser.
|
||||
- Fixed searching for terms starting with a hyphen (e.g. `/-foo`)
|
||||
- Proxy authentication credentials are now remembered between different tabs.
|
||||
- Fixed updating of the tab title on pages without title.
|
||||
- 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 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/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Added missing manpage (doc/qutebrowser.1.asciidoc) to archive.
|
||||
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.0[v0.2.0]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Session support
|
||||
* new command `:session-load` to load a session.
|
||||
* new command `:session-save` to save a session.
|
||||
* new command `:session-delete` to delete a session.
|
||||
* new setting `general -> save-session` to always save the session on quit.
|
||||
* new setting `general -> session-default-name` to configure the session name to use if none is given.
|
||||
* new argument `-r`/`--restore` to specify a session to load.
|
||||
* new argument `-R`/`--override-restore` to not load a session even if one was saved.
|
||||
- New commands to manage downloads:
|
||||
* `:download` to download a URL or the current page.
|
||||
* `:download-cancel` to cancel a download.
|
||||
* `:download-delete` to delete a download from disk.
|
||||
* `:download-open` to open a finished download.
|
||||
* `:download-remove` to remove a download from the list. `:download-remove --all` or the new 'cd' keybinding can be used to clear all finished downloads.
|
||||
- History completion
|
||||
* New option `completion -> timestamp-format` to set the format used to display the history timestamps.
|
||||
* New option `completion -> web-history-max-items` to configure how many history items to show in the completion.
|
||||
* The option `completion -> history-length` for the command history got renamed to `cmd-history-max-items`.
|
||||
- Better save logic for the config/state:
|
||||
* Only save files if modified (e.g. don't overwrite the config if it was edited outside of qutebrowser and nothing was changed in qutebrowser).
|
||||
* Save things (cookies, config, quickmarks, ...) periodically all 15 seconds (time can be changed with the `general -> auto-save-interval` option).
|
||||
- Opera-like mouse rocker gestures
|
||||
* New option `input -> rocker-gestures`. When turned on, the history can be navigated back/forward by holding a mouse button and pressing the other one.
|
||||
- New `-f` option for `:reload` to reload and bypass the cache.
|
||||
- Pass more information (`QUTE_MODE`, `QUTE_SELECTED_TEXT`, `QUTE_SELECTED_HTML`, `QUTE_USER_AGENT`, `QUTE_HTML`, `QUTE_TEXT`) to userscripts.
|
||||
- New `--userscript` option to `:spawn` (which deprecates `:run-userscript`).
|
||||
- Ability to toggle a value to `:set` by appending a `!` to the value.
|
||||
- New options to hide the tab-/statusbar:
|
||||
* `tabs -> hide-always` for the tabbar
|
||||
* `ui -> hide-statusbar` for the statusbar
|
||||
- New options to configure how the tab/window titles should look:
|
||||
* `tabs -> title-format` for the tabbar
|
||||
* `ui -> window-title-format` for the window title
|
||||
- HTML5 Geolocation/Notification support:
|
||||
* New option `content -> geolocation` to permanently turn the geolocation off.
|
||||
* New option `content -> notifications` to permanently turn notifications off.
|
||||
- New options to disable javascript prompts/alerts:
|
||||
* `content -> ignore-javascript-prompt` to turn off prompts.
|
||||
* `content -> ignore-javascript-alerts` to turn off alerts.
|
||||
- Two new options to customize the behavior of hints:
|
||||
* `hints -> min-chars` to set minimum number of chars in hints.
|
||||
* `hints -> scatter` which when turned off distributes the hints sequentially (like dwb) instead of scattering their positions (like Vimium).
|
||||
- Make it possible to use `:open -[twb]` without url.
|
||||
* New option `general -> default-page` to set the page to be opened when doing that.
|
||||
- New `input -> partial-timeout` option to clear partial keystrings.
|
||||
- New option `completion -> download-path-suggestion` to configure what to show in the completion for downloads.
|
||||
- Queue messages shown in unfocused windows and show them when the window is focused.
|
||||
* New option `ui -> message-unfocused` to disable this behavior.
|
||||
- New `--relaxed-config` argument which ignores unknown options.
|
||||
- New `:tab-detach` command to open the current tab in a new window.
|
||||
- Zooming via Ctrl-Mousewheel.
|
||||
* New option `input -> mouse-zoom-divider` to control how much the page is zoomed when rotating the wheel.
|
||||
- New option (`content -> host-blocking-enabled`) to enable/disable host blocking.
|
||||
- New values `tab-bg`/`tab-bg-silent` for `new-instance-open-target` to open a background tab.
|
||||
- New `ui -> downloads-position` setting to move the downloads to the bottom.
|
||||
- New `ui -> hide-mouse-cursor` option to hide the mouse cursor inside qutebrowser.
|
||||
- New argument `-s` for qutebrowser to set a temporary config option.
|
||||
- New argument `-p` for the `:set` command to print the new value.
|
||||
- New `--rapid` option to `:hint`. The `rapid`/`rapid-win` targets are now deprecated, and `--rapid` can be used as well with the targets run/hover/userscript/spawn as well.
|
||||
- New `-f` argument to `:bind` to overwrite the old binding.
|
||||
- New `--qt-name` argument to qutebrowser which is passed to Qt to set `WM_CLASS`.
|
||||
- Alternating row colors in completion. This adds a new `colors -> completion.alternate-bg` option.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Ignore quotes with maxsplit-commands (`:open`, `:quickmark-load`, etc.) and don't quote arguments for those commands in the completions. This also means some commands needed adjustments:
|
||||
* Clear search when `:search` without arguments is given. (`:search ""` will now search for the literal text `""`)
|
||||
* Add `-s`/`--space` argument to `:set-cmd-text` (as `:set-cmd-text "foo "` will now set the literal text `"foo "`)
|
||||
- Ignore `;;` for splitting with some commands like `:bind`.
|
||||
- Add unbound (new) default keybindings to config. This also adds a new `<unbound>` special command.
|
||||
* To unbind a command keybinding without binding it to a new key, you now have to bind it to `<unbound>` or it'll be readded automatically.
|
||||
- If an SSL error is raised multiple times with the same error/certificate/host/scheme/port, the user is only asked once.
|
||||
- Jump to last instead of first item when pressing Shift-Tab the first time in the completion.
|
||||
- Add a fullscreen keybinding.
|
||||
- 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/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.
|
||||
- Don't open relative file paths with `:open`, only with commandline arguments.
|
||||
- Expand environment variables in config settings which take a file path.
|
||||
- Add a list of common user agents to the user agent setting completion.
|
||||
- Move cursor to end of textboxes when hinting.
|
||||
- Don't start searches on invalid URLs for quickmarks/startpage.
|
||||
- Various performance improvements for the completion.
|
||||
- Always open URLs given as argument in the foreground.
|
||||
- Improve various error messages.
|
||||
- Add `startpage`/`default-page` values to `tabs -> last-close`.
|
||||
- Various improvements to `:restart` - it should be more robust now and uses sessions so all state (focused tab, scroll position, etc.) gets remembered.
|
||||
- Add tab index display to the statusbar.
|
||||
- Keep progress bar height fixed when the statusbar is multiline.
|
||||
- Many improvements to tests and related infrastructure:
|
||||
* `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 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.
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- The `:run-userscript` command - use `:spawn --userscript` instead.
|
||||
- The `rapid` and `rapid-win` targets for `:hint` - use the `--rapid` argument to `:hint` instead.
|
||||
- The `:cancel-download` command - use `:download-cancel` instead.
|
||||
- The `:download-page` command - use `:download` instead.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- `init_venv.py` and `run_checks.py` have been replaced by http://tox.readthedocs.org/[tox]. Install tox and run `tox -e mkvenv` instead..
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fix for cache never being used.
|
||||
- Fixed handling of key release events (e.g. for javascript) when holding a key and pressing a second one.
|
||||
- Fix handling of commands using `;;` at various places (key config, command parser, `:bind`)
|
||||
- Fix splitting of flags with arguments (`:bind -m`/`--mode`).
|
||||
- Fix bindings of special keys with lower-case modifiers (e.g. `<ctrl-x>`)
|
||||
- Fix for weird search highlights when changing tabs while search is active.
|
||||
- Fix starting with `-c ""`.
|
||||
- Fix removing of partial downloads when a download is cancelled via context menu.
|
||||
- Fix retrying of downloads which were started in a now closed tab.
|
||||
- Highlight text case-insensitively in completion.
|
||||
- Scroll completion to top when showing it.
|
||||
- Handle unencodable file paths in config types correctly.
|
||||
- Fix for crash when executing a delayed command (because of a shadowed keybinding) and then unfocusing the window.
|
||||
- Fix for crash when hinting on a page which doesn't have a URL yet.
|
||||
- Fix exception when using `:set-cmd-text` with an empty argument.
|
||||
- Add a timeout to pastebin HTTP replies.
|
||||
- Various other fixes for small/rare bugs.
|
||||
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.4[v0.1.4]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
* The Windows builds come with Qt 5.4.1 which has some https://lists.schokokeks.org/pipermail/qutebrowser/2015-March/000054.html[related bugfixes].
|
||||
* Improvements to CPU usage when idle.
|
||||
* Ensure there's no size for `font-family` settings.
|
||||
* Handle URLs with double-colon as search strings.
|
||||
* Adjust prompt size hint based on content.
|
||||
* Refactor websettings and save/restore defaults.
|
||||
* Various small improvements to logging.
|
||||
* Various improvements for hinting.
|
||||
* Improve parsing of `faulthandler` logs.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
* Remove default search engines.
|
||||
* Remove debug console completing completely.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
* Ignore RuntimeError in `mouserelease_insertmode`.
|
||||
* Hide Qt warning when aborting download reply.
|
||||
* Hide "Error while shutting down tabs" message.
|
||||
* Clear open target in `acceptNavigationRequest`.
|
||||
* Fix handling of signals with deleted tabs.
|
||||
* Restore `sys.std*` in `utils.fake_io` on exceptions.
|
||||
* Allow font names with integers in them.
|
||||
* Fix `QIODevice` warnings when closing tabs.
|
||||
* Set the `QSettings` path to a config-subdirectory.
|
||||
* Add workaround for adblock-message without window.
|
||||
* Fix searching for terms starting with a slash.
|
||||
* Ignore tab key presses if they'd switch focus.
|
||||
|
||||
Security
|
||||
~~~~~~~~
|
||||
|
||||
* Stop the icon database from being created when private-browsing is set to true.
|
||||
* Disable insecure SSL ciphers.
|
||||
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.3[v0.1.3]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
* Various small logging improvements.
|
||||
* Don't open relative files in `fuzzy_url` with `:open`
|
||||
* Various crashdialog improvements.
|
||||
* Hide adblocked iframes.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
* Handle shutdown of page with prompt correctly.
|
||||
* fuzzy_url: handle invalid URLs with autosearch off
|
||||
* Handle explicit searches with `auto-search=false`.
|
||||
* Abort download override question on error/cancel.
|
||||
* Set a higher z-index for hint labels.
|
||||
* Close contextmenu when closing tab to avoid crash.
|
||||
* Fix statusbar quickly popping up as window.
|
||||
* Clean up `NetworkManager` after downloads finished.
|
||||
* Fix restoring of cmd widget after an error.
|
||||
* Fix retrying of downloads after the tab is closed.
|
||||
* Fix `check_libraries()` output for Arch Linux.
|
||||
* Handle all `IPCErrors` properly.
|
||||
* Handle another `webelem.IsNullError` with hints.
|
||||
* Handle `UnicodeDecodeError` when reading configs.
|
||||
|
||||
Security
|
||||
~~~~~~~~
|
||||
|
||||
* Fix for HTTP passwords accidentally being written to debug log.
|
||||
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.2[v0.1.2]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
* Uncheck sending of debug log by default when private browsing is on.
|
||||
* Add SSL info to version info.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
* Remove hosts-file.net from blocker default lists.
|
||||
|
||||
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 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
|
||||
* Make `init_venv.py` work with multiple sip `.so` files.
|
||||
* Fix splitting with certain commands with an empty argument
|
||||
* Fix uppercase hints.
|
||||
* Fix segfaults if another page is loaded while a prompt is open
|
||||
* Fix exception with invalid `ShellCommand` config values.
|
||||
* Replace unencodable chars
|
||||
* Fix user-stylesheet setting with an empty value.
|
||||
|
||||
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1.1[v0.1.1]
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
* Set window icon and add a qutebrowser.ico file for Windows.
|
||||
* Ask the user when downloading to an already existing file.
|
||||
* Add a `network -> proxy-dns-requests` option.
|
||||
* Add "Remove finished" to the download context menu
|
||||
* Open and remove clicked downloads.
|
||||
|
||||
Changes
|
||||
~~~~~~~
|
||||
|
||||
* Windows releases are now built with Qt 5.4 which brings many improvements and bugfixes.
|
||||
* Add a troubleshooting section to the FAQ.
|
||||
* Display IPC errors to the user.
|
||||
* Rewrite keymode handling to use only one mode which also fixes various bugs.
|
||||
* Save version to state config.
|
||||
* 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 OS X with homebrew/macports.
|
||||
* Allow min-/maximizing of print preview on Windows.
|
||||
* Various documentation improvements.
|
||||
* Various other small improvements and cleanups.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
* Clean up and temporarily disable alias completion.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
* Fix setting of `QWebSettings` (e.g. web fonts) with empty strings.
|
||||
* Re-focus web view when leaving prompt/yesno mode.
|
||||
* Handle `:restart` correctly with Python eggs.
|
||||
* Handle an invalid cwd properly.
|
||||
* Fix popping of a dead question in prompter.
|
||||
* Fix `AttributeError` on config changes on Ubuntu.
|
||||
* Don't treat things like "31c3" as IP address.
|
||||
* Handle category being `None` in Qt message handler.
|
||||
* Force-include pygments in `freeze.py`.
|
||||
* Fix scroll percentage not updating on some pages like twitter.
|
||||
* Encode `Content-Disposition` header name properly.
|
||||
* Fix item sorting in `NeighborList`.
|
||||
* Handle data being `None` in download read timer.
|
||||
* Stop download read timer when reply has finished.
|
||||
* Fix handling of small/big `fuzzyval`'s in `NeighborList`.
|
||||
* Fix crashes when entering invalid values in `qute:settings`.
|
||||
* Abort questions in `NetworkManager` when destroyed.
|
||||
* Fix height calculation of download view.
|
||||
* Always auto-remove adblock downloads when done.
|
||||
* Ensure the docs get included in `freeze.py`.
|
||||
* Fix crash with `:zoom`.
|
||||
|
||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.1[v0.1]
|
||||
-------------------------------------------------------------------
|
||||
|
||||
Initial release.
|
||||
327
INSTALL.asciidoc
@@ -1,327 +0,0 @@
|
||||
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
|
||||
----
|
||||
66
MANIFEST.in
@@ -1,49 +1,17 @@
|
||||
recursive-include qutebrowser *.py
|
||||
recursive-include qutebrowser/img *.svg *.png
|
||||
recursive-include qutebrowser/test *.py
|
||||
recursive-include qutebrowser/javascript *.js
|
||||
graft qutebrowser/html
|
||||
graft qutebrowser/3rdparty
|
||||
graft icons
|
||||
graft doc/img
|
||||
graft misc/apparmor
|
||||
graft misc/userscripts
|
||||
recursive-include scripts *.py
|
||||
include qutebrowser/utils/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
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
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
prune scripts/testbrowser_cpp
|
||||
prune .github
|
||||
exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
recursive-exclude doc *.asciidoc
|
||||
include doc/qutebrowser.1.asciidoc
|
||||
prune tests
|
||||
prune qutebrowser/3rdparty
|
||||
prune misc/requirements
|
||||
prune misc/docker
|
||||
exclude .editorconfig
|
||||
exclude pytest.ini
|
||||
exclude qutebrowser.rcc
|
||||
exclude .coveragerc
|
||||
exclude .pylintrc
|
||||
exclude .eslintrc
|
||||
exclude .eslintignore
|
||||
exclude doc/help
|
||||
exclude .appveyor.yml
|
||||
exclude .travis.yml
|
||||
exclude codecov.yml
|
||||
exclude .pydocstylerc
|
||||
exclude misc/appveyor_install.py
|
||||
exclude misc/qutebrowser.spec
|
||||
exclude .flake8
|
||||
|
||||
global-exclude __pycache__ *.pyc *.pyo
|
||||
recursive-include qutebrowser/html *.html
|
||||
recursive-include qutebrowser/test *.py
|
||||
recursive-include icons *
|
||||
include qutebrowser/test/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include COPYING doc/* README.asciidoc
|
||||
include qutebrowser.desktop
|
||||
|
||||
exclude scripts/run_checks.py
|
||||
exclude scripts/cleanup.py
|
||||
exclude scripts/minimal_webkit_testbrowser.py
|
||||
exclude scripts/run_profile.py
|
||||
exclude scripts/generate_authors.sh
|
||||
exclude .flake8
|
||||
exclude .pylintrc
|
||||
exclude doc/notes
|
||||
prune pkg
|
||||
|
||||
185
README.asciidoc
@@ -6,20 +6,10 @@
|
||||
qutebrowser
|
||||
===========
|
||||
|
||||
// QUTE_WEB_HIDE
|
||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.*
|
||||
image:icons/qutebrowser-64x64.png[] _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/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://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: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
|
||||
qutebrowser is a keyboard-focused browser with with a minimal GUI. It's based
|
||||
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
|
||||
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||
@@ -27,10 +17,10 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||
Screenshots
|
||||
-----------
|
||||
|
||||
image:doc/img/main.png["screenshot 1",width=300,link="doc/img/main.png"]
|
||||
image:doc/img/downloads.png["screenshot 2",width=300,link="doc/img/downloads.png"]
|
||||
image:doc/img/completion.png["screenshot 3",width=300,link="doc/img/completion.png"]
|
||||
image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"]
|
||||
image:doc/img/main.png[width=300,link="doc/img/main.png"]
|
||||
image:doc/img/downloads.png[width=300,link="doc/img/downloads.png"]
|
||||
image:doc/img/completion.png[width=300,link="doc/img/completion.png"]
|
||||
image:doc/img/hints.png[width=300,link="doc/img/hints.png"]
|
||||
|
||||
Downloads
|
||||
---------
|
||||
@@ -39,7 +29,7 @@ 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
|
||||
See link:doc/INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
|
||||
qutebrowser running for various platforms.
|
||||
|
||||
Documentation
|
||||
@@ -48,15 +38,13 @@ Documentation
|
||||
In addition to the topics mentioned in this README, the following documents are
|
||||
available:
|
||||
|
||||
* 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"]
|
||||
* A http://qutebrowser.org/img/cheatsheet-big.png[keybinding cheatsheet]: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser keybinding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* link:doc/quickstart.asciidoc[Quick start guide]
|
||||
* link:FAQ.asciidoc[Frequently asked questions]
|
||||
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
|
||||
* link:INSTALL.asciidoc[INSTALL]
|
||||
* link:CHANGELOG.asciidoc[Change Log]
|
||||
* link:doc/FAQ.asciidoc[Frequently asked questions]
|
||||
* link:doc/HACKING.asciidoc[HACKING]
|
||||
* link:doc/INSTALL.asciidoc[INSTALL]
|
||||
* link:doc/stacktrace.asciidoc[Reporting segfaults]
|
||||
* link:doc/userscripts.asciidoc[How to write userscripts]
|
||||
|
||||
Getting help
|
||||
------------
|
||||
@@ -73,8 +61,7 @@ Contributions / Bugs
|
||||
--------------------
|
||||
|
||||
You want to contribute to qutebrowser? Awesome! Please read
|
||||
link:CONTRIBUTING.asciidoc[the contribution guidelines] for details and
|
||||
useful hints.
|
||||
link:doc/HACKING.asciidoc[HACKING] for details and useful hints.
|
||||
|
||||
If you found a bug or have a feature request, you can report it in several
|
||||
ways:
|
||||
@@ -93,29 +80,27 @@ Requirements
|
||||
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* http://www.python.org/[Python] 3.4 or newer
|
||||
* http://qt.io/[Qt] 5.2.0 or newer (5.5.1 recommended)
|
||||
* http://www.python.org/[Python] 3.4
|
||||
* http://qt-project.org/[Qt] 5.2.0 or newer (5.4 recommended)
|
||||
* QtWebKit
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
|
||||
(5.5.1 recommended) for Python 3
|
||||
(5.3.2 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]
|
||||
|
||||
The following libraries are optional and provide a better user experience:
|
||||
|
||||
* http://cthedot.de/cssutils/[cssutils]
|
||||
|
||||
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.
|
||||
The following libraries are optional and provide colored logging in the
|
||||
console:
|
||||
|
||||
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
|
||||
and its dependencies.
|
||||
* https://pypi.python.org/pypi/colorlog/[colorlog]
|
||||
* On Windows: https://pypi.python.org/pypi/colorama/[colorama]
|
||||
|
||||
See link:doc/INSTALL.asciidoc[INSTALL] for directions on how to install
|
||||
qutebrowser and its dependencies.
|
||||
|
||||
Donating
|
||||
--------
|
||||
@@ -140,119 +125,29 @@ Contributors, sorted by the number of commits in descending order:
|
||||
|
||||
// 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
|
||||
* Martin Zimmermann
|
||||
* Error 800
|
||||
* Mathias Fussenegger
|
||||
* Larry Hynes
|
||||
* Johannes Altmanninger
|
||||
* Jeremy Kaplan
|
||||
* Ismail
|
||||
* Edgar Hipp
|
||||
* Daryl Finlay
|
||||
* adam
|
||||
* Samir Benmendil
|
||||
* Joel Torstensson
|
||||
* 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
|
||||
* Peter Vilim
|
||||
* 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)
|
||||
* WOFall (icon)
|
||||
* regines (keybinding cheatsheet)
|
||||
|
||||
Thanks / Similar projects
|
||||
-------------------------
|
||||
Thanks / Similiar projects
|
||||
--------------------------
|
||||
|
||||
Many projects with a similar goal as qutebrowser exist:
|
||||
|
||||
@@ -293,10 +188,9 @@ problems and helpful hints:
|
||||
|
||||
Also, thanks to:
|
||||
|
||||
* 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]
|
||||
https://bugreports.qt-project.org/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.
|
||||
@@ -316,14 +210,3 @@ 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
pdf.js
|
||||
------
|
||||
|
||||
qutebrowser optionally uses https://github.com/mozilla/pdf.js/[pdf.js] to
|
||||
display PDF files in the browser. Windows releases come with a bundled pdf.js.
|
||||
|
||||
pdf.js is distributed under the terms of the Apache License. You can
|
||||
find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the
|
||||
Windows release or after running `scripts/dev/update_3rdparty.py`), or online
|
||||
http://www.apache.org/licenses/LICENSE-2.0.html[here].
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
status:
|
||||
project:
|
||||
enabled: no
|
||||
patch:
|
||||
enabled: no
|
||||
changes:
|
||||
enabled: no
|
||||
|
||||
comment: off
|
||||
@@ -1,22 +1,21 @@
|
||||
Frequently asked questions
|
||||
==========================
|
||||
:title: Frequently asked questions
|
||||
The Compiler <mail@qutebrowser.org>
|
||||
|
||||
[qanda]
|
||||
What is qutebrowser based on?::
|
||||
qutebrowser uses http://www.python.org/[Python], http://qt.io/[Qt] and
|
||||
http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
qutebrowser uses http://www.python.org/[Python], http://qt-project.org/[Qt]
|
||||
and http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
+
|
||||
The concept of it is largely inspired by http://portix.bitbucket.org/dwb/[dwb]
|
||||
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
|
||||
key bindings are similar to dwb.
|
||||
keybindings are similar to dwb.
|
||||
|
||||
Why another browser?::
|
||||
It might be hard to believe, but I didn't find any browser which I was
|
||||
happy with, so I started to write my own. Also, I needed a project to get
|
||||
into writing GUI applications with Python and
|
||||
link:http://qt.io/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
link:http://qt-project.org/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
+
|
||||
Read the next few questions to find out why I was unhappy with existing
|
||||
software.
|
||||
@@ -33,11 +32,12 @@ API] seems to lack basic features like proxy support, and almost no projects
|
||||
seem to have started porting to WebKit2 (I only know of
|
||||
http://www.uzbl.org/[uzbl]).
|
||||
+
|
||||
qutebrowser uses http://qt.io/[Qt] and http://wiki.qt.io/QtWebKit[QtWebKit]
|
||||
instead, which suffers from far less such crashes. It might switch to
|
||||
http://wiki.qt.io/QtWebEngine[QtWebEngine] in the future, which is based on
|
||||
Google's https://en.wikipedia.org/wiki/Blink_(layout_engine)[Blink] rendering
|
||||
engine.
|
||||
qutebrowser uses http://qt-project.org/[Qt] and
|
||||
http://qt-project.org/wiki/QtWebKit[QtWebKit] instead, which suffers from far
|
||||
less such crashes. It might switch to
|
||||
http://qt-project.org/wiki/QtWebEngine[QtWebEngine] in the future, which is
|
||||
based on Google's https://en.wikipedia.org/wiki/Blink_(layout_engine)[Blink]
|
||||
rendering engine.
|
||||
|
||||
What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:http://5digits.org/pentadactyl/[Pentadactyl]/link:http://www.vimperator.org/vimperator[Vimperator]?::
|
||||
Firefox likes to break compatibility with addons on each upgrade, gets
|
||||
@@ -54,14 +54,14 @@ What's wrong with http://www.chromium.org/Home[Chromium] and https://vimium.gith
|
||||
|
||||
Why Python?::
|
||||
I enjoy writing Python since 2011, which made it one of the possible
|
||||
choices. I wanted to use http://qt.io/[Qt] because of
|
||||
http://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
|
||||
http://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
|
||||
like C++ and can't write it very well, so that wasn't an alternative.
|
||||
choices. I wanted to use http://qt-project.org/[Qt] because of
|
||||
http://qt-project.org/wiki/QtWebKit[QtWebKit] so I didn't have
|
||||
http://qt-project.org/wiki/Category:LanguageBindings[many other choices]. I
|
||||
don't like C++ and can't write it very well, so that wasn't an alternative.
|
||||
|
||||
But isn't Python too slow for a browser?::
|
||||
http://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
|
||||
I believe efficiency while coding is a lot more important than efficiency
|
||||
I believe efficency while coding is a lot more important than efficency
|
||||
while running. Also, most of the heavy lifting of qutebrowser is done by Qt
|
||||
and WebKit in C++, with the
|
||||
https://wiki.python.org/moin/GlobalInterpreterLock[GIL] released.
|
||||
@@ -75,43 +75,6 @@ Is there an adblocker?::
|
||||
usage], so implementing it properly might take some time and won't be done
|
||||
for v0.1 if at all.
|
||||
|
||||
How do I play Youtube videos with mpv?::
|
||||
You can easily add a key binding to play youtube videos inside a real video
|
||||
player - optionally even with hinting for links:
|
||||
+
|
||||
----
|
||||
:bind m spawn mpv {url}
|
||||
:bind M hint links spawn mpv {hint-url}
|
||||
----
|
||||
+
|
||||
Note that you might need an additional package (e.g.
|
||||
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
|
||||
Archlinux) to play web videos with mpv.
|
||||
+
|
||||
There is a very useful script for mpv, which emulates "unique application"
|
||||
functionality. This way you can add links to the mpv playlist instead of
|
||||
playing them all at once.
|
||||
+
|
||||
You can find the script here: https://github.com/mpv-player/mpv/blob/master/TOOLS/umpv
|
||||
+
|
||||
It also works nicely with rapid hints:
|
||||
+
|
||||
----
|
||||
:bind m spawn umpv {url}
|
||||
:bind M hint links spawn umpv {hint-url}
|
||||
:bind ;M hint --rapid links spawn umpv {hint-url}
|
||||
----
|
||||
|
||||
How do I use qutebrowser with mutt?::
|
||||
Due to a Qt limitation, local files without `.html` extensions are
|
||||
"downloaded" instead of displayed, see
|
||||
https://github.com/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;
|
||||
----
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
Configuration not saved after modifying config.::
|
||||
@@ -128,7 +91,7 @@ Unable to view flash content.::
|
||||
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`.
|
||||
Try exiting qutebroser and removing `~/.mozilla/plugins/mozplugger*.so`.
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/357[Issue #357]
|
||||
for more details.
|
||||
|
||||
@@ -140,22 +103,15 @@ Experiencing segfaults (crashes) on Debian systems.::
|
||||
|
||||
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
|
||||
visting these sites. This is caused by a known bug in Qt which has 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].
|
||||
+
|
||||
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].
|
||||
some packages. There is currently no easy way to manually upgrade to Qt
|
||||
5.4 on those systems.
|
||||
|
||||
My issue is not listed.::
|
||||
If you experience any segfaults or crashes, you can report the issue in
|
||||
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
|
||||
information.
|
||||
https://github.com/The-Compiler/qutebrowser/blob/master/doc/stacktrace.asciidoc[guide]
|
||||
on how to report them with all needed information.
|
||||
@@ -1,5 +1,5 @@
|
||||
Contributing to qutebrowser
|
||||
===========================
|
||||
qutebrowser HACKING
|
||||
===================
|
||||
The Compiler <mail@qutebrowser.org>
|
||||
:icons:
|
||||
:data-uri:
|
||||
@@ -37,6 +37,8 @@ If you want to find something useful to do, check the
|
||||
https://github.com/The-Compiler/qutebrowser/issues[issue tracker]. Some
|
||||
pointers:
|
||||
|
||||
* https://github.com/The-Compiler/qutebrowser/milestones/v0.1[Open issues for
|
||||
the v0.1 release]
|
||||
* https://github.com/The-Compiler/qutebrowser/labels/easy[Issues which should
|
||||
be easy to solve]
|
||||
* https://github.com/The-Compiler/qutebrowser/labels/not%20code[Issues which
|
||||
@@ -55,7 +57,7 @@ qutebrowser uses http://git-scm.com/[git] for its development. You can clone
|
||||
the repo like this:
|
||||
|
||||
----
|
||||
git clone https://github.com/The-Compiler/qutebrowser.git
|
||||
git clone git://the-compiler.org/qutebrowser
|
||||
----
|
||||
|
||||
If you don't know git, a http://git-scm.com/[git cheatsheet] might come in
|
||||
@@ -66,13 +68,8 @@ contributing, feel free to send normal patches instead, e.g. generated via
|
||||
Getting patches
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The preferred way of submitting changes is to
|
||||
https://help.github.com/articles/fork-a-repo/[fork the repository] and to
|
||||
https://help.github.com/articles/creating-a-pull-request/[submit a pull
|
||||
request].
|
||||
|
||||
If you prefer to send a patch to the mailinglist, you can generate a patch
|
||||
based on your changes like this:
|
||||
After you finished your work and did `git commit`, you can get patches of your
|
||||
changes like this:
|
||||
|
||||
----
|
||||
git format-patch origin/master <1>
|
||||
@@ -86,44 +83,32 @@ Useful utilities
|
||||
Checkers
|
||||
~~~~~~~~
|
||||
|
||||
qutebrowser uses http://tox.readthedocs.org/en/latest/[tox] to run its
|
||||
unittests and several linters/checkers.
|
||||
In the _scripts/_ subfolder, there is a `run_checks.py` script.
|
||||
|
||||
Currently, following tox environments are available:
|
||||
It runs a bunch of static checks on all source files, using the following
|
||||
checkers:
|
||||
|
||||
* Tests using https://www.pytest.org[pytest]:
|
||||
- `py34`: Run pytest for python-3.4.
|
||||
- `py35`: Run pytest for python-3.5.
|
||||
- `py34-cov`: Run pytest for python-3.4 with code coverage report.
|
||||
- `py35-cov`: Run pytest for python-3.5 with code coverage report.
|
||||
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
|
||||
https://pypi.python.org/pypi/pyflakes[pyflakes],
|
||||
https://pypi.python.org/pypi/pep8[pep8],
|
||||
https://pypi.python.org/pypi/mccabe[mccabe]
|
||||
* `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find
|
||||
unused code portions.
|
||||
* `pylint`: Run http://pylint.org/[pylint] static code analysis.
|
||||
* `pydocstyle`: Check
|
||||
https://www.python.org/dev/peps/pep-0257/[PEP257] compliance with
|
||||
https://github.com/PyCQA/pydocstyle[pydocstyle]
|
||||
* `pyroma`: Check packaging practices with
|
||||
https://pypi.python.org/pypi/pyroma/[pyroma]
|
||||
* `eslint`: Run http://eslint.org/[ESLint] javascript checker.
|
||||
* `check-manifest`: Check MANIFEST.in completeness with
|
||||
https://github.com/mgedmin/check-manifest[check-manifest]
|
||||
* `mkvenv`: Bootstrap a virtualenv for testing.
|
||||
* `misc`: Run `scripts/misc_checks.py` to check for:
|
||||
* Unit tests using the Python
|
||||
https://docs.python.org/3.4/library/unittest.html[unittest] framework
|
||||
* https://pypi.python.org/pypi/flake8/1.3.1[flake8]
|
||||
* https://github.com/GreenSteam/pep257/[pep257]
|
||||
* http://pylint.org/[pylint]
|
||||
* A custom checker for the following things:
|
||||
- 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` .
|
||||
If you changed `setup.py` or `MANIFEST.in`, add the `--setup` argument to run
|
||||
the following additional checkers:
|
||||
|
||||
Please make sure the checks run without any warnings on your new contributions.
|
||||
* https://pypi.python.org/pypi/pyroma/0.9.3[pyroma]
|
||||
* https://github.com/mgedmin/check-manifest[check-manifest]
|
||||
|
||||
There's of course the possibility of false-positives, and the following
|
||||
techniques are useful to handle these:
|
||||
It needs all the checkers to be installed and also needs
|
||||
https://pypi.python.org/pypi/colorama/[colorama].
|
||||
|
||||
Please make sure this script runs without any warnings on your new
|
||||
contributions. 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.
|
||||
@@ -140,48 +125,16 @@ smallest scope which makes sense. Most of the time, this will be line scope.
|
||||
* If you really think a check shouldn't be done globally as it yields a lot of
|
||||
false-positives, let me know! I'm still tweaking the parameters.
|
||||
|
||||
|
||||
Running Specific Tests
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
While you are developing you often don't want to run the full test
|
||||
suite each time.
|
||||
|
||||
Specific test environments can be run with `tox -e <envlist>`.
|
||||
|
||||
Additional parameters can be passed to the test scripts by separating
|
||||
them from `tox` arguments with `--`.
|
||||
|
||||
Examples:
|
||||
|
||||
----
|
||||
# run only pytest tests which failed in last run:
|
||||
tox -e py35 -- --lf
|
||||
|
||||
# run only the end2end feature tests:
|
||||
tox -e py35 -- tests/end2end/features
|
||||
|
||||
# run everything with undo in the generated name, based on the scenario text
|
||||
tox -e py35 -- tests/end2end/features/test_tabs_bdd.py -k undo
|
||||
|
||||
# run coverage test for specific file (updates htmlcov/index.html)
|
||||
tox -e py35-cov -- tests/unit/browser/test_webelem.py
|
||||
----
|
||||
|
||||
Profiling
|
||||
~~~~~~~~~
|
||||
|
||||
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.4/library/profile.html[cProfile] module and can show
|
||||
the output in four different ways:
|
||||
|
||||
* Raw profile file (`--profile-tool=none`)
|
||||
* https://pypi.python.org/pypi/pyprof2calltree/[pyprof2calltree] and http://kcachegrind.sourceforge.net/html/Home.html[KCacheGrind] (`--profile-tool=kcachegrind`)
|
||||
* https://jiffyclub.github.io/snakeviz/[SnakeViz] (`--profile-tool=snakeviz`)
|
||||
* https://github.com/jrfonseca/gprof2dot[gprof2dot] (needs `dot` from http://graphviz.org/[Graphviz] and http://feh.finalrewind.org/[feh])
|
||||
It needs https://pypi.python.org/pypi/pyprof2calltree/[pyprof2calltree] and
|
||||
http://kcachegrind.sourceforge.net/html/Home.html[KCacheGrind]. It uses the
|
||||
built-in Python https://docs.python.org/3.4/library/profile.html[cProfile]
|
||||
module.
|
||||
|
||||
Debugging
|
||||
~~~~~~~~~
|
||||
@@ -203,7 +156,7 @@ Useful websites
|
||||
|
||||
Some resources which might be handy:
|
||||
|
||||
* http://doc.qt.io/qt-5/classes.html[The Qt5 reference]
|
||||
* http://qt-project.org/doc/qt-5/classes.html[The Qt5 reference]
|
||||
* https://docs.python.org/3/library/index.html[The Python reference]
|
||||
* http://httpbin.org/[httpbin, a test service for HTTP requests/responses]
|
||||
* http://requestb.in/[RequestBin, a service to inspect HTTP requests]
|
||||
@@ -216,6 +169,7 @@ Documentation of used Python libraries:
|
||||
* http://pythonhosted.org/setuptools/[setuptools]
|
||||
* http://cx-freeze.readthedocs.org/en/latest/overview.html[cx_Freeze]
|
||||
* https://pypi.python.org/pypi/colorama[colorama]
|
||||
* https://pypi.python.org/pypi/colorlog[colorlog]
|
||||
|
||||
Related RFCs and standards:
|
||||
|
||||
@@ -260,7 +214,8 @@ Other
|
||||
Languages] (http://www.rfc-editor.org/errata_search.php?rfc=5646[Errata])
|
||||
* http://www.w3.org/TR/CSS2/[Cascading Style Sheets Level 2 Revision 1 (CSS
|
||||
2.1) Specification]
|
||||
* http://doc.qt.io/qt-5/stylesheet-reference.html[Qt Style Sheets Reference]
|
||||
* http://qt-project.org/doc/qt-4.8/stylesheet-reference.html[Qt Style Sheets
|
||||
Reference]
|
||||
* http://mimesniff.spec.whatwg.org/[MIME Sniffing Standard]
|
||||
* http://spec.whatwg.org/[WHATWG specifications]
|
||||
* http://www.w3.org/html/wg/drafts/html/master/Overview.html[HTML 5.1 Nightly]
|
||||
@@ -276,7 +231,7 @@ Hints
|
||||
Python and Qt objects
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
For many tasks, there are solutions in both Qt and the Python standard library
|
||||
For many tasks, there are solutions in both Qt and the Python standard libary
|
||||
available.
|
||||
|
||||
In qutebrowser, the policy is usually using the Python libraries, as they
|
||||
@@ -286,7 +241,9 @@ 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` if certain actions (e.g.
|
||||
cleanup) when the process finished are desired, as it provides signals for
|
||||
that.
|
||||
* `QUrl` is used instead of storing URLs as string, see the
|
||||
<<handling-urls,handling URLs>> section for details.
|
||||
|
||||
@@ -295,22 +252,22 @@ When using Qt objects, two issues must be taken care of:
|
||||
* 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()`
|
||||
method such as `QUrl` or `QModelIndex`, there's a helper function
|
||||
`ensure_valid` in `qutebrowser.utils.qtutils` which should get called
|
||||
on all such objects. It will raise
|
||||
`qutebrowser.utils.qtutils.QtValueError` if the value is not valid.
|
||||
If a function gets or returns a Qt object which
|
||||
has an `.isValid()` method such as `QUrl` or `QModelIndex`, there's a helper
|
||||
function `ensure_valid` in `qutebrowser.utils.qt` which should get called on
|
||||
all such objects. It will raise `qutebrowser.utils.qt.QtValueError` if the
|
||||
value is not valid.
|
||||
+
|
||||
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
|
||||
underlying C++ types.
|
||||
* Methods of Qt objects have certain maximum values, based on their underlying
|
||||
C++ types.
|
||||
+
|
||||
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).
|
||||
When passing a numeric parameter to a Qt function, all numbers should be
|
||||
range-checked using `qutebrowser.utils.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 +282,7 @@ There are currently these object registries, also called 'scopes':
|
||||
`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
|
||||
focused tab by default. A tab can be explicitely selected by passing
|
||||
+tab=_tab-id_, window=_win-id_+ to it.
|
||||
|
||||
A new object can be registered by using
|
||||
@@ -341,8 +298,8 @@ All objects can be printed by starting with the `--debug` flag and using the
|
||||
|
||||
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.
|
||||
http://qt-project.org/doc/qt-5/signalsandslots.html[signals and slots]
|
||||
mechanism would be difficult.
|
||||
|
||||
Logging
|
||||
~~~~~~~
|
||||
@@ -374,7 +331,7 @@ The following logging levels are available for every logger:
|
||||
|
||||
[width="75%",cols="25%,75%"]
|
||||
|=======================================================================
|
||||
|critical |Critical issue, qutebrowser can't continue to run.
|
||||
|criticial |Critical issue, qutebrowser can't continue to run.
|
||||
|error |There was an issue and some kind of operation was abandoned.
|
||||
|warning |There was an issue but the operation can continue running.
|
||||
|info |General informational messages.
|
||||
@@ -412,67 +369,44 @@ then gets passed as the `self` parameter to the handler. The `scope` argument
|
||||
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
|
||||
`qutebrowser.commands.cmdutils` for details.
|
||||
There are also other arguments to customize the way the command is registered,
|
||||
see the class documentation for `register` in `qutebrowser.commands.utils` 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
|
||||
qutebrowser's commandline.
|
||||
|
||||
The type can be overridden using Python's
|
||||
http://legacy.python.org/dev/peps/pep-3107/[function annotations]:
|
||||
This behaviour can be overridden using Python's
|
||||
http://legacy.python.org/dev/peps/pep-3107/[function annotations]. The
|
||||
annotation should always be a `dict`, like this:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
@cmdutils.register(...)
|
||||
def foo(bar: int, baz=True):
|
||||
def foo(bar: {'type': int}, baz=True):
|
||||
...
|
||||
----
|
||||
|
||||
Possible values:
|
||||
The following keys are supported in the dict:
|
||||
|
||||
* `type`: The type this value should have. The value entered by the user is
|
||||
then automatically checked. Possible values:
|
||||
- A callable (`int`, `float`, etc.): Gets called to validate/convert the
|
||||
value.
|
||||
- A string: The value must match exactly (mainly useful with tuples to get
|
||||
a choice of values, see below).
|
||||
- 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 e.g. be used to customize the
|
||||
flag an argument should get:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
@cmdutils.register(...)
|
||||
@cmdutils.argument('bar', flag='c')
|
||||
def foo(bar):
|
||||
...
|
||||
----
|
||||
|
||||
For a `str` argument, you can restrict the allowed strings using `choices`:
|
||||
|
||||
[source,python]
|
||||
----
|
||||
@cmdutils.register(...)
|
||||
@cmdutils.argument('bar', choices=['val1', 'val2'])
|
||||
def foo(bar: str):
|
||||
...
|
||||
----
|
||||
|
||||
For `typing.Union` types, the given `choices` are only checked if other types
|
||||
(like `int`) don't match.
|
||||
|
||||
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 `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
|
||||
underscores stripped and underscores replaced by dashes.
|
||||
- A tuple of multiple types above: Any of these types are valid values,
|
||||
e.g. `('foo', 'bar')` or `(int, 'foo')`.
|
||||
* `flag`: The flag to be used, as 1-char string (default: First char of the
|
||||
long name).
|
||||
* `name`: The long name to be used, as string (default: Name of the parameter).
|
||||
* `special`: The string `count` or `win_id` if the parameter should be
|
||||
auto-filled (with the count given by the user and the window ID the command was
|
||||
executed in, respectively).
|
||||
* `nargs`: Gets passed to argparse, see
|
||||
https://docs.python.org/dev/library/argparse.html#nargs[its documentation].
|
||||
|
||||
[[handling-urls]]
|
||||
Handling URLs
|
||||
@@ -502,30 +436,6 @@ displaying it to the user.
|
||||
`QUrl` and take appropriate action if not. Note the URL of the current page
|
||||
always could be an invalid QUrl (if nothing is loaded yet).
|
||||
|
||||
Running valgrind on QtWebKit
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you want to run qutebrowser (and thus QtWebKit) with
|
||||
http://valgrind.org/[valgrind], you'll need to pass `--smc-check=all` to it or
|
||||
recompile QtWebKit with the Javascript JIT disabled.
|
||||
|
||||
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, 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.
|
||||
|
||||
...
|
||||
|
||||
Note that the default option will catch the vast majority of cases. The main
|
||||
case it will not catch is programs such as JIT compilers that dynamically
|
||||
generate code and subsequently overwrite part or all of it. Running with all
|
||||
will slow Valgrind down noticeably.
|
||||
____
|
||||
|
||||
Style conventions
|
||||
-----------------
|
||||
@@ -595,7 +505,10 @@ Return:
|
||||
* The layout of a class should be like this:
|
||||
- docstring
|
||||
- `__magic__` methods
|
||||
- other methods
|
||||
- properties
|
||||
- _private methods
|
||||
- public methods
|
||||
- `on_*` methods
|
||||
- overrides of Qt methods
|
||||
|
||||
Checklists
|
||||
@@ -608,66 +521,42 @@ New Qt release
|
||||
|
||||
* Run all tests and check nothing is broken.
|
||||
* Check the
|
||||
https://bugreports.qt.io/issues/?jql=reporter%20%3D%20%22The%20Compiler%22%20ORDER%20BY%20fixVersion%20ASC[Qt bugtracker]
|
||||
https://bugreports.qt-project.org/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 own PKGBUILDs based on upstream Archlinux updates.
|
||||
* Build developer packages.
|
||||
* Build non-developer symbol packages.
|
||||
* Upload symbols patch to http://www.qutebrowser.org/qt-symbols.patch
|
||||
* Upload symbols packages to http://www.qutebrowser.org/qt-symbols-pkg/
|
||||
* Update recommended Qt version in `README`
|
||||
* Update OS X instructions in `README`
|
||||
* Make sure Gentoo instructions are up to date.
|
||||
* Grep for `WORKAROUND` in the code and test if fixed stuff works without the
|
||||
workaround.
|
||||
* Check relevant
|
||||
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)
|
||||
* Download new installer and update PyQt installer path in `ci_install.py`.
|
||||
|
||||
qutebrowser release
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Make sure there are no unstaged changes and the tests are green.
|
||||
|
||||
* 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`.
|
||||
* Remove *(unreleased)* from changelog.
|
||||
* Run tests again
|
||||
* Make sure there are no unstaged changes.
|
||||
* Run `src2asciidoc.py` and commit changes if necessary.
|
||||
* Run `asciidoc2html.py`.
|
||||
* Commit
|
||||
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
|
||||
|
||||
* 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.
|
||||
* `git push origin`; `git push origin v0.X.Y`
|
||||
* Run all tests on all supported systems.
|
||||
* Test an upgrade from the previous version (no manual intervention).
|
||||
* Test an upgrade from the first version (no manual intervention).
|
||||
|
||||
* Create annotated git tag (`git tag -s "v0.1" -m "Release v0.1"`)
|
||||
* Create git branch `v0.1.x`
|
||||
* Push including `--tags`
|
||||
* Create release on github
|
||||
* Mark the milestone at https://github.com/The-Compiler/qutebrowser/milestones
|
||||
as closed.
|
||||
|
||||
* 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
|
||||
* Create standalone Windows package (32/64bit) in Windows VM
|
||||
* Upload to PyPI: `python setup.py register sdist upload --sign`
|
||||
* Maybe upload to http://qt-apps.org/
|
||||
* Upload to webpage with checksum/GPG (when/if it exists)
|
||||
|
||||
* 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
|
||||
* Maybe annouce at other places?
|
||||
142
doc/INSTALL.asciidoc
Normal file
@@ -0,0 +1,142 @@
|
||||
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)
|
||||
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python-virtualenv
|
||||
----
|
||||
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
repository (rather than a release):
|
||||
|
||||
----
|
||||
# apt-get install asciidoc
|
||||
# python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
Then run the supplied script to run qutebrowser inside a
|
||||
https://virtualenv.pypa.io/en/latest/virtualenv.html[virtualenv]:
|
||||
|
||||
----
|
||||
# python3 scripts/init_venv.py
|
||||
----
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder. The
|
||||
system-wide Qt5/PyQt5 installations are symlinked into the virtualenv.
|
||||
|
||||
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
|
||||
----
|
||||
|
||||
On Archlinux
|
||||
------------
|
||||
|
||||
There are two Archlinux packages available in the AUR:
|
||||
https://aur.archlinux.org/packages/qutebrowser/[qutebrowser] and
|
||||
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
|
||||
|
||||
You can install them like this:
|
||||
|
||||
----
|
||||
$ mkdir qutebrowser
|
||||
$ cd qutebrowser
|
||||
$ wget https://aur.archlinux.org/packages/qu/qutebrowser-git/PKGBUILD
|
||||
$ makepkg -si
|
||||
----
|
||||
|
||||
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
|
||||
|
||||
On Gentoo
|
||||
---------
|
||||
|
||||
A dedicated overlay is available on
|
||||
https://github.com/posativ/qutebrowser-overlay[GitHub]. To install it, add the
|
||||
overlay with http://wiki.gentoo.org/wiki/Layman[layman]:
|
||||
|
||||
----
|
||||
# wget https://raw.githubusercontent.com/posativ/qutebrowser-overlay/master/overlays.xml -O /etc/layman/overlays/qutebrowser.xml
|
||||
# layman -a qutebrowser
|
||||
----
|
||||
|
||||
Note, that Qt5 is available in the portage tree, but masked. You may need to do
|
||||
a lot of keywording to install qutebrowser. Also make sure you have `python3_4`
|
||||
in your `PYTHON_TARGETS` (`/etc/portage/make.conf`) and rebuild your system
|
||||
(`emerge -uDNav @world`). Afterwards, you can install qutebrowser:
|
||||
|
||||
----
|
||||
# emerge -av qutebrowser
|
||||
----
|
||||
|
||||
On Windows
|
||||
----------
|
||||
|
||||
You can either use one of the
|
||||
https://github.com/The-Compiler/qutebrowser/releases[prebuilt standalone
|
||||
packages or MSI installers], or install manually:
|
||||
|
||||
* 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.
|
||||
* Run `pip install virtualenv` or
|
||||
http://www.lfd.uci.edu/~gohlke/pythonlibs/#virtualenv[the installer from here]
|
||||
to install virtualenv.
|
||||
|
||||
Then run the supplied script to run qutebrowser inside a
|
||||
https://virtualenv.pypa.io/en/latest/virtualenv.html[virtualenv]:
|
||||
|
||||
----
|
||||
# python3 scripts/init_venv.py
|
||||
----
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder. The
|
||||
system-wide Qt5/PyQt5 installations are used in the virtualenv.
|
||||
|
||||
On OS X
|
||||
-------
|
||||
|
||||
To install qutebrowser on OS X, you'll want a package manager, e.g.
|
||||
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]. Also make
|
||||
sure, you have https://itunes.apple.com/en/app/xcode/id497799835[XCode]
|
||||
installed to compile PyQt5 in a later step.
|
||||
|
||||
----
|
||||
$ brew install python3 pyqt5
|
||||
$ pip3.4 install qutebrowser
|
||||
----
|
||||
|
||||
if you are using Homebrew. 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.
|
||||
@@ -1,172 +0,0 @@
|
||||
Crowdfunding backers
|
||||
====================
|
||||
|
||||
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
|
||||
- Jean-Louis Fuchs
|
||||
- Matthias Lisin
|
||||
- 1 Anonymous
|
||||
|
||||
Day sponsors
|
||||
------------
|
||||
|
||||
- Agent 42
|
||||
- Iggy Jackson
|
||||
- James B
|
||||
- Rudi Seitz
|
||||
- Tim „Das MooL“ Wegener
|
||||
- amd1212
|
||||
- gavtroy
|
||||
- 4 Anonymous
|
||||
|
||||
Other sponsors
|
||||
--------------
|
||||
|
||||
- AP M
|
||||
- Alessandro Balzano
|
||||
- Allan Nordhøy
|
||||
- Andor Uhlar
|
||||
- Andreas Leppert
|
||||
- Andreas Saga Romsdal
|
||||
- Andrew Rogers / tuxlovesyou
|
||||
- André Glüpker
|
||||
- Arian Sanusi
|
||||
- Arin Lares
|
||||
- Assaf Lavie
|
||||
- Baptiste Wicht
|
||||
- Benjamin Richter
|
||||
- Benjamin Schnitzler
|
||||
- Bernardo Kuri
|
||||
- Boris Kourtoukov
|
||||
- Brian Buccola
|
||||
- Bruno Oliveira
|
||||
- Bryan Gilbert
|
||||
- Cassandra Rebecca Ruppen
|
||||
- Charles Saternos
|
||||
- Chris H
|
||||
- Christian Karl
|
||||
- Christian Lange
|
||||
- Christian Strasser
|
||||
- Colin O'Brien
|
||||
- Corsin Pfister
|
||||
- Cosmin Popescu
|
||||
- Daniel Andersson
|
||||
- David Wilson
|
||||
- Demure Demeanor
|
||||
- Doug Stone-Weaver
|
||||
- Eero Kari
|
||||
- Enric Morales
|
||||
- Eric Krohn
|
||||
- Eskild Hustvedt
|
||||
- Federico Panico
|
||||
- Felix Van der Jeugt
|
||||
- Francis Tseng
|
||||
- Geir Isene
|
||||
- George Voronin
|
||||
- German Correa
|
||||
- Grady Martin
|
||||
- Gregor Böhl
|
||||
- Guilherme Stein
|
||||
- Hannes Doyle
|
||||
- Hasan Soydabas
|
||||
- Ian Scott
|
||||
- Jacob Boldman
|
||||
- Jacob Wikmark
|
||||
- Jan Verbeek
|
||||
- Jarrod Seccombe
|
||||
- Joel Bradshaw
|
||||
- Johannes Martinsson
|
||||
- Jonas Schürmann
|
||||
- Josh Medeiros
|
||||
- José Alberto Orejuela García
|
||||
- Julie Engel
|
||||
- Jörg Behrmann
|
||||
- Jørgen Skancke
|
||||
- Kevin Velghe
|
||||
- Konstantin Shmelkov
|
||||
- Kyle Frazer
|
||||
- Lukas Gierth
|
||||
- Mar v Leeuwaarde
|
||||
- Marek Roszman
|
||||
- Marius Betz
|
||||
- Marius Krämer
|
||||
- Markus Schmidinger
|
||||
- Martin Gabelmann
|
||||
- Martin Zimmermann
|
||||
- Mathias Fußenegger
|
||||
- Maxime Wack
|
||||
- Michał Góral
|
||||
- Nathan Isom
|
||||
- Nathanael Philipp
|
||||
- Nils Stål
|
||||
- Oliver Hope
|
||||
- Oskar Nyberg
|
||||
- Pablo Navarro
|
||||
- Panashe M. Fundira
|
||||
- Patric Schmitz
|
||||
- Pete M
|
||||
- Peter Smith
|
||||
- Phil Collins
|
||||
- Philipp Hansch
|
||||
- Philipp Kuhnz
|
||||
- Raphael Khaiat
|
||||
- Raphael Pierzina
|
||||
- Renan Guilherme
|
||||
- Rick Losie
|
||||
- Robert Cross
|
||||
- Roy Van Ginneken
|
||||
- Rupus Reinefjord
|
||||
- Ryan Roden-Corrent
|
||||
- Samir Benmendil
|
||||
- Simon Giotta
|
||||
- Stephen England
|
||||
- Sverrir H Steindorsson
|
||||
- Tarcisio Fedrizzi
|
||||
- Thorsten Wißmann
|
||||
- Timon Stampfli
|
||||
- Tjelvar Olsson
|
||||
- Tomasz Kramkowski
|
||||
- Tsukiko Tsutsukakushi
|
||||
- Vasilij Schneidermann
|
||||
- Vinney Cavallo
|
||||
- Wesly Grefrath
|
||||
- Will Ware
|
||||
- Yousaf Khurshid
|
||||
- Zach Schultz
|
||||
- averrin
|
||||
- ben hengst
|
||||
- colin
|
||||
- craigtski47
|
||||
- dag.robole
|
||||
- daniel.m.kao
|
||||
- diepfann3
|
||||
- eamonn oneil
|
||||
- esakaforever
|
||||
- francois47
|
||||
- glspisso
|
||||
- gmccoy4242
|
||||
- gtcee3
|
||||
- jonathf
|
||||
- lapinski.maciej
|
||||
- lauri.hakko
|
||||
- ljanzen
|
||||
- mutilx9
|
||||
- nussgipfel
|
||||
- oed
|
||||
- p p
|
||||
- r.c.bruno.andre
|
||||
- robert.perce
|
||||
- sghctoma
|
||||
- targy
|
||||
- freelancer
|
||||
- pupu
|
||||
- regines
|
||||
- 37 Anonymous
|
||||
@@ -8,11 +8,8 @@ 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:commands.html[Documentation of commands]
|
||||
* link:settings.html[Documentation of settings]
|
||||
* link:userscripts.html[How to write userscripts]
|
||||
* link:CONTRIBUTING.html[Contributing to qutebrowser]
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
|
Before Width: | Height: | Size: 989 KiB |
|
Before Width: | Height: | Size: 43 KiB |
114
doc/notes
@@ -80,117 +80,3 @@ 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
|
||||
|
||||
@@ -5,26 +5,12 @@ The Compiler <mail@qutebrowser.org>
|
||||
NOTE: This page will only appear on the first start. To view it at a later
|
||||
time, use the `:help` command.
|
||||
|
||||
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). 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
|
||||
* To click on something without using the mouse, press `f` to show the hints, then type the keys next to what you want to click on (if that sounds weird, then just try pressing `f` and see what happens)
|
||||
* Press `:` to show the commandline
|
||||
* To search in a page, press `/`, type the phrase to search for, then press Enter. Use `n` and `N` to go back and forth through the matches, and press Esc to stop doing the search.
|
||||
* To close qutebrowser, press `Alt-F4`, or `:q`, or `:wq` to save the currently open tabs and quit (note that in the settings you can make qutebrowser always save the currently open tabs)
|
||||
|
||||
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"]
|
||||
* Run `:adblock-update` to download adblock lists and activate adblocking.
|
||||
* View the http://qutebrowser.org/img/cheatsheet-big.png[keybinding cheatsheet]
|
||||
to make yourself familiar with the keybindings: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser keybinding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* 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.
|
||||
|
||||
@@ -16,15 +16,11 @@ qutebrowser - a keyboard-driven, vim-like browser based on PyQt5 and QtWebKit.
|
||||
*qutebrowser* ['-OPTION' ['...']] [':COMMAND' ['...']] ['URL' ['...']]
|
||||
|
||||
== DESCRIPTION
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI. It's based
|
||||
qutebrowser is a keyboard-focused browser with with a minimal GUI. It's based
|
||||
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
|
||||
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
|
||||
|
||||
Note the commands and settings of qutebrowser are not described in this
|
||||
manpage, but in the help integrated in qutebrowser - use the ":help" command to
|
||||
show it.
|
||||
|
||||
== OPTIONS
|
||||
// QUTE_OPTIONS_START
|
||||
=== positional arguments
|
||||
@@ -39,37 +35,13 @@ show it.
|
||||
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. Other --*dir arguments are ignored if this is given.
|
||||
Set config directory (empty for no config storage)
|
||||
|
||||
*-V*, *--version*::
|
||||
Show version and quit.
|
||||
|
||||
*-s* 'SECTION' 'OPTION' 'VALUE', *--set* 'SECTION' 'OPTION' 'VALUE'::
|
||||
Set a temporary setting for this session.
|
||||
|
||||
*-r* 'SESSION', *--restore* 'SESSION'::
|
||||
Restore a named session.
|
||||
|
||||
*-R*, *--override-restore*::
|
||||
Don't restore a session even if one would be restored.
|
||||
|
||||
*--target* '{auto,tab,tab-bg,tab-silent,tab-bg-silent,window}'::
|
||||
How URLs should be opened if there is already a qutebrowser instance running.
|
||||
|
||||
*--backend* '{webkit,webengine}'::
|
||||
Which backend to use (webengine backend is EXPERIMENTAL!).
|
||||
|
||||
=== debug arguments
|
||||
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
||||
*-l* 'LOGLEVEL', *--loglevel* 'LOGLEVEL'::
|
||||
Set loglevel
|
||||
|
||||
*--logfilter* 'LOGFILTER'::
|
||||
@@ -81,38 +53,20 @@ show it.
|
||||
*--debug*::
|
||||
Turn on debugging options.
|
||||
|
||||
*--json-logging*::
|
||||
Output log lines in JSON format (one object per line).
|
||||
|
||||
*--nocolor*::
|
||||
Turn off colored logging.
|
||||
|
||||
*--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-name* 'NAME'::
|
||||
Set the window name.
|
||||
*--no-crash-dialog*::
|
||||
Don't show a crash dialog.
|
||||
|
||||
*--qt-style* 'STYLE'::
|
||||
Set the Qt GUI style to use.
|
||||
@@ -134,14 +88,8 @@ show it.
|
||||
|
||||
- '~/.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.
|
||||
|
||||
Note qutebrowser conforms to the XDG basedir specification - if
|
||||
'XDG_CONFIG_HOME', 'XDG_DATA_HOME' or 'XDG_CACHE_HOME' are set in the
|
||||
environment, the directories configured there are used instead of the above
|
||||
defaults.
|
||||
- '~/.config/qutebrowser/keys.conf': Defined keybindings.
|
||||
- '~/.local/share/qutebrowser/': Various state information
|
||||
|
||||
== BUGS
|
||||
Bugs are tracked in the Github issue tracker at
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
Getting stacktraces on crashes
|
||||
==============================
|
||||
:toc:
|
||||
The Compiler <mail@qutebrowser.org>
|
||||
|
||||
When there is a fatal crash in qutebrowser - most of the times a
|
||||
@@ -15,17 +14,10 @@ https://en.wikipedia.org/wiki/Debug_symbol[debugging symbols] is required.
|
||||
The rest of this guide is quite Linux specific, though there is a
|
||||
<<windows,section for Windows>> at the end.
|
||||
|
||||
Crashes which can be reproduced
|
||||
-------------------------------
|
||||
|
||||
If a crash can be reproduced, packages with debugging symbols should be
|
||||
installed, and the crash should be reproduced under gdb.
|
||||
|
||||
Getting debugging symbols
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
-------------------------
|
||||
|
||||
Debian/Ubuntu/...
|
||||
^^^^^^^^^^^^^^^^^
|
||||
.Debian/Ubuntu/...
|
||||
|
||||
For Debian based systems (Debian, Ubuntu, Linux Mint, ...), debug information
|
||||
is available in the repositories:
|
||||
@@ -34,66 +26,76 @@ is available in the repositories:
|
||||
# apt-get install python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg python3-dbg libqt5webkit5-dbg
|
||||
----
|
||||
|
||||
Archlinux
|
||||
^^^^^^^^^
|
||||
.Archlinux
|
||||
|
||||
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).
|
||||
debugging symbols compiled by me (x86_64 only).
|
||||
|
||||
.To compile by yourself
|
||||
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
|
||||
$ makepkg -si
|
||||
$ cd ../pyqt5
|
||||
$ makepkg -si --pkg pyqt5-common-debug,python-pyqt5-debug
|
||||
$ makepkg -si
|
||||
----
|
||||
|
||||
.To install my pre-built packages
|
||||
|
||||
First download and sign the key:
|
||||
To install my pre-built packages:
|
||||
|
||||
----
|
||||
# pacman-key -r 0xD6A1C70FE80A0C82
|
||||
$ pacman-key -f 0xD6A1C70FE80A0C82
|
||||
Key fingerprint = 14AF EC28 70C6 4863 C5C7 ACCB D6A1 C70F E80A 0C82
|
||||
# pacman-key --lsign-key 0xD6A1C70FE80A0C82
|
||||
$ mkdir qt-debug
|
||||
$ cd qt-debug
|
||||
$ wget -r -l1 -A '*.tar.xz' -L -np -nd http://www.qutebrowser.org/qt-symbols-pkg/
|
||||
# pacman -U *.pkg.tar.xz
|
||||
----
|
||||
|
||||
Then edit your `/etc/pacman.conf` to add the repository to the bottom:
|
||||
After you are done debugging, make sure to install the system packages again so
|
||||
you get updates. This can be done with this command:
|
||||
|
||||
----
|
||||
[qt-debug]
|
||||
Server = http://qutebrowser.org/qt-debug/$arch
|
||||
# pacman -S qt5
|
||||
----
|
||||
|
||||
Then install the packages:
|
||||
Getting a core dump
|
||||
-------------------
|
||||
|
||||
The next step is finding the core dump so we can get a stacktrace from it.
|
||||
|
||||
First of all, try to reproduce your problem. If you can, run qutebrowser
|
||||
directly inside gdb like this:
|
||||
|
||||
----
|
||||
# pacman -Suy pyqt5-common-debug python-pyqt5-debug qt5-base-debug qt5-webkit-debug,qt5-webengine-debug
|
||||
$ gdb $(which python3) -ex 'run -m qutebrowser --debug'
|
||||
----
|
||||
|
||||
The `-debug` packages conflict with the non-debug variants - it's safe to
|
||||
remove them.
|
||||
If you cannot reproduce the problem, you need to check if a coredump got
|
||||
written somewhere.
|
||||
|
||||
Getting the stack trace
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Check the file `/proc/sys/kernel/core_pattern` on your system. If it does not
|
||||
start with a `|` character (pipe), check if there is a file named `core` or
|
||||
`core.NNNN` in the directory from that file, or in the current directory.
|
||||
|
||||
First install `gdb` on your system if it's not installed already.
|
||||
|
||||
Then run qutebrowser directly inside gdb like this:
|
||||
If so, execute gdb like this:
|
||||
|
||||
----
|
||||
$ gdb $(readlink -f $(which python3)) -ex 'run -m qutebrowser --debug'
|
||||
$ gdb $(which python3) /path/to/core
|
||||
----
|
||||
|
||||
After you reproduce the crash, you should now see something like:
|
||||
If your `/proc/sys/kernel/core_pattern` contains something like
|
||||
`|/usr/lib/systemd/systemd-coredump`, use `coredumpctl` as root to run gdb:
|
||||
|
||||
----
|
||||
# coredumpctl gdb $(which python3)
|
||||
----
|
||||
|
||||
Getting a stack trace
|
||||
---------------------
|
||||
|
||||
Regardless of the way you used to open gdb, you should now see something like:
|
||||
|
||||
----
|
||||
Program received signal SIGSEGV, Segmentation fault.
|
||||
@@ -105,58 +107,16 @@ Now enter these commands at the gdb prompt:
|
||||
|
||||
----
|
||||
(gdb) set logging on
|
||||
(gdb) bt full
|
||||
# you might have to press enter a few times until you get the prompt back
|
||||
(gdb) quit
|
||||
----
|
||||
|
||||
This will create a `gdb.txt` in your current directory.
|
||||
|
||||
Copy the last few lines of the debug log (before you got the gdb prompt) and
|
||||
the full content of `gdb.txt` into the bug report. Please also add some words
|
||||
about what you were doing (or what pages you visited) before the crash
|
||||
happened.
|
||||
|
||||
Crashes which can NOT be reproduced
|
||||
-----------------------------------
|
||||
|
||||
If you cannot reproduce the problem, you need to check if a coredump got
|
||||
written somewhere. You should not install debug symbols as they won't match the
|
||||
generated coredump.
|
||||
|
||||
First install `gdb` on your system if it's not installed already.
|
||||
|
||||
Then check the file `/proc/sys/kernel/core_pattern` on your system. If it does
|
||||
not start with a `|` character (pipe), check if there is a file named `core` or
|
||||
`core.NNNN` in the directory from that file, or in the current directory.
|
||||
|
||||
If so, execute gdb like this:
|
||||
|
||||
----
|
||||
$ gdb $(readlink -f $(which python3)) /path/to/core
|
||||
----
|
||||
|
||||
If your `/proc/sys/kernel/core_pattern` contains something like
|
||||
`|/usr/lib/systemd/systemd-coredump`, use `coredumpctl` to run gdb:
|
||||
|
||||
----
|
||||
$ coredumpctl gdb $(readlink -f $(which python3))
|
||||
----
|
||||
|
||||
Getting the stack trace
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now enter these commands at the gdb prompt:
|
||||
|
||||
----
|
||||
(gdb) set logging on
|
||||
(gdb) set logging redirect on
|
||||
(gdb) bt
|
||||
# you might have to press enter a few times until you get the prompt back
|
||||
(gdb) set logging redirect off
|
||||
(gdb) quit
|
||||
----
|
||||
|
||||
Copy the content of `gdb.txt` into the bug report. Please also add some words
|
||||
about what you were doing (or what pages you visited) before the crash
|
||||
Now copy the last few lines of the debug log (before you got the gdb prompt)
|
||||
and the full content of `gdb.txt` into the bug report. Please also add some
|
||||
words about what you were doing (or what pages you visited) before the crash
|
||||
happened.
|
||||
|
||||
[[windows]]
|
||||
@@ -170,9 +130,9 @@ file displayed there.
|
||||
|
||||
Now install
|
||||
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*.
|
||||
Microsoft, then run the "DebugDiag 2 Analysis" tool. There, check
|
||||
"CrashHangAnalysis" and add your crash dump via "Add Data files". Then click
|
||||
"Start analysis".
|
||||
|
||||
Close the Internet Explorer which opens when it's done and use the
|
||||
folder-button at the top left to get to the reports. There find the report file
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
Writing qutebrowser userscripts
|
||||
===============================
|
||||
The Compiler <mail@qutebrowser.org>
|
||||
|
||||
qutebrowser is extensible by writing userscripts which can be called via the
|
||||
`:spawn --userscript` command, or via a key binding.
|
||||
|
||||
You can also call a userscript via hints so they get the selected hint URL by
|
||||
calling `:hint links userscript ...`.
|
||||
|
||||
These userscripts are similar to the (non-javascript) dwb userscripts. They can
|
||||
be written in any language which can read environment variables and write to a
|
||||
FIFO. Note they are *not* related to Greasemonkey userscripts.
|
||||
|
||||
Note for simple things such as opening the current page with another browser or
|
||||
mpv, a simple key binding to something like `:spawn mpv {url}` should suffice.
|
||||
|
||||
Also note userscripts need to have the executable bit set (`chmod +x`) for
|
||||
qutebrowser to run them.
|
||||
|
||||
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.
|
||||
|
||||
Getting information
|
||||
-------------------
|
||||
|
||||
The following environment variables will be set when a userscript is launched:
|
||||
|
||||
- `QUTE_MODE`: Either `hints` (started via hints) or `command` (started via
|
||||
command or key binding).
|
||||
- `QUTE_USER_AGENT`: The currently set user agent.
|
||||
- `QUTE_FIFO`: The FIFO or file to write commands to.
|
||||
- `QUTE_HTML`: Path of a file containing the HTML source of the current page.
|
||||
- `QUTE_TEXT`: Path of a file containing the plaintext of the current page.
|
||||
- `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.
|
||||
|
||||
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.
|
||||
|
||||
In `hints` mode:
|
||||
|
||||
- `QUTE_URL`: The URL selected via hints.
|
||||
- `QUTE_SELECTED_TEXT`: The plain text of the element selected via hints.
|
||||
- `QUTE_SELECTED_HTML` The HTML of the element selected via hints.
|
||||
|
||||
Sending commands
|
||||
----------------
|
||||
|
||||
Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be
|
||||
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
|
||||
soon as your userscript terminates. This means when writing multiple commands,
|
||||
you should append to the file (`>>` in bash) rather than overwrite it (`>`).
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Opening the currently selected word on http://www.dict.cc/[dict.cc]:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
#!/bin/bash
|
||||
|
||||
echo "open -t http://www.dict.cc/?s=$QUTE_SELECTED_TEXT" >> "$QUTE_FIFO"
|
||||
----
|
||||
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 945 B After Width: | Height: | Size: 872 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 21 KiB |
@@ -1,207 +0,0 @@
|
||||
/* XPM */
|
||||
static char *qutebrowser[] = {
|
||||
/* columns rows colors chars-per-pixel */
|
||||
"32 32 169 2 ",
|
||||
" c #0A396E",
|
||||
". c #0B3C72",
|
||||
"X c #0B4077",
|
||||
"o c #0C437B",
|
||||
"O c #134175",
|
||||
"+ c #15467C",
|
||||
"@ c #18477B",
|
||||
"# c #1A497D",
|
||||
"$ c #0D4B86",
|
||||
"% c #0F4E8D",
|
||||
"& c #124A80",
|
||||
"* c #1F4F83",
|
||||
"= c #0E518C",
|
||||
"- c #1F5084",
|
||||
"; c #11508C",
|
||||
": c #0F5193",
|
||||
"> c #115799",
|
||||
", c #115B9C",
|
||||
"< c #204F83",
|
||||
"1 c #245287",
|
||||
"2 c #2A598C",
|
||||
"3 c #325E8F",
|
||||
"4 c #11609F",
|
||||
"5 c #346496",
|
||||
"6 c #3B6898",
|
||||
"7 c #115CA1",
|
||||
"8 c #115EAC",
|
||||
"9 c #1263A3",
|
||||
"0 c #1260AD",
|
||||
"q c #136BAC",
|
||||
"w c #136BB2",
|
||||
"e c #1366BA",
|
||||
"r c #196BB2",
|
||||
"t c #157ABB",
|
||||
"y c #1577BB",
|
||||
"u c #2E6DB0",
|
||||
"i c #387FB1",
|
||||
"p c #456E9A",
|
||||
"a c #4873A1",
|
||||
"s c #4375AA",
|
||||
"d c #507AA6",
|
||||
"f c #597EA4",
|
||||
"g c #4D7EB3",
|
||||
"h c #156FCB",
|
||||
"j c #167AC5",
|
||||
"k c #1675CA",
|
||||
"l c #177BCE",
|
||||
"z c #1777D8",
|
||||
"x c #1476E4",
|
||||
"c c #167BE6",
|
||||
"v c #167DE8",
|
||||
"b c #197EEF",
|
||||
"n c #1A7FF0",
|
||||
"m c #1A80BE",
|
||||
"M c #5F87AF",
|
||||
"N c #5D8BBA",
|
||||
"B c #5A84B1",
|
||||
"V c #6C8FB3",
|
||||
"C c #6F96BE",
|
||||
"Z c #1886CC",
|
||||
"A c #1883D7",
|
||||
"S c #198DD5",
|
||||
"D c #1987D9",
|
||||
"F c #198ADC",
|
||||
"G c #1A96DC",
|
||||
"H c #3090D9",
|
||||
"J c #1682E9",
|
||||
"K c #1983ED",
|
||||
"L c #1689E9",
|
||||
"P c #1A8DEE",
|
||||
"I c #1B95ED",
|
||||
"U c #1C9EEA",
|
||||
"Y c #1B97E4",
|
||||
"T c #1A84F2",
|
||||
"R c #1A8BF2",
|
||||
"E c #1C94F4",
|
||||
"W c #1D9CF5",
|
||||
"Q c #3388E6",
|
||||
"! c #3D90E9",
|
||||
"~ c #228EF3",
|
||||
"^ c #229FF6",
|
||||
"/ c #3294F4",
|
||||
"( c #3D9FF6",
|
||||
") c #339CF4",
|
||||
"_ c #1CA2E5",
|
||||
"` c #1DABEE",
|
||||
"' c #1DA4F6",
|
||||
"] c #1EA9F7",
|
||||
"[ c #1EADF8",
|
||||
"{ c #1FB4F9",
|
||||
"} c #1FB9FA",
|
||||
"| c #20ACF8",
|
||||
" . c #27A4F6",
|
||||
".. c #3DA9F6",
|
||||
"X. c #20B9FA",
|
||||
"o. c #2EB6F9",
|
||||
"O. c #458DC9",
|
||||
"+. c #5C8DC1",
|
||||
"@. c #5795C6",
|
||||
"#. c #709DCB",
|
||||
"$. c #74A8DD",
|
||||
"%. c #4A97EA",
|
||||
"&. c #4896EA",
|
||||
"*. c #559EEA",
|
||||
"=. c #439AF5",
|
||||
"-. c #46A3F6",
|
||||
";. c #5FA9F6",
|
||||
":. c #5EA6F3",
|
||||
">. c #47BCF9",
|
||||
",. c #51B5F8",
|
||||
"<. c #58BDF8",
|
||||
"1. c #68ABEF",
|
||||
"2. c #7DB9E7",
|
||||
"3. c #63AEF7",
|
||||
"4. c #6FB1F7",
|
||||
"5. c #66B9F8",
|
||||
"6. c #61B2F6",
|
||||
"7. c #71B4F7",
|
||||
"8. c #78B7F4",
|
||||
"9. c #72BFF9",
|
||||
"0. c #3BC0FA",
|
||||
"q. c #6FCEFB",
|
||||
"w. c #6CC5FA",
|
||||
"e. c #7BCAF9",
|
||||
"r. c #89A7C3",
|
||||
"t. c #83A2C1",
|
||||
"y. c #98B6D3",
|
||||
"u. c #9DB9D3",
|
||||
"i. c #89B6E4",
|
||||
"p. c #83B6E9",
|
||||
"a. c #81BDF7",
|
||||
"s. c #83BFF8",
|
||||
"d. c #9EC4E9",
|
||||
"f. c #8CC2F9",
|
||||
"g. c #85CDFB",
|
||||
"h. c #87C4F9",
|
||||
"j. c #92C6F9",
|
||||
"k. c #95CAFA",
|
||||
"l. c #9CCBFA",
|
||||
"z. c #89D7FC",
|
||||
"x. c #91D9FC",
|
||||
"c. c #9CDEFD",
|
||||
"v. c #9ED2FB",
|
||||
"b. c #A7CAEC",
|
||||
"n. c #B5CEE3",
|
||||
"m. c #A1CEFA",
|
||||
"M. c #AED0F0",
|
||||
"N. c #ACD6FA",
|
||||
"B. c #A0DFFC",
|
||||
"V. c #AFD8FC",
|
||||
"C. c #B5D9FB",
|
||||
"Z. c #BCDDFC",
|
||||
"A. c #BFDCF5",
|
||||
"S. c #ACE3FD",
|
||||
"D. c #B5E5FE",
|
||||
"F. c #BBE2FC",
|
||||
"G. c #CFE5F5",
|
||||
"H. c #C3E1FC",
|
||||
"J. c #CAE6FD",
|
||||
"K. c #CCEBFD",
|
||||
"L. c #C4EBFE",
|
||||
"P. c #D6EDFE",
|
||||
"I. c #DAEEFD",
|
||||
"U. c #DEF1FE",
|
||||
"Y. c #D6F2FE",
|
||||
"T. c #E4F4FE",
|
||||
"R. c #E9F6FE",
|
||||
"E. c #EBF8FF",
|
||||
"W. c None",
|
||||
/* pixels */
|
||||
"W.W.W.W.W.W.W.W.W.W.W.c.S.L.Y.E.E.S.X.} W.W.W.W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.D.T.E.E.T.L.D.c.z.} } X.} } W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.B.T.T.R.T.R.U.0.X.z.S.} } } } { { X.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.x.x.K.T.T.T.L.P.q.o.{ } } ` _ { { { { { W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.c.P.D.G.u.r.i 9 Z _ { { G 4 X t { { { { { { W.W.W.W.W.",
|
||||
"W.W.W.W.K.U.n.f O { = t { { { { [ { { W.W.W.W.",
|
||||
"W.W.W.F.I.t.. ' t { { [ [ [ [ [ >.W.W.W.",
|
||||
"W.W.x.P.V ' X t ` [ [ [ [ [ [ o.e.W.W.",
|
||||
"W.W.J.y. X t S Y Z $ ' . y [ [ [ ] [ [ | Z.J.W.W.",
|
||||
"W.<.e.& , _ ] ] [ ] U . ' . y [ ' [ ] ] ] w.K.J.g.W.",
|
||||
"W.' S o ' ' [ ' [ ' ] o ' . y Y 9 = = 9 @.J.J.J.F.W.",
|
||||
"W.| , j ' ' ' ' ' ' ' o ' . $ p A.J.J.g.",
|
||||
"' .. G ' ' ' ' ' ' ' o ' . M H.H.h.",
|
||||
",.2. . W ' W ' ' ' ' W . ' . M.A.x.",
|
||||
"N.M.. . W W W ' W W W W .w 9 I U 0 #.Z.m.",
|
||||
" .9.O D W W W W ' W j $ % F W W W .5 d Z.C.",
|
||||
"W W ; 9 9.h.5...Q % o j W W W W W W O. 3 C.N.",
|
||||
"E W 7 B b.d.a . w E E W W W E W E A @ C.l.",
|
||||
"I E l u W E W E W E E E E A . - k.6.",
|
||||
"P E E 7 m.o E E E E E E E E l . = E P ",
|
||||
"L E E E > . O s.o E E E E E E E E 7 , E L ",
|
||||
"W.R E R ) #.5 1 6 N i.2 s.+ E E E E E E R L . k R W.",
|
||||
"W.L R E -.m.m.m.m.m.m.2 m.@ N m.m.s.( R R % X E J W.",
|
||||
"W.W.K R ~ a.m.l.l.l.l.2 s.+ < i.l.m.j.h % e K W.W.",
|
||||
"W.W.J R R / l.l.l.l.k.2 s.+ * 5 + 8 R J W.W.",
|
||||
"W.W.W.v T R 3.k.k.j.k.2 2 j.& . 8 R v W.W.W.",
|
||||
"W.W.W.W.J T ~ 7.j.j.j.g +.p.j.s.+. . . : z T v W.W.W.W.",
|
||||
"W.W.W.W.W.c T T =.f.j.j.s.j.j.j.j.$.g s u e h b T T v W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.c b n 4.f.f.s.m.s.s.s.j.s.j./ T n T b c W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.c x 1.s.s.s.s.s.s.s.s.4.=.n T n c c W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.&.*.1.a.s.s.s.s.3.n n v x x W.W.W.W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.W.W.W.W.W.W.%.%.%.%.*.*.Q x x x W.W.W.W.W.W.W.W.W.W.W."
|
||||
};
|
||||
@@ -29,8 +29,6 @@ profile qutebrowser /usr/{local/,}bin/qutebrowser {
|
||||
|
||||
/proc/*/mounts r,
|
||||
owner /tmp/** rwkl,
|
||||
owner /run/user/*/ rw,
|
||||
owner /run/user/*/** krw,
|
||||
|
||||
@{HOME}/.config/qutebrowser/** krw,
|
||||
@{HOME}/.local/share/qutebrowser/** krw,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
height="640"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.91 r13725"
|
||||
inkscape:version="0.48.5 r10040"
|
||||
version="1.0"
|
||||
sodipodi:docname="cheatsheet.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
@@ -32,22 +32,21 @@
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.7582312"
|
||||
inkscape:cx="875.18895"
|
||||
inkscape:cy="136.8726"
|
||||
inkscape:zoom="1.2432572"
|
||||
inkscape:cx="510.06077"
|
||||
inkscape:cy="315.85317"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="1024px"
|
||||
height="640px"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1362"
|
||||
inkscape:window-height="740"
|
||||
inkscape:window-width="1024"
|
||||
inkscape:window-height="723"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="24"
|
||||
inkscape:window-y="0"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:snap-text-baseline="true">
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
id="GridFromPre046Settings"
|
||||
type="xygrid"
|
||||
@@ -1455,27 +1454,23 @@
|
||||
x="714.29938"
|
||||
y="108.87096">)</tspan></text>
|
||||
<rect
|
||||
ry="3.3457608"
|
||||
y="363.19348"
|
||||
ry="4.3646927"
|
||||
y="363.55695"
|
||||
x="238.30771"
|
||||
height="44.799603"
|
||||
height="58.443066"
|
||||
width="361.69229"
|
||||
id="rect5017"
|
||||
style="font-size:18px;fill:#babdb6;fill-opacity:1;stroke:none" />
|
||||
<g
|
||||
id="g4061"
|
||||
transform="translate(0,-6.7232151)">
|
||||
<text
|
||||
id="text5021"
|
||||
y="395.78867"
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:13px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
|
||||
x="245.32532"
|
||||
y="395.78867"
|
||||
id="text5021"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5023"
|
||||
x="245.32532"
|
||||
style="font-style:normal;font-weight:normal;font-size:13px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
|
||||
xml:space="preserve"><tspan
|
||||
y="395.78867"
|
||||
x="245.32532"
|
||||
id="tspan5023"
|
||||
sodipodi:role="line">Space</tspan></text>
|
||||
</g>
|
||||
y="395.78867">Space</tspan></text>
|
||||
<text
|
||||
id="text6971"
|
||||
y="317.98907"
|
||||
@@ -1870,16 +1865,16 @@
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
x="317.63174"
|
||||
x="320.22501"
|
||||
y="195.40761"
|
||||
id="text7245"
|
||||
sodipodi:linespacing="89.999998%"><tspan
|
||||
sodipodi:role="line"
|
||||
x="317.63174"
|
||||
x="320.22501"
|
||||
y="195.40761"
|
||||
id="tspan7366" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="317.63174"
|
||||
x="320.22501"
|
||||
y="202.78995"
|
||||
id="tspan7249"
|
||||
style="font-size:8px">reload</tspan></text>
|
||||
@@ -1939,7 +1934,7 @@
|
||||
x="542.06946"
|
||||
sodipodi:role="line"
|
||||
id="tspan4938"
|
||||
style="font-size:8px">scroll</tspan><tspan
|
||||
style="font-size:8px">scoll</tspan><tspan
|
||||
y="276.1955"
|
||||
x="542.06946"
|
||||
sodipodi:role="line"
|
||||
@@ -2094,7 +2089,7 @@
|
||||
id="tspan4998"
|
||||
style="font-size:8px">new tab<tspan
|
||||
style="fill:#ff0000"
|
||||
id="tspan3699" /></tspan><tspan
|
||||
id="tspan3699"></tspan></tspan><tspan
|
||||
y="177.83009"
|
||||
x="670.26074"
|
||||
sodipodi:role="line"
|
||||
@@ -2629,8 +2624,8 @@
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
transform="translate(0,-38.539167)"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
|
||||
transform="translate(0,-14.539167)"><flowRegion
|
||||
id="flowRegion5693"><rect
|
||||
id="rect5695"
|
||||
width="322.5"
|
||||
@@ -2639,8 +2634,8 @@
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan5705">(1)</flowSpan> copying/yanking:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5701">yy - copy/yank URL</flowPara><flowPara
|
||||
@@ -2652,10 +2647,10 @@
|
||||
id="flowPara5709">yT - copy title to selection</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5711" /></flowRoot> <flowRoot
|
||||
transform="translate(0.713591,38.823906)"
|
||||
transform="translate(0.713591,62.823906)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-0"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-7"><rect
|
||||
id="rect5695-0"
|
||||
width="322.5"
|
||||
@@ -2664,8 +2659,8 @@
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697-9"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan5705-5">(2)</flowSpan> pasting:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5701-9">pp - open URL from clipboard</flowPara><flowPara
|
||||
@@ -2673,26 +2668,26 @@
|
||||
id="flowPara5703-8">pP - open URL from selection</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5707-0">Pp - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6101">pp</flowSpan>, in new tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5709-3">PP - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6103">pP</flowSpan>, in new tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5763">wp - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6105">pp</flowSpan>, in new window</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5765">wP - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6107">pP</flowSpan>, in new window</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5711-1" /></flowRoot> <flowRoot
|
||||
transform="translate(171.2479,-38.539167)"
|
||||
transform="translate(171.2479,-14.539167)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-0-9"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-7-0"><rect
|
||||
id="rect5695-0-5"
|
||||
width="322.5"
|
||||
@@ -2700,9 +2695,9 @@
|
||||
x="17.5"
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowPara5701-9-6"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan5705-5-8">(3)</flowSpan> navigation:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5829">[[ - click "previous"-link on page</flowPara><flowPara
|
||||
@@ -2710,11 +2705,11 @@
|
||||
id="flowPara5703-8-2">]] - click "next"-link on page</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5707-0-7">{{ - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6111">[[</flowSpan>, in new tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5709-3-1">}} - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6109">]]</flowSpan>, in new tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5835"><Ctrl-A> - increment no. in URL</flowPara><flowPara
|
||||
@@ -2774,10 +2769,10 @@
|
||||
id="tspan4936-1-1-9-2"
|
||||
style="font-size:8px;fill:#ff0000">(3)</tspan></text>
|
||||
<flowRoot
|
||||
transform="translate(169.83695,63.823906)"
|
||||
transform="translate(169.83695,87.823906)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-9"><rect
|
||||
id="rect5695-9"
|
||||
width="322.5"
|
||||
@@ -2786,8 +2781,8 @@
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697-3"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan5705-0">(4)</flowSpan> scrolling:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5701-8"><Ctrl-F> - page down</flowPara><flowPara
|
||||
@@ -2797,59 +2792,59 @@
|
||||
id="flowPara5962"><Ctrl-D> - half page down</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5711-7"><Ctrl-U> - half page up</flowPara></flowRoot> <flowRoot
|
||||
transform="translate(360.81663,-38.539167)"
|
||||
transform="translate(360.81663,-14.539167)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9"
|
||||
style="font-style:normal;font-weight:bold;font-size:40px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans Bold';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:bold;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"><flowRegion
|
||||
id="flowRegion5693-9-1"><rect
|
||||
id="rect5695-9-8"
|
||||
width="322.5"
|
||||
height="162.5"
|
||||
x="17.5"
|
||||
y="448.75"
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#000000" /></flowRegion><flowPara
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
|
||||
style="font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold" /></flowRegion><flowPara
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowPara4171">in prompt mode:</flowPara><flowPara
|
||||
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara4175">Enter - accept prompt</flowPara><flowPara
|
||||
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara4177">y - answer yes to prompt</flowPara><flowPara
|
||||
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara4179">n - answer no to prompt</flowPara><flowPara
|
||||
style="font-weight:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-weight:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara6016" /></flowRoot> <flowRoot
|
||||
transform="translate(360.8264,16.645949)"
|
||||
transform="translate(360.8264,40.645949)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-0-9-9"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;-inkscape-font-specification:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"><flowRegion
|
||||
id="flowRegion5693-7-0-2"><rect
|
||||
id="rect5695-0-5-6"
|
||||
width="322.5"
|
||||
height="162.5"
|
||||
x="17.5"
|
||||
y="448.75"
|
||||
style="font-style:normal;-inkscape-font-specification:Sans;fill:#000000" /></flowRegion><flowPara
|
||||
style="font-style:normal;font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
|
||||
style="font-style:normal;fill:#000000;-inkscape-font-specification:Sans" /></flowRegion><flowPara
|
||||
style="font-size:10px;font-style:normal;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowPara5701-9-6-8"><flowSpan
|
||||
style="font-style:normal;font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-style:normal;font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan5705-5-8-3">(6)</flowSpan> opening:</flowPara><flowPara
|
||||
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara5829-1">go - open based on cur. URL</flowPara><flowPara
|
||||
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara5703-8-2-8">gO - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6132">go</flowSpan>, in new tab</flowPara><flowPara
|
||||
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara3581">xO - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan6134">go</flowSpan>, in bg. tab</flowPara><flowPara
|
||||
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara5709-3-1-6">xo - open in background tab</flowPara><flowPara
|
||||
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara5841-1">wo - open in new window</flowPara><flowPara
|
||||
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara5839-8" /><flowPara
|
||||
style="font-style:normal;font-size:10px;-inkscape-font-specification:Sans;fill:#000000"
|
||||
style="font-size:10px;font-style:normal;fill:#000000;-inkscape-font-specification:Sans"
|
||||
id="flowPara5711-1-8-7" /></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
@@ -2904,10 +2899,10 @@
|
||||
id="tspan6219"
|
||||
style="font-size:8px">mode</tspan></text>
|
||||
<flowRoot
|
||||
transform="translate(361.29883,97.78408)"
|
||||
transform="translate(361.29883,121.78408)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-9-1-7"><rect
|
||||
id="rect5695-9-8-7"
|
||||
width="322.5"
|
||||
@@ -2916,8 +2911,8 @@
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697-3-7-6"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan5705-0-4-7">(7)</flowSpan> back/forward:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara5701-8-5-8"><flowSpan
|
||||
@@ -2964,10 +2959,10 @@
|
||||
style="font-size:8px;fill:#ff0000"
|
||||
id="tspan3662">(9)</tspan></tspan></text>
|
||||
<flowRoot
|
||||
transform="translate(526.15723,-38.548933)"
|
||||
transform="translate(526.15723,-14.548933)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3-6"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-9-1-7-3"><rect
|
||||
id="rect5695-9-8-7-7"
|
||||
width="322.5"
|
||||
@@ -2976,15 +2971,15 @@
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697-3-7-6-8"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#ff0000">(8)</flowPara><flowPara
|
||||
style="font-size:10px;font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold">(8)</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3626-7">prefix with w - in new window</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3725" /></flowRoot> <flowRoot
|
||||
transform="translate(525.65723,10.440325)"
|
||||
transform="translate(525.65723,34.440325)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3-1"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-9-1-7-1"><rect
|
||||
id="rect5695-9-8-7-5"
|
||||
width="322.5"
|
||||
@@ -2993,14 +2988,12 @@
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697-3-7-6-1"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan5705-0-4-7-6">(9)</flowSpan> extended hint mode:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3626-73">;b - open hint in background tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4051">;f - open hint in foreground tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3788">;h - hover over hint (mouse-over)</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3790">;i - hint images</flowPara><flowPara
|
||||
@@ -3010,7 +3003,7 @@
|
||||
id="flowPara3794">;o - put hinted URL in cmd. line</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3796">;O - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan3798">;o</flowSpan>, in new tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3800">;y - yank hinted URL to clipboard</flowPara><flowPara
|
||||
@@ -3020,33 +3013,37 @@
|
||||
id="flowPara3804">;r - rapid hinting</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3806">;R - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan3810">;r</flowSpan>, in new window</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3808">;d - download hinted URL</flowPara></flowRoot> <flowRoot
|
||||
transform="translate(706.84131,-38.539167)"
|
||||
transform="translate(706.84131,-14.539167)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3-6-1"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-9-1-7-3-5"><rect
|
||||
id="rect5695-9-8-7-7-0"
|
||||
width="154.90645"
|
||||
height="240.73535"
|
||||
width="148.08141"
|
||||
height="203.19766"
|
||||
x="17.5"
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697-3-7-6-8-2"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan3852">(10)</flowSpan> misc. commands:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3725-0">gt - switch tabs by name</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4052"><flowSpan
|
||||
id="flowPara3725-0"><flowSpan
|
||||
style="fill:#0000ff"
|
||||
id="flowSpan4054">gm/gl/lr</flowSpan> - move tab</flowPara><flowPara
|
||||
id="flowSpan5471">gm</flowSpan> - move tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4056"> (to index/left/right)</flowPara><flowPara
|
||||
id="flowPara3854"><flowSpan
|
||||
style="fill:#0000ff"
|
||||
id="flowSpan5473">gl</flowSpan> - move tab to left</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3856"><flowSpan
|
||||
style="fill:#0000ff"
|
||||
id="flowSpan5475">gr</flowSpan> - move tab to right</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3858">gC - clone tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
@@ -3055,7 +3052,7 @@
|
||||
id="flowPara3915">gu - navigate up in URL</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3917">gU - like <flowSpan
|
||||
style="font-style:italic;-inkscape-font-specification:'Sans Italic'"
|
||||
style="font-style:italic;-inkscape-font-specification:Sans Italic"
|
||||
id="flowSpan3923">gu</flowSpan>, in new tab</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3921">sf - save config</flowPara><flowPara
|
||||
@@ -3075,16 +3072,10 @@
|
||||
id="flowPara4169"><flowSpan
|
||||
style="fill:#0000ff"
|
||||
id="flowSpan5438">ad</flowSpan> - cancel download</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4077">co - close other tabs</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4081">cd - clear downloads</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3933" /><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3935" /><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4079" /></flowRoot> <text
|
||||
id="flowPara3935" /></flowRoot> <text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text9514-8-9-0-8"
|
||||
y="204.26315"
|
||||
@@ -3121,10 +3112,10 @@
|
||||
id="tspan4936-1-1-9-59-5"
|
||||
style="font-size:8px;fill:#ff0000">(10)</tspan></text>
|
||||
<flowRoot
|
||||
transform="translate(841.04351,-38.539167)"
|
||||
transform="translate(841.04351,-14.539167)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3-6-1-2"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-9-1-7-3-5-2"><rect
|
||||
id="rect5695-9-8-7-7-0-9"
|
||||
width="328.31396"
|
||||
@@ -3133,8 +3124,8 @@
|
||||
y="448.75"
|
||||
style="fill:#000000" /></flowRegion><flowPara
|
||||
id="flowPara5697-3-7-6-8-2-0"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"><flowSpan
|
||||
style="font-weight:bold;-inkscape-font-specification:'Sans Bold';fill:#ff0000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"><flowSpan
|
||||
style="font-weight:bold;fill:#ff0000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowSpan3852-6">(11)</flowSpan> modifier commands:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3933-6"><Alt-num> - select tab</flowPara><flowPara
|
||||
@@ -3150,11 +3141,11 @@
|
||||
id="flowPara4138"><Ctrl-S> - stop loading</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4140"><Ctrl-Alt-P> - print</flowPara><flowPara
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowPara4142">in insert mode:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4144"><Ctrl-E> - open editor</flowPara><flowPara
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#000000"
|
||||
style="font-size:10px;font-weight:bold;fill:#000000;-inkscape-font-specification:Sans Bold"
|
||||
id="flowPara4146">in command mode:</flowPara><flowPara
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara4148"><Ctrl-P> - prev. history item</flowPara><flowPara
|
||||
@@ -3163,142 +3154,126 @@
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
|
||||
id="rect3764-9"
|
||||
width="60"
|
||||
height="45.993073"
|
||||
height="60"
|
||||
x="168.32558"
|
||||
y="362"
|
||||
ry="3.4348924" />
|
||||
ry="4.480969" />
|
||||
<rect
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
|
||||
id="rect3764-9-3"
|
||||
width="60"
|
||||
height="45.993073"
|
||||
height="60"
|
||||
x="47.906979"
|
||||
y="362"
|
||||
ry="3.4348924" />
|
||||
ry="4.480969" />
|
||||
<rect
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
|
||||
id="rect3764-9-1"
|
||||
width="60"
|
||||
height="45.993073"
|
||||
height="60"
|
||||
x="613.81396"
|
||||
y="362"
|
||||
ry="3.4348924" />
|
||||
ry="4.480969" />
|
||||
<rect
|
||||
style="font-size:18px;fill:#eeeeec;fill-opacity:1;stroke:none"
|
||||
id="rect3764-9-7"
|
||||
width="60"
|
||||
height="45.993073"
|
||||
height="60"
|
||||
x="730.46509"
|
||||
y="362"
|
||||
ry="3.4348924" />
|
||||
<g
|
||||
id="g4049"
|
||||
transform="translate(1.3728676,-1.9658966)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
|
||||
ry="4.480969" />
|
||||
<text
|
||||
id="text7358-8"
|
||||
y="395.78867"
|
||||
x="62.269463"
|
||||
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
|
||||
xml:space="preserve"><tspan
|
||||
y="395.78867"
|
||||
x="62.269463"
|
||||
y="385.78867"
|
||||
id="text7358-8"><tspan
|
||||
style="font-size:12px;font-family:'DejaVu Sans Mono'"
|
||||
sodipodi:role="line"
|
||||
id="tspan7360-1"
|
||||
x="62.269463"
|
||||
y="385.78867">Ctrl</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="67.315361"
|
||||
y="400.26315"
|
||||
id="text9514-8-9-0-8-4-0"
|
||||
sodipodi:linespacing="89.999998%"><tspan
|
||||
style="font-size:8px;fill:#ff0000"
|
||||
id="tspan4936-1-1-9-59-8-3"
|
||||
sodipodi:role="line"
|
||||
x="67.315361"
|
||||
y="400.26315">(11)</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g4055"
|
||||
transform="translate(1.6278992,-11.965897)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="186.34709"
|
||||
id="tspan7360-1"
|
||||
sodipodi:role="line"
|
||||
style="font-size:12px;font-family:DejaVu Sans Mono">Ctrl</tspan></text>
|
||||
<text
|
||||
id="text7358-8-3"
|
||||
y="395.78867"
|
||||
x="745.17719"
|
||||
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
|
||||
xml:space="preserve"><tspan
|
||||
y="395.78867"
|
||||
id="text7358-8-3-8-1"><tspan
|
||||
style="font-size:12px;font-family:'DejaVu Sans Mono'"
|
||||
sodipodi:role="line"
|
||||
id="tspan7360-1-7-0-2"
|
||||
x="186.34709"
|
||||
y="395.78867">Alt</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="187.47893"
|
||||
y="410.26315"
|
||||
id="text9514-8-9-0-8-4-0-8"
|
||||
sodipodi:linespacing="89.999998%"><tspan
|
||||
style="font-size:8px;fill:#ff0000"
|
||||
id="tspan4936-1-1-9-59-8-3-8"
|
||||
sodipodi:role="line"
|
||||
x="187.47893"
|
||||
y="410.26315">(11)</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g4065"
|
||||
transform="translate(5.706604,-11.965897)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="627.75677"
|
||||
y="395.78867"
|
||||
id="text7358-8-3-8"><tspan
|
||||
style="font-size:12px;font-family:'DejaVu Sans Mono'"
|
||||
sodipodi:role="line"
|
||||
id="tspan7360-1-7-0"
|
||||
x="627.75677"
|
||||
y="395.78867">Alt</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="628.88861"
|
||||
y="410.26315"
|
||||
id="text9514-8-9-0-8-4-0-7"
|
||||
sodipodi:linespacing="89.999998%"><tspan
|
||||
style="font-size:8px;fill:#ff0000"
|
||||
id="tspan4936-1-1-9-59-8-3-82"
|
||||
sodipodi:role="line"
|
||||
x="628.88861"
|
||||
y="410.26315">(11)</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
id="g4071"
|
||||
transform="translate(1.0232544,-11.965897)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:12px;font-family:'DejaVu Sans Mono';fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="745.17719"
|
||||
id="tspan7360-1-7"
|
||||
sodipodi:role="line"
|
||||
style="font-size:12px;font-family:DejaVu Sans Mono">Ctrl</tspan></text>
|
||||
<text
|
||||
id="text7358-8-3-8"
|
||||
y="395.78867"
|
||||
x="627.75677"
|
||||
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
|
||||
xml:space="preserve"><tspan
|
||||
y="395.78867"
|
||||
id="text7358-8-3"><tspan
|
||||
style="font-size:12px;font-family:'DejaVu Sans Mono'"
|
||||
sodipodi:role="line"
|
||||
id="tspan7360-1-7"
|
||||
x="745.17719"
|
||||
y="395.78867">Ctrl</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||
x="750.22308"
|
||||
x="627.75677"
|
||||
id="tspan7360-1-7-0"
|
||||
sodipodi:role="line"
|
||||
style="font-size:12px;font-family:DejaVu Sans Mono">Alt</tspan></text>
|
||||
<text
|
||||
id="text7358-8-3-8-1"
|
||||
y="395.78867"
|
||||
x="186.34709"
|
||||
style="font-size:12px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono"
|
||||
xml:space="preserve"><tspan
|
||||
y="395.78867"
|
||||
x="186.34709"
|
||||
id="tspan7360-1-7-0-2"
|
||||
sodipodi:role="line"
|
||||
style="font-size:12px;font-family:DejaVu Sans Mono">Alt</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text9514-8-9-0-8-4-0"
|
||||
y="410.26315"
|
||||
x="67.315361"
|
||||
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
xml:space="preserve"><tspan
|
||||
y="410.26315"
|
||||
id="text9514-8-9-0-8-4-0-3"
|
||||
sodipodi:linespacing="89.999998%"><tspan
|
||||
style="font-size:8px;fill:#ff0000"
|
||||
id="tspan4936-1-1-9-59-8-3-4"
|
||||
sodipodi:role="line"
|
||||
x="750.22308"
|
||||
y="410.26315">(11)</tspan></text>
|
||||
</g>
|
||||
x="67.315361"
|
||||
sodipodi:role="line"
|
||||
id="tspan4936-1-1-9-59-8-3"
|
||||
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text9514-8-9-0-8-4-0-8"
|
||||
y="410.26315"
|
||||
x="187.47893"
|
||||
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
xml:space="preserve"><tspan
|
||||
y="410.26315"
|
||||
x="187.47893"
|
||||
sodipodi:role="line"
|
||||
id="tspan4936-1-1-9-59-8-3-8"
|
||||
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text9514-8-9-0-8-4-0-7"
|
||||
y="410.26315"
|
||||
x="628.88861"
|
||||
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
xml:space="preserve"><tspan
|
||||
y="410.26315"
|
||||
x="628.88861"
|
||||
sodipodi:role="line"
|
||||
id="tspan4936-1-1-9-59-8-3-82"
|
||||
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text9514-8-9-0-8-4-0-3"
|
||||
y="410.26315"
|
||||
x="750.22308"
|
||||
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
xml:space="preserve"><tspan
|
||||
y="410.26315"
|
||||
x="750.22308"
|
||||
sodipodi:role="line"
|
||||
id="tspan4936-1-1-9-59-8-3-4"
|
||||
style="font-size:8px;fill:#ff0000">(11)</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
@@ -3322,15 +3297,27 @@
|
||||
style="font-size:8px">tab</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#ff0000;fill-opacity:1;stroke:none"
|
||||
x="274.21381"
|
||||
y="343.17578"
|
||||
style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
x="267.67316"
|
||||
y="326.20523"
|
||||
id="text10547-23-6-7"
|
||||
sodipodi:linespacing="89.999998%"><tspan
|
||||
sodipodi:role="line"
|
||||
x="274.21381"
|
||||
y="343.17578"
|
||||
id="tspan4052">(10)</tspan></text>
|
||||
x="267.67316"
|
||||
y="326.20523"
|
||||
id="tspan10560-1-3-1" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="267.67316"
|
||||
y="333.40524"
|
||||
id="tspan5325">co: close</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="267.67316"
|
||||
y="340.60522"
|
||||
id="tspan5327">other</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="267.67316"
|
||||
y="347.80524"
|
||||
id="tspan10562-12-5-98">tabs</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text10564-6-7-8-0"
|
||||
@@ -3411,10 +3398,10 @@
|
||||
id="tspan4936-1-1-9-59-5-6"
|
||||
style="font-size:8px;fill:#ff0000">(10)</tspan></text>
|
||||
<flowRoot
|
||||
transform="translate(838.55559,134.52236)"
|
||||
transform="translate(838.55559,158.52236)"
|
||||
xml:space="preserve"
|
||||
id="flowRoot5691-4-9-3-6-6"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"><flowRegion
|
||||
id="flowRegion5693-9-1-7-3-8"><rect
|
||||
id="rect5695-9-8-7-7-6"
|
||||
width="322.5"
|
||||
@@ -3425,50 +3412,9 @@
|
||||
style="font-size:10px;fill:#000000"
|
||||
id="flowPara3626-7-0"><flowSpan
|
||||
id="flowSpan5520"
|
||||
style="font-weight:bold;font-size:10px;-inkscape-font-specification:'Sans Bold';fill:#0000ff">blue keys </flowSpan><flowSpan
|
||||
style="font-size:10px;font-weight:bold;fill:#0000ff;-inkscape-font-specification:Sans Bold">blue keys </flowSpan><flowSpan
|
||||
style="fill:#0000ff"
|
||||
id="flowSpan5528">can be</flowSpan></flowPara><flowPara
|
||||
style="font-size:10px;fill:#0000ff"
|
||||
id="flowPara3725-9">prefixed by a count</flowPara></flowRoot> <text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
x="317.95987"
|
||||
y="155.85321"
|
||||
id="text7245-1-7"
|
||||
sodipodi:linespacing="89.999998%"><tspan
|
||||
sodipodi:role="line"
|
||||
x="317.95987"
|
||||
y="155.85321"
|
||||
id="tspan7366-3-3" /><tspan
|
||||
sodipodi:role="line"
|
||||
x="317.95987"
|
||||
y="163.23555"
|
||||
id="tspan5293-5"
|
||||
style="font-size:8px">reload </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="317.95987"
|
||||
y="170.43555"
|
||||
style="font-size:8px"
|
||||
id="tspan3716">(bypass </tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="317.95987"
|
||||
y="177.63554"
|
||||
style="font-size:8px"
|
||||
id="tspan3719">cache)</tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text9514-60-7-7-0-8"
|
||||
y="338.04874"
|
||||
x="342.42523"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8px;line-height:89.99999762%;font-family:TlwgTypewriter;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
|
||||
xml:space="preserve"><tspan
|
||||
y="338.04874"
|
||||
x="342.42523"
|
||||
sodipodi:role="line"
|
||||
id="tspan5689-6">visual</tspan><tspan
|
||||
y="345.24875"
|
||||
x="342.42523"
|
||||
sodipodi:role="line"
|
||||
id="tspan4112">mode</tspan></text>
|
||||
</g>
|
||||
id="flowPara3725-9">prefixed by a count</flowPara></flowRoot> </g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 135 KiB |
@@ -1,35 +0,0 @@
|
||||
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
|
||||
@@ -1,35 +0,0 @@
|
||||
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
|
||||
@@ -1,37 +0,0 @@
|
||||
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,74 +0,0 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.getcwd())
|
||||
from scripts import setupcommon
|
||||
|
||||
block_cipher = None
|
||||
|
||||
|
||||
def get_data_files():
|
||||
data_files = [
|
||||
('../qutebrowser/html', 'html'),
|
||||
('../qutebrowser/img', 'img'),
|
||||
('../qutebrowser/javascript', 'javascript'),
|
||||
('../qutebrowser/html/doc', 'html/doc'),
|
||||
('../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!")
|
||||
|
||||
return data_files
|
||||
|
||||
|
||||
setupcommon.write_git_file()
|
||||
|
||||
|
||||
if os.name == 'nt':
|
||||
icon = 'icons/qutebrowser.ico'
|
||||
elif sys.platform == 'darwin':
|
||||
icon = 'icons/qutebrowser.icns'
|
||||
else:
|
||||
icon = None
|
||||
|
||||
|
||||
a = Analysis(['../qutebrowser/__main__.py'],
|
||||
pathex=['misc'],
|
||||
binaries=None,
|
||||
datas=get_data_files(),
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
win_no_prefer_redirects=False,
|
||||
win_private_assemblies=False,
|
||||
cipher=block_cipher)
|
||||
pyz = PYZ(a.pure, a.zipped_data,
|
||||
cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
exclude_binaries=True,
|
||||
name='qutebrowser',
|
||||
icon=icon,
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False )
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
name='qutebrowser')
|
||||
|
||||
app = BUNDLE(coll,
|
||||
name='qutebrowser.app',
|
||||
icon=icon,
|
||||
info_plist={'NSHighResolutionCapable': 'True'},
|
||||
bundle_identifier=None)
|
||||
@@ -1,20 +0,0 @@
|
||||
This directory contains various `requirements` files which are used by `tox` to
|
||||
have reproducable tests with pinned versions.
|
||||
|
||||
The files are generated based on unpinned requirements in `*.txt-raw` files.
|
||||
|
||||
Those files can also contain some special commands:
|
||||
|
||||
- Add an additional comment to a line: `#@ comment: <package> <comment here>`
|
||||
- Filter a line for requirements.io: `#@ filter: <package> <filter>`
|
||||
- Don't include a package in the output: `#@ ignore: <package>` (or multiple packages)
|
||||
- Replace a part of a frozen package specification with another: `#@ replace <regex> <replacement>`
|
||||
|
||||
Some examples:
|
||||
|
||||
```
|
||||
#@ comment: mypkg blah blub
|
||||
#@ filter: mypkg != 1.0.0
|
||||
#@ ignore: mypkg, otherpkg
|
||||
#@ replace: foo bar
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
check-manifest==0.31
|
||||
@@ -1 +0,0 @@
|
||||
check-manifest
|
||||
@@ -1,5 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
codecov==2.0.5
|
||||
coverage==4.1
|
||||
requests==2.10.0
|
||||
@@ -1 +0,0 @@
|
||||
codecov
|
||||
@@ -1,3 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
cx-Freeze==4.3.4
|
||||
@@ -1 +0,0 @@
|
||||
cx_Freeze
|
||||
@@ -1,26 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
flake8==2.6.2 # rq.filter: < 3.0.0
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==1.4.0
|
||||
flake8-deprecated==1.0
|
||||
flake8-docstrings==0.2.8
|
||||
flake8-future-import==0.4.3
|
||||
flake8-mock==0.2
|
||||
flake8-pep3101==0.4
|
||||
flake8-putty==0.4.0
|
||||
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.0.0
|
||||
pydocstyle==1.0.0
|
||||
pyflakes==1.2.3
|
||||
pyparsing==2.1.5
|
||||
six==1.10.0
|
||||
@@ -1,23 +0,0 @@
|
||||
flake8<3.0.0
|
||||
flake8-copyright
|
||||
flake8-debugger
|
||||
flake8-deprecated
|
||||
flake8-docstrings
|
||||
flake8-future-import
|
||||
flake8-mock
|
||||
flake8-pep3101
|
||||
flake8-putty
|
||||
flake8-string-format
|
||||
flake8-tidy-imports
|
||||
flake8-tuple
|
||||
hacking
|
||||
pep8-naming
|
||||
pydocstyle
|
||||
pyflakes
|
||||
|
||||
pep8==1.7.0
|
||||
|
||||
#@ comment: pep257 still needed by flake8-docstrings but ignored
|
||||
|
||||
# Waiting until hacking/flake8-tuple are updated
|
||||
#@ filter: flake8 < 3.0.0
|
||||
@@ -1 +0,0 @@
|
||||
pip==8.1.2
|
||||
@@ -1,3 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyInstaller==3.2
|
||||
@@ -1 +0,0 @@
|
||||
PyInstaller
|
||||
@@ -1,11 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
||||
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.10.0
|
||||
six==1.10.0
|
||||
wrapt==1.10.8
|
||||
@@ -1,13 +0,0 @@
|
||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||
./scripts/dev/pylint_checkers
|
||||
requests
|
||||
|
||||
# https://github.com/PyCQA/pylint/issues/932
|
||||
mccabe==0.5.0
|
||||
|
||||
# remove @commit-id for scm installs
|
||||
#@ replace: @.*# #
|
||||
|
||||
# fix qute-pylint location
|
||||
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers
|
||||
@@ -1,11 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
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.10.0
|
||||
six==1.10.0
|
||||
wrapt==1.10.8
|
||||
@@ -1,6 +0,0 @@
|
||||
pylint
|
||||
./scripts/dev/pylint_checkers
|
||||
requests
|
||||
|
||||
# fix qute-pylint location
|
||||
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers
|
||||
@@ -1,4 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.12
|
||||
pyroma==2.0.2
|
||||
@@ -1 +0,0 @@
|
||||
pyroma
|
||||
@@ -1,6 +0,0 @@
|
||||
Jinja2
|
||||
Pygments
|
||||
pyPEG2
|
||||
PyYAML
|
||||
colorama
|
||||
cssutils
|
||||
@@ -1,32 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
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.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.3.0
|
||||
pytest-faulthandler==1.3.0
|
||||
pytest-instafail==0.3.0
|
||||
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==0.2.0
|
||||
six==1.10.0
|
||||
vulture==0.10
|
||||
Werkzeug==0.11.10
|
||||
@@ -1,22 +0,0 @@
|
||||
beautifulsoup4
|
||||
CherryPy
|
||||
coverage
|
||||
Flask==0.10.1
|
||||
httpbin
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
pytest-catchlog
|
||||
pytest-cov
|
||||
pytest-faulthandler
|
||||
pytest-instafail
|
||||
pytest-mock
|
||||
pytest-qt
|
||||
pytest-repeat
|
||||
pytest-rerunfailures
|
||||
pytest-travis-fold
|
||||
pytest-xvfb
|
||||
vulture
|
||||
|
||||
#@ filter: Flask < 0.11.0
|
||||
#@ ignore: Jinja2, MarkupSafe
|
||||
@@ -1,6 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pluggy==0.3.1
|
||||
py==1.4.31
|
||||
tox==2.3.1
|
||||
virtualenv==15.0.2
|
||||
@@ -1 +0,0 @@
|
||||
tox
|
||||
@@ -1,3 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==0.10
|
||||
@@ -1 +0,0 @@
|
||||
vulture
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Pipes history, quickmarks, and URL into dmenu.
|
||||
#
|
||||
# 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:
|
||||
# :bind o spawn --userscript dmenu_qutebrowser
|
||||
#
|
||||
# Use the hotkey to open in new tab/window, press 'o' to open URL in current tab/window
|
||||
# You can simulate "go" by pressing "o<tab>", as the current URL is always first in the list
|
||||
#
|
||||
# I personally use "<Mod4>o" to launch this script. For me, my workflow is:
|
||||
# Default keys Keys with this script
|
||||
# O <Mod4>o
|
||||
# o o
|
||||
# go o<Tab>
|
||||
# gO gC, then o<Tab>
|
||||
# (This is unnecessarily long. I use this rarely, feel free to make this script accept parameters.)
|
||||
#
|
||||
|
||||
[ -z "$QUTE_URL" ] && QUTE_URL='http://google.com'
|
||||
|
||||
url=$(echo "$QUTE_URL" | cat - "$QUTE_CONFIG_DIR/quickmarks" "$QUTE_DATA_DIR/history" | dmenu -l 15 -p qutebrowser)
|
||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | egrep "https?:" || echo "$url")
|
||||
|
||||
[ -z "${url// }" ] && exit
|
||||
|
||||
echo "open $url" >> "$QUTE_FIFO" || qutebrowser "$url"
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Both standalone script and qutebrowser userscript that opens a rofi menu with
|
||||
# all files from the download director and opens the selected file. It works
|
||||
# both as a userscript and a standalone script that is called from outside of
|
||||
# qutebrowser.
|
||||
#
|
||||
# Suggested keybinding (for "show downloads"):
|
||||
# spawn --userscript ~/.config/qutebrowser/open_download
|
||||
# sd
|
||||
#
|
||||
# Requirements:
|
||||
# - 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 remove-finished-downloads. If you want to
|
||||
# see the recent downloads, just press "sd".
|
||||
#
|
||||
# Thorsten Wißmann, 2015 (thorsten` on freenode)
|
||||
# Any feedback is welcome!
|
||||
|
||||
set -e
|
||||
|
||||
# open a file from the download directory using rofi
|
||||
DOWNLOAD_DIR=${DOWNLOAD_DIR:-$QUTE_DOWNLOAD_DIR}
|
||||
DOWNLOAD_DIR=${DOWNLOAD_DIR:-$HOME/Downloads}
|
||||
# the name of the rofi command
|
||||
ROFI_CMD=${ROFI_CMD:-rofi}
|
||||
ROFI_ARGS=${ROFI_ARGS:-}
|
||||
|
||||
msg() {
|
||||
local cmd="$1"
|
||||
shift
|
||||
local msg="$*"
|
||||
if [ -z "$QUTE_FIFO" ] ; then
|
||||
echo "$cmd: $msg" >&2
|
||||
else
|
||||
echo "message-$cmd '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||
fi
|
||||
}
|
||||
die() {
|
||||
msg error "$*"
|
||||
if [ -n "$QUTE_FIFO" ] ; then
|
||||
# when run as a userscript, the above error message already informs the
|
||||
# user about the failure, and no additional "userscript exited with status
|
||||
# 1" is needed.
|
||||
exit 0;
|
||||
else
|
||||
exit 1;
|
||||
fi
|
||||
}
|
||||
|
||||
if ! [ -d "$DOWNLOAD_DIR" ] ; then
|
||||
die "Download directory »$DOWNLOAD_DIR« not found!"
|
||||
fi
|
||||
if ! which "${ROFI_CMD}" > /dev/null ; then
|
||||
die "Rofi command »${ROFI_CMD}« not found in PATH!"
|
||||
fi
|
||||
|
||||
rofi_default_args=(
|
||||
-monitor -2 # place above window
|
||||
-location 6 # aligned at the bottom
|
||||
-width 100 # use full window width
|
||||
-i
|
||||
-no-custom
|
||||
-format i # make rofi return the index
|
||||
-l 10
|
||||
-p 'Open download:' -dmenu
|
||||
)
|
||||
|
||||
crop-first-column() {
|
||||
local maxlength=${1:-40}
|
||||
local expression='s|^\([^\t]\{0,'"$maxlength"'\}\)[^\t]*\t|\1\t|'
|
||||
sed "$expression"
|
||||
}
|
||||
|
||||
ls-files() {
|
||||
# add the slash at the end of the download dir enforces to follow the
|
||||
# symlink, if the DOWNLOAD_DIR itself is a symlink
|
||||
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
|
||||
| grep '^[-]' \
|
||||
| cut -d' ' -f3- \
|
||||
| sed 's,^\(.*[^\]\) \(.*\)$,\2\t\1,' \
|
||||
| sed 's,\\\(.\),\1,g'
|
||||
}
|
||||
|
||||
mapfile -t entries < <(ls-files)
|
||||
|
||||
# we need to manually check that there are items, because rofi doesn't show up
|
||||
# if there are no items and -no-custom is passed to rofi.
|
||||
if [ "${#entries[@]}" -eq 0 ] ; then
|
||||
die "Download directory »${DOWNLOAD_DIR}« empty"
|
||||
fi
|
||||
|
||||
line=$(printf "%s\n" "${entries[@]}" \
|
||||
| crop-first-column 55 \
|
||||
| column -s $'\t' -t \
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" $ROFI_ARGS) || true
|
||||
if [ -z "$line" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
file="${entries[$line]}"
|
||||
file="${file%%$'\t'*}"
|
||||
path="$DOWNLOAD_DIR/$file"
|
||||
filetype=$(xdg-mime query filetype "$path")
|
||||
application=$(xdg-mime query default "$filetype")
|
||||
|
||||
if [ -z "$application" ] ; then
|
||||
die "Do not know how to open »$file« of type $filetype"
|
||||
fi
|
||||
|
||||
msg info "Opening »$file« (of type $filetype) with ${application%.desktop}"
|
||||
|
||||
xdg-open "$path" &
|
||||
@@ -1,39 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright 2015 jnphilipp <me@jnphilipp.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# 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:
|
||||
# :bind gF spawn --userscript openfeeds
|
||||
#
|
||||
# Use the hotkey to open the feeds in new tab/window, press 'gF' to open
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import urljoin
|
||||
|
||||
with open(os.environ['QUTE_HTML'], 'r') as f:
|
||||
soup = BeautifulSoup(f)
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as f:
|
||||
for link in soup.find_all('link', rel='alternate', type=re.compile(r'application/((rss|rdf|atom)\+)?xml|text/xml')):
|
||||
f.write('open -t %s\n' % urljoin(os.environ['QUTE_URL'], link.get('href')))
|
||||
@@ -1,365 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
help() {
|
||||
blink=$'\e[1;31m' reset=$'\e[0m'
|
||||
cat <<EOF
|
||||
This script can only be used as a userscript for qutebrowser
|
||||
2015, Thorsten Wißmann <edu _at_ thorsten-wissmann _dot_ de>
|
||||
In case of questions or suggestions, do not hesitate to send me an E-Mail or to
|
||||
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
|
||||
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
|
||||
|
||||
Usage: run as a userscript form qutebrowser, e.g.:
|
||||
spawn --userscript ~/.config/qutebrowser/password_fill
|
||||
|
||||
Pass backend: (see also passwordstore.org)
|
||||
This script expects pass to store the credentials of each page in an extra
|
||||
file, where the filename (or filepath) contains the domain of the respective
|
||||
page. The first line of the file must contain the password, the login name
|
||||
must be contained in a later line beginning with "user:", "login:", or
|
||||
"username:" (configurable by the user_pattern variable).
|
||||
|
||||
Behavior:
|
||||
It will try to find a username/password entry in the configured backend
|
||||
(currently only pass) for the current website and will load that pair of
|
||||
username and password to any form on the current page that has some password
|
||||
entry field. If multiple entries are found, a zenity menu is offered.
|
||||
|
||||
If no entry is found, then it crops subdomains from the url if at least one
|
||||
entry is found in the backend. (In that case, it always shows a menu)
|
||||
|
||||
Configuration:
|
||||
This script loads the bash script ~/.config/qutebrowser/password_fill_rc (if
|
||||
it exists), so you can change any configuration variable and overwrite any
|
||||
function you like.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
shopt -s nocasematch # make regexp matching in bash case insensitive
|
||||
|
||||
if [ -z "$QUTE_FIFO" ] ; then
|
||||
help
|
||||
exit
|
||||
fi
|
||||
|
||||
error() {
|
||||
local msg="$*"
|
||||
echo "message-error '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||
}
|
||||
msg() {
|
||||
local msg="$*"
|
||||
echo "message-info '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
|
||||
}
|
||||
die() {
|
||||
error "$*"
|
||||
exit 0
|
||||
}
|
||||
|
||||
javascript_escape() {
|
||||
# print the first argument in an escaped way, such that it can safely
|
||||
# be used within javascripts double quotes
|
||||
sed "s,[\\\'\"],\\\&,g" <<< "$1"
|
||||
}
|
||||
|
||||
# ======================================================= #
|
||||
# CONFIGURATION
|
||||
# ======================================================= #
|
||||
# The configuration file is per default located in
|
||||
# ~/.config/qutebrowser/password_fill_rc and is a bash script that is loaded
|
||||
# later in the present script. So basically you can replace all of the
|
||||
# following definitions and make them fit your needs.
|
||||
|
||||
# The following simplifies a URL to the domain (e.g. "wiki.qutebrowser.org")
|
||||
# which is later used to search the correct entries in the password backend. If
|
||||
# you e.g. don't want the "www." to be removed or if you want to distinguish
|
||||
# between different paths on the same domain.
|
||||
|
||||
simplify_url() {
|
||||
simple_url="${1##*://}" # remove protocol specification
|
||||
simple_url="${simple_url%%\?*}" # remove GET parameters
|
||||
simple_url="${simple_url%%/*}" # remove directory path
|
||||
simple_url="${simple_url%:*}" # remove port
|
||||
simple_url="${simple_url##www.}" # remove www. subdomain
|
||||
}
|
||||
|
||||
# no_entries_found() is called if the first query_entries() call did not find
|
||||
# any matching entries. Multiple implementations are possible:
|
||||
# The easiest behavior is to quit:
|
||||
#no_entries_found() {
|
||||
# if [ 0 -eq "${#files[@]}" ] ; then
|
||||
# die "No entry found for »$simple_url«"
|
||||
# fi
|
||||
#}
|
||||
# But you could also fill the files array with all entries from your pass db
|
||||
# if the first db query did not find anything
|
||||
# no_entries_found() {
|
||||
# if [ 0 -eq "${#files[@]}" ] ; then
|
||||
# query_entries ""
|
||||
# if [ 0 -eq "${#files[@]}" ] ; then
|
||||
# die "No entry found for »$simple_url«"
|
||||
# fi
|
||||
# fi
|
||||
# }
|
||||
|
||||
# Another behavior is to drop another level of subdomains until search hits
|
||||
# are found:
|
||||
no_entries_found() {
|
||||
while [ 0 -eq "${#files[@]}" ] && [ -n "$simple_url" ]; do
|
||||
shorter_simple_url=$(sed 's,^[^.]*\.,,' <<< "$simple_url")
|
||||
if [ "$shorter_simple_url" = "$simple_url" ] ; then
|
||||
# if no dot, then even remove the top level domain
|
||||
simple_url=""
|
||||
query_entries "$simple_url"
|
||||
break
|
||||
fi
|
||||
simple_url="$shorter_simple_url"
|
||||
query_entries "$simple_url"
|
||||
#die "No entry found for »$simple_url«"
|
||||
# enforce menu if we do "fuzzy" matching
|
||||
menu_if_one_entry=1
|
||||
done
|
||||
if [ 0 -eq "${#files[@]}" ] ; then
|
||||
die "No entry found for »$simple_url«"
|
||||
fi
|
||||
}
|
||||
|
||||
# Backend implementations tell, how the actual password store is accessed.
|
||||
# Right now, there is only one fully functional password backend, namely for
|
||||
# the program "pass".
|
||||
# A password backend consists of three actions:
|
||||
# - init() initializes backend-specific things and does sanity checks.
|
||||
# - query_entries() is called with a simplified url and is expected to fill
|
||||
# the bash array $files with the names of matching password entries. There
|
||||
# are no requirements how these names should look like.
|
||||
# - open_entry() is called with some specific entry of the $files array and is
|
||||
# expected to write the username of that entry to the $username variable and
|
||||
# the corresponding password to $password
|
||||
|
||||
reset_backend() {
|
||||
init() { true ; }
|
||||
query_entries() { true ; }
|
||||
open_entry() { true ; }
|
||||
}
|
||||
|
||||
# choose_entry() is expected to choose one entry from the array $files and
|
||||
# write it to the variable $file.
|
||||
choose_entry() {
|
||||
choose_entry_zenity
|
||||
}
|
||||
|
||||
# The default implementation chooses a random entry from the array. So if there
|
||||
# are multiple matching entries, multiple calls to this userscript will
|
||||
# eventually pick the "correct" entry. I.e. if this userscript is bound to
|
||||
# "zl", the user has to press "zl" until the correct username shows up in the
|
||||
# login form.
|
||||
choose_entry_random() {
|
||||
local nr=${#files[@]}
|
||||
file="${files[$((RANDOM % nr))]}"
|
||||
# Warn user, that there might be other matching password entries
|
||||
if [ "$nr" -gt 1 ] ; then
|
||||
msg "Picked $file out of $nr entries: ${files[*]}"
|
||||
fi
|
||||
}
|
||||
|
||||
# another implementation would be to ask the user via some menu (like rofi or
|
||||
# dmenu or zenity or even qutebrowser completion in future?) which entry to
|
||||
# pick
|
||||
MENU_COMMAND=( head -n 1 )
|
||||
# whether to show the menu if there is only one entry in it
|
||||
menu_if_one_entry=0
|
||||
choose_entry_menu() {
|
||||
local nr=${#files[@]}
|
||||
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
|
||||
file="${files[0]}"
|
||||
else
|
||||
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||
fi
|
||||
}
|
||||
|
||||
choose_entry_rofi() {
|
||||
MENU_COMMAND=( rofi -p "qutebrowser> " -dmenu
|
||||
-mesg $'Pick a password entry for <b>'"${QUTE_URL//&/&}"'</b>' )
|
||||
choose_entry_menu || true
|
||||
}
|
||||
|
||||
choose_entry_zenity() {
|
||||
MENU_COMMAND=( zenity --list --title "Qutebrowser password fill"
|
||||
--text "Pick the password entry:"
|
||||
--column "Name" )
|
||||
choose_entry_menu || true
|
||||
}
|
||||
|
||||
choose_entry_zenity_radio() {
|
||||
zenity_helper() {
|
||||
awk '{ print $0 ; print $0 }' \
|
||||
| zenity --list --radiolist \
|
||||
--title "Qutebrowser password fill" \
|
||||
--text "Pick the password entry:" \
|
||||
--column " " --column "Name"
|
||||
}
|
||||
MENU_COMMAND=( zenity_helper )
|
||||
choose_entry_menu || true
|
||||
}
|
||||
|
||||
# =======================================================
|
||||
# backend: PASS
|
||||
|
||||
# configuration options:
|
||||
match_filename=1 # whether allowing entry match by filepath
|
||||
match_line=0 # whether allowing entry match by URL-Pattern in file
|
||||
# Note: match_line=1 gets very slow, even for small password stores!
|
||||
match_line_pattern='^url: .*' # applied using grep -iE
|
||||
user_pattern='^(user|username|login): '
|
||||
|
||||
GPG_OPTS=( "--quiet" "--yes" "--compress-algo=none" "--no-encrypt-to" )
|
||||
GPG="gpg"
|
||||
export GPG_TTY="${GPG_TTY:-$(tty 2>/dev/null)}"
|
||||
which gpg2 &>/dev/null && GPG="gpg2"
|
||||
[[ -n $GPG_AGENT_INFO || $GPG == "gpg2" ]] && GPG_OPTS+=( "--batch" "--use-agent" )
|
||||
|
||||
pass_backend() {
|
||||
init() {
|
||||
PREFIX="${PASSWORD_STORE_DIR:-$HOME/.password-store}"
|
||||
if ! [ -d "$PREFIX" ] ; then
|
||||
die "Can not open password store dir »$PREFIX«"
|
||||
fi
|
||||
}
|
||||
query_entries() {
|
||||
local url="$1"
|
||||
|
||||
if ((match_line)) ; then
|
||||
# add entries with matching URL-tag
|
||||
while read -r -d "" passfile ; do
|
||||
if $GPG "${GPG_OPTS}" -d "$passfile" \
|
||||
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
|
||||
then
|
||||
passfile="${passfile#$PREFIX}"
|
||||
passfile="${passfile#/}"
|
||||
files+=( "${passfile%.gpg}" )
|
||||
fi
|
||||
done < <(find -L "$PREFIX" -iname '*.gpg' -print0)
|
||||
fi
|
||||
if ((match_filename)) ; then
|
||||
# add entries with matching filepath
|
||||
while read -r passfile ; do
|
||||
passfile="${passfile#$PREFIX}"
|
||||
passfile="${passfile#/}"
|
||||
files+=( "${passfile%.gpg}" )
|
||||
done < <(find -L "$PREFIX" -iname '*.gpg' | grep "$url")
|
||||
fi
|
||||
}
|
||||
open_entry() {
|
||||
local path="$PREFIX/${1}.gpg"
|
||||
password=""
|
||||
local firstline=1
|
||||
while read -r line ; do
|
||||
if ((firstline)) ; then
|
||||
password="$line"
|
||||
firstline=0
|
||||
else
|
||||
if [[ $line =~ $user_pattern ]] ; then
|
||||
# remove the matching prefix "user: " from the beginning of the line
|
||||
username=${line#${BASH_REMATCH[0]}}
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < <($GPG "${GPG_OPTS}" -d "$path" )
|
||||
}
|
||||
}
|
||||
# =======================================================
|
||||
|
||||
# =======================================================
|
||||
# backend: secret
|
||||
secret_backend() {
|
||||
init() {
|
||||
return
|
||||
}
|
||||
query_entries() {
|
||||
local domain="$1"
|
||||
while read -r line ; do
|
||||
if [[ "$line" =~ "attribute.username = " ]] ; then
|
||||
files+=("$domain ${line#${BASH_REMATCH[0]}}")
|
||||
fi
|
||||
done < <( secret-tool search --unlock --all domain "$domain" 2>&1 )
|
||||
}
|
||||
open_entry() {
|
||||
local domain="${1%% *}"
|
||||
username="${1#* }"
|
||||
password=$(secret-tool lookup domain "$domain" username "$username")
|
||||
}
|
||||
}
|
||||
# =======================================================
|
||||
|
||||
# load some sane default backend
|
||||
reset_backend
|
||||
pass_backend
|
||||
# load configuration
|
||||
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
||||
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
||||
if [ -f "$PWFILL_CONFIG" ] ; then
|
||||
source "$PWFILL_CONFIG"
|
||||
fi
|
||||
init
|
||||
|
||||
simplify_url "$QUTE_URL"
|
||||
query_entries "${simple_url}"
|
||||
no_entries_found
|
||||
# remove duplicates
|
||||
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
|
||||
choose_entry
|
||||
if [ -z "$file" ] ; then
|
||||
# choose_entry didn't want any of these entries
|
||||
exit 0
|
||||
fi
|
||||
open_entry "$file"
|
||||
#username="$(date)"
|
||||
#password="XYZ"
|
||||
#msg "$username, ${#password}"
|
||||
|
||||
[ -n "$username" ] || die "Username not set in entry $file"
|
||||
[ -n "$password" ] || die "Password not set in entry $file"
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
function hasPasswordField(form) {
|
||||
var inputs = form.getElementsByTagName("input");
|
||||
for (var j = 0; j < inputs.length; j++) {
|
||||
var input = inputs[j];
|
||||
if (input.type == "password") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
function loadData2Form (form) {
|
||||
var inputs = form.getElementsByTagName("input");
|
||||
for (var j = 0; j < inputs.length; j++) {
|
||||
var input = inputs[j];
|
||||
if (input.type == "text" || input.type == "email") {
|
||||
input.value = "$(javascript_escape "${username}")";
|
||||
}
|
||||
if (input.type == "password") {
|
||||
input.value = "$(javascript_escape "${password}")";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var forms = document.getElementsByTagName("form");
|
||||
for (i = 0; i < forms.length; i++) {
|
||||
if (hasPasswordField(forms[i])) {
|
||||
loadData2Form(forms[i]);
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
printjs() {
|
||||
js | sed 's,//.*$,,' | tr '\n' ' '
|
||||
}
|
||||
echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#
|
||||
# This script fetches the unprocessed HTML source for a page and opens it in vim.
|
||||
# :bind gf spawn --userscript qutebrowser_viewsource
|
||||
#
|
||||
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
|
||||
#
|
||||
|
||||
path=/tmp/qutebrowser_$(mktemp XXXXXXXX).html
|
||||
|
||||
curl "$QUTE_URL" > $path
|
||||
urxvt -e vim "$path"
|
||||
|
||||
rm "$path"
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Handle open -s && open -t with bemenu
|
||||
|
||||
#:bind o spawn --userscript /path/to/userscripts/qutedmenu open
|
||||
#:bind O spawn --userscript /path/to/userscripts/qutedmenu tab
|
||||
|
||||
# If you would like to set a custom colorscheme/font use these dirs.
|
||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/bemenucolors
|
||||
readonly confdir=${XDG_CONFIG_HOME:-$HOME/.config}
|
||||
|
||||
readonly optsfile=$confdir/dmenu/bemenucolors
|
||||
|
||||
create_menu() {
|
||||
# Check quickmarks
|
||||
while read -r url; do
|
||||
printf -- '%s\n' "$url"
|
||||
done < "$QUTE_CONFIG_DIR"/quickmarks
|
||||
|
||||
# Next bookmarks
|
||||
while read -r url _; do
|
||||
printf -- '%s\n' "$url"
|
||||
done < "$QUTE_CONFIG_DIR"/bookmarks/urls
|
||||
|
||||
# Finally history
|
||||
while read -r _ url; do
|
||||
printf -- '%s\n' "$url"
|
||||
done < "$QUTE_DATA_DIR"/history
|
||||
}
|
||||
|
||||
get_selection() {
|
||||
opts+=(-p qutebrowser)
|
||||
#create_menu | dmenu -l 10 "${opts[@]}"
|
||||
create_menu | bemenu -l 10 "${opts[@]}"
|
||||
}
|
||||
|
||||
# Main
|
||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
|
||||
if [[ -s $confdir/dmenu/font ]]; then
|
||||
read -r font < "$confdir"/dmenu/font
|
||||
fi
|
||||
|
||||
if [[ $font ]]; then
|
||||
opts+=(-fn "$font")
|
||||
fi
|
||||
|
||||
if [[ -s $optsfile ]]; then
|
||||
source "$optsfile"
|
||||
fi
|
||||
|
||||
url=$(get_selection)
|
||||
url=${url/*http/http}
|
||||
|
||||
# If no selection is made, exit (escape pressed, e.g.)
|
||||
[[ ! $url ]] && exit 0
|
||||
|
||||
case $1 in
|
||||
open) printf '%s' "open $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;;
|
||||
tab) printf '%s' "open -t $url" >> "$QUTE_FIFO" || qutebrowser "$url" ;;
|
||||
esac
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Behavior:
|
||||
# Userscript for qutebrowser which adds a task to taskwarrior.
|
||||
# If run as a command (:spawn --userscript taskadd), it creates a new task
|
||||
# with the description equal to the current page title and annotates it with
|
||||
# the current page url. Additional arguments are passed along so you can add
|
||||
# mods to the task (e.g. priority, due date, tags).
|
||||
#
|
||||
# Example:
|
||||
# :spawn --userscript taskadd due:eod pri:H
|
||||
#
|
||||
# To enable passing along extra args, I suggest using a mapping like:
|
||||
# :bind <somekey> set-cmd-text -s :spawn --userscript taskadd
|
||||
#
|
||||
# If run from hint mode, it uses the selected hint text as the description
|
||||
# and the selected hint url as the annotation.
|
||||
#
|
||||
# Ryan Roden-Corrent (rcorre), 2016
|
||||
# Any feedback is welcome!
|
||||
#
|
||||
# For more info on Taskwarrior, see http://taskwarrior.org/
|
||||
|
||||
# use either the current page title or the hint text as the task description
|
||||
[[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE
|
||||
|
||||
# try to add the task and grab the output
|
||||
msg="$(task add $title $@ 2>&1)"
|
||||
|
||||
if [[ $? == 0 ]]; then
|
||||
# annotate the new task with the url, send the output back to the browser
|
||||
task +LATEST annotate "$QUTE_URL"
|
||||
echo "message-info '$msg'" >> $QUTE_FIFO
|
||||
else
|
||||
echo "message-error '$msg'" >> $QUTE_FIFO
|
||||
fi
|
||||
@@ -1,143 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Behavior:
|
||||
# Userscript for qutebrowser which views the current web page in mpv using
|
||||
# sensible mpv-flags. While viewing the page in MPV, all <video>, <embed>,
|
||||
# and <object> tags in the original page are temporarily removed. Clicking on
|
||||
# such a removed video restores the respective video.
|
||||
#
|
||||
# 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:
|
||||
#
|
||||
# mpv = spawn --userscript /path/to/view_in_mpv
|
||||
#
|
||||
# Background:
|
||||
# Most of my machines are too slow to play youtube videos using html5, but
|
||||
# they work fine in mpv (and mpv has further advantages like video scaling,
|
||||
# etc). Of course, I don't want the video to be played (or even to be
|
||||
# downloaded) twice — in MPV and in qwebkit. So I often close the tab after
|
||||
# opening it in mpv. However, I actually want to keep the rest of the page
|
||||
# (comments and video suggestions), i.e. only the videos should disappear
|
||||
# when mpv is started. And that's precisely what the present script does.
|
||||
#
|
||||
# Thorsten Wißmann, 2015 (thorsten` on freenode)
|
||||
# Any feedback is welcome!
|
||||
|
||||
set -e
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 }
|
||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
||||
|
||||
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\\">
|
||||
Opening page with:
|
||||
<span style=\\"font-family: monospace;\\">${video_command[*]}</span>
|
||||
</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"
|
||||
|
||||
msg info "Opening $QUTE_URL with mpv"
|
||||
"${video_command[@]}" "$QUTE_URL"
|
||||
40
pytest.ini
@@ -1,40 +0,0 @@
|
||||
[pytest]
|
||||
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.
|
||||
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.
|
||||
integration: Tests which test a bigger portion of code
|
||||
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.
|
||||
flaky_once: Try to rerun this test once if it fails
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
^SetProcessDpiAwareness failed: .*
|
||||
^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"
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSL write failed with error: -9805
|
||||
^virtual void QSslSocketBackendPrivate::transmit\(\) SSLRead failed with: -9805
|
||||
^Type conversion already registered from type .*
|
||||
^QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once\.
|
||||
^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
|
||||
qt_wait_signal_raising = true
|
||||
xfail_strict = true
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,12 +24,12 @@
|
||||
import os.path
|
||||
|
||||
__author__ = "Florian Bruhin"
|
||||
__copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)"
|
||||
__copyright__ = "Copyright 2014-2015 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (0, 8, 2)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__version_info__ = (0, 1, 2)
|
||||
__version__ = '.'.join(map(str, __version_info__))
|
||||
__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-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
1407
qutebrowser/app.py
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -17,18 +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/>.
|
||||
|
||||
"""Functions related to ad blocking."""
|
||||
"""Functions related to adblocking."""
|
||||
|
||||
import io
|
||||
import os.path
|
||||
import functools
|
||||
import posixpath
|
||||
import zipfile
|
||||
import fnmatch
|
||||
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, standarddir, log, message, usertypes
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.utils import objreg, standarddir, log, message
|
||||
from qutebrowser.commands import cmdutils
|
||||
|
||||
|
||||
def guess_zip_filename(zf):
|
||||
@@ -48,7 +49,7 @@ def guess_zip_filename(zf):
|
||||
|
||||
|
||||
def get_fileobj(byte_io):
|
||||
"""Get a usable file object to read the hosts file from."""
|
||||
"""Get an usable file object to read the hosts file from."""
|
||||
byte_io.seek(0) # rewind downloaded file
|
||||
if zipfile.is_zipfile(byte_io):
|
||||
byte_io.seek(0) # rewind what zipfile.is_zipfile did
|
||||
@@ -60,22 +61,6 @@ def get_fileobj(byte_io):
|
||||
return io.TextIOWrapper(byte_io, encoding='utf-8')
|
||||
|
||||
|
||||
def is_whitelisted_host(host):
|
||||
"""Check if the given host is on the adblock whitelist.
|
||||
|
||||
Args:
|
||||
host: The host of the request as string.
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
class FakeDownload:
|
||||
|
||||
"""A download stub to use on_download_finished with local files."""
|
||||
@@ -91,12 +76,10 @@ class HostBlocker:
|
||||
"""Manage blocked hosts based from /etc/hosts-like files.
|
||||
|
||||
Attributes:
|
||||
_blocked_hosts: A set of blocked hosts.
|
||||
_config_blocked_hosts: A set of blocked hosts from ~/.config.
|
||||
blocked_hosts: A set of blocked hosts.
|
||||
_in_progress: The DownloadItems which are currently downloading.
|
||||
_done_count: How many files have been read successfully.
|
||||
_local_hosts_file: The path to the blocked-hosts file.
|
||||
_config_hosts_file: The path to a blocked-hosts in ~/.config
|
||||
_hosts_file: The path to the blocked-hosts file.
|
||||
|
||||
Class attributes:
|
||||
WHITELISTED: Hosts which never should be blocked.
|
||||
@@ -106,90 +89,32 @@ class HostBlocker:
|
||||
'local')
|
||||
|
||||
def __init__(self):
|
||||
self._blocked_hosts = set()
|
||||
self._config_blocked_hosts = set()
|
||||
self.blocked_hosts = set()
|
||||
self._in_progress = []
|
||||
self._done_count = 0
|
||||
|
||||
data_dir = standarddir.data()
|
||||
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()
|
||||
if config_dir is None:
|
||||
self._config_hosts_file = None
|
||||
else:
|
||||
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
|
||||
|
||||
data_dir = standarddir.get(QStandardPaths.DataLocation)
|
||||
self._hosts_file = os.path.join(data_dir, 'blocked-hosts')
|
||||
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.get('content', 'host-blocking-enabled'):
|
||||
return False
|
||||
host = url.host()
|
||||
return ((host in self._blocked_hosts or
|
||||
host in self._config_blocked_hosts) and
|
||||
not is_whitelisted_host(host))
|
||||
|
||||
def _read_hosts_file(self, filename, target):
|
||||
"""Read hosts from the given filename.
|
||||
|
||||
Args:
|
||||
filename: The file to read.
|
||||
target: The set to store the hosts in.
|
||||
|
||||
Return:
|
||||
True if a read was attempted, False otherwise
|
||||
"""
|
||||
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:
|
||||
log.misc.exception("Failed to read host blocklist!")
|
||||
|
||||
return True
|
||||
|
||||
def read_hosts(self):
|
||||
"""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)
|
||||
|
||||
found = self._read_hosts_file(self._local_hosts_file,
|
||||
self._blocked_hosts)
|
||||
|
||||
if not found:
|
||||
args = objreg.get('args')
|
||||
if (config.get('content', 'host-block-lists') is not None and
|
||||
args.basedir is None):
|
||||
message.info('current',
|
||||
self.blocked_hosts = set()
|
||||
if os.path.exists(self._hosts_file):
|
||||
try:
|
||||
with open(self._hosts_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
self.blocked_hosts.add(line.strip())
|
||||
except OSError:
|
||||
log.misc.exception("Failed to read host blocklist!")
|
||||
else:
|
||||
if config.get('content', 'host-block-lists') is not None:
|
||||
message.info('last-focused',
|
||||
"Run :adblock-update to get adblock lists.")
|
||||
|
||||
@cmdutils.register(instance='host-blocker')
|
||||
@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.
|
||||
"""
|
||||
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()
|
||||
def adblock_update(self, win_id: {'special': 'win_id'}):
|
||||
"""Update the adblock block lists."""
|
||||
self.blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
download_manager = objreg.get('download-manager', scope='window',
|
||||
@@ -210,8 +135,7 @@ class HostBlocker:
|
||||
else:
|
||||
fobj = io.BytesIO()
|
||||
fobj.name = 'adblock: ' + url.host()
|
||||
target = usertypes.FileObjDownloadTarget(fobj)
|
||||
download = download_manager.get(url, target=target,
|
||||
download = download_manager.get(url, fileobj=fobj,
|
||||
auto_remove=True)
|
||||
self._in_progress.append(download)
|
||||
download.finished.connect(
|
||||
@@ -232,8 +156,9 @@ class HostBlocker:
|
||||
f = get_fileobj(byte_io)
|
||||
except (OSError, UnicodeDecodeError, zipfile.BadZipFile,
|
||||
zipfile.LargeZipFile) as e:
|
||||
message.error('current', "adblock: Error while reading {}: {} - "
|
||||
"{}".format(byte_io.name, e.__class__.__name__, e))
|
||||
message.error('last-focused', "adblock: Error while reading {}: "
|
||||
"{} - {}".format(
|
||||
byte_io.name, e.__class__.__name__, e))
|
||||
return
|
||||
for line in f:
|
||||
line_count += 1
|
||||
@@ -258,31 +183,30 @@ class HostBlocker:
|
||||
error_count += 1
|
||||
continue
|
||||
if host not in self.WHITELISTED:
|
||||
self._blocked_hosts.add(host)
|
||||
self.blocked_hosts.add(host)
|
||||
log.misc.debug("{}: read {} lines".format(byte_io.name, line_count))
|
||||
if error_count > 0:
|
||||
message.error('current', "adblock: {} read errors for {}".format(
|
||||
error_count, byte_io.name))
|
||||
message.error('last-focused', "adblock: {} read errors for "
|
||||
"{}".format(error_count, byte_io.name))
|
||||
|
||||
def on_lists_downloaded(self):
|
||||
"""Install block lists after files have been downloaded."""
|
||||
with open(self._local_hosts_file, 'w', encoding='utf-8') as f:
|
||||
for host in sorted(self._blocked_hosts):
|
||||
with open(self._hosts_file, 'w', encoding='utf-8') as f:
|
||||
for host in sorted(self.blocked_hosts):
|
||||
f.write(host + '\n')
|
||||
message.info('current', "adblock: Read {} hosts from {} sources."
|
||||
.format(len(self._blocked_hosts), self._done_count))
|
||||
message.info('last-focused', "adblock: Read {} hosts from {} "
|
||||
"sources.".format(len(self.blocked_hosts),
|
||||
self._done_count))
|
||||
|
||||
@config.change_filter('content', 'host-block-lists')
|
||||
def on_config_changed(self):
|
||||
"""Update files when the config changed."""
|
||||
urls = config.get('content', 'host-block-lists')
|
||||
if urls is None and self._local_hosts_file is not None:
|
||||
if urls is None:
|
||||
try:
|
||||
os.remove(self._local_hosts_file)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except OSError as e:
|
||||
log.misc.exception("Failed to delete hosts file: {}".format(e))
|
||||
os.remove(self._hosts_file)
|
||||
except OSError:
|
||||
log.misc.exception("Failed to delete hosts file.")
|
||||
|
||||
def on_download_finished(self, download):
|
||||
"""Check if all downloads are finished and if so, trigger reading.
|
||||
|
||||
@@ -1,641 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2016 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/>.
|
||||
|
||||
"""Base class for a wrapper over QWebView/QWebEngineView."""
|
||||
|
||||
import itertools
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QPoint
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QLayout
|
||||
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, objreg, usertypes, message, log, qtutils
|
||||
|
||||
|
||||
tab_id_gen = itertools.count(0)
|
||||
|
||||
|
||||
def create(win_id, parent=None):
|
||||
"""Get a QtWebKit/QtWebEngine tab object.
|
||||
|
||||
Args:
|
||||
win_id: The window ID where the tab will be shown.
|
||||
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 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, parent=parent)
|
||||
|
||||
|
||||
class WebTabError(Exception):
|
||||
|
||||
"""Base class for various errors."""
|
||||
|
||||
|
||||
class WrapperLayout(QLayout):
|
||||
|
||||
"""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)
|
||||
|
||||
|
||||
class TabData:
|
||||
|
||||
"""A simple namespace with a fixed set of attributes.
|
||||
|
||||
Attributes:
|
||||
keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
|
||||
load.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
viewing_source: Set if we're currently showing a source view.
|
||||
"""
|
||||
|
||||
__slots__ = ['keep_icon', 'viewing_source', 'inspector']
|
||||
|
||||
def __init__(self):
|
||||
self.keep_icon = False
|
||||
self.viewing_source = False
|
||||
self.inspector = None
|
||||
|
||||
|
||||
class AbstractPrinting:
|
||||
|
||||
"""Attribute of AbstractTab for printing the page."""
|
||||
|
||||
def __init__(self):
|
||||
self._widget = None
|
||||
|
||||
def check_pdf_support(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def check_printer_support(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_pdf(self, filename):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_printer(self, printer):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractSearch(QObject):
|
||||
|
||||
"""Attribute of AbstractTab for doing searches.
|
||||
|
||||
Attributes:
|
||||
text: The last thing this view was searched for.
|
||||
_flags: The flags of the last search (needs to be set by subclasses).
|
||||
_widget: The underlying WebView widget.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._widget = None
|
||||
self.text = None
|
||||
|
||||
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. (True/False/'smart')
|
||||
reverse: Reverse search direction.
|
||||
result_cb: Called with a bool indicating whether a match was found.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def clear(self):
|
||||
"""Clear the current search."""
|
||||
raise NotImplementedError
|
||||
|
||||
def prev_result(self, *, result_cb=None):
|
||||
"""Go to the previous result of the current search.
|
||||
|
||||
Args:
|
||||
result_cb: Called with a bool indicating whether a match was found.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def next_result(self, *, result_cb=None):
|
||||
"""Go to the next result of the current search.
|
||||
|
||||
Args:
|
||||
result_cb: Called with a bool indicating whether a match was found.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractZoom(QObject):
|
||||
|
||||
"""Attribute of AbstractTab for controlling zoom.
|
||||
|
||||
Attributes:
|
||||
_neighborlist: A NeighborList with the zoom levels.
|
||||
_default_zoom_changed: Whether the zoom was changed from the default.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._widget = None
|
||||
self._win_id = win_id
|
||||
self._default_zoom_changed = False
|
||||
self._init_neighborlist()
|
||||
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/The-Compiler/qutebrowser/issues/390
|
||||
# self.destroyed.connect(functools.partial(
|
||||
# cfg.changed.disconnect, self.init_neighborlist))
|
||||
|
||||
@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.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.get('ui', 'zoom-levels')
|
||||
self._neighborlist = usertypes.NeighborList(
|
||||
levels, mode=usertypes.NeighborList.Modes.edge)
|
||||
self._neighborlist.fuzzyval = config.get('ui', 'default-zoom')
|
||||
|
||||
def offset(self, offset):
|
||||
"""Increase/Decrease the zoom level by the given offset.
|
||||
|
||||
Args:
|
||||
offset: The offset in the zoom level list.
|
||||
|
||||
Return:
|
||||
The new zoom percentage.
|
||||
"""
|
||||
level = self._neighborlist.getitem(offset)
|
||||
self.set_factor(float(level) / 100, fuzzyval=False)
|
||||
return level
|
||||
|
||||
def set_factor(self, factor, *, fuzzyval=True):
|
||||
"""Zoom to a given zoom factor.
|
||||
|
||||
Args:
|
||||
factor: The zoom factor as float.
|
||||
fuzzyval: Whether to set the NeighborLists fuzzyval.
|
||||
"""
|
||||
if fuzzyval:
|
||||
self._neighborlist.fuzzyval = int(factor * 100)
|
||||
if factor < 0:
|
||||
raise ValueError("Can't zoom to factor {}!".format(factor))
|
||||
self._default_zoom_changed = True
|
||||
self._set_factor_internal(factor)
|
||||
|
||||
def factor(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_default(self):
|
||||
default_zoom = config.get('ui', 'default-zoom')
|
||||
self._set_factor_internal(float(default_zoom) / 100)
|
||||
|
||||
@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):
|
||||
|
||||
"""Attribute of AbstractTab for caret browsing."""
|
||||
|
||||
def __init__(self, win_id, tab, mode_manager, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._win_id = win_id
|
||||
self._widget = None
|
||||
self.selection_enabled = False
|
||||
mode_manager.entered.connect(self._on_mode_entered)
|
||||
mode_manager.left.connect(self._on_mode_left)
|
||||
|
||||
def _on_mode_entered(self, mode):
|
||||
raise NotImplementedError
|
||||
|
||||
def _on_mode_left(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_next_line(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_prev_line(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_next_char(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_prev_char(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_end_of_word(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_next_word(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_prev_word(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_start_of_line(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_end_of_line(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_start_of_next_block(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_start_of_prev_block(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_end_of_next_block(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_end_of_prev_block(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_start_of_document(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def move_to_end_of_document(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle_selection(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def drop_selection(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def has_selection(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def selection(self, html=False):
|
||||
raise NotImplementedError
|
||||
|
||||
def follow_selected(self, *, tab=False):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractScroller(QObject):
|
||||
|
||||
"""Attribute of AbstractTab to manage scroll position."""
|
||||
|
||||
perc_changed = pyqtSignal(int, int)
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
|
||||
def _init_widget(self, widget):
|
||||
self._widget = widget
|
||||
|
||||
def pos_px(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def pos_perc(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_perc(self, x=None, y=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_point(self, point):
|
||||
raise NotImplementedError
|
||||
|
||||
def delta(self, x=0, y=0):
|
||||
raise NotImplementedError
|
||||
|
||||
def delta_page(self, x=0, y=0):
|
||||
raise NotImplementedError
|
||||
|
||||
def up(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def down(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def left(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def right(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def top(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def bottom(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def page_up(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def page_down(self, count=1):
|
||||
raise NotImplementedError
|
||||
|
||||
def at_top(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def at_bottom(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractHistory:
|
||||
|
||||
"""The history attribute of a AbstractTab."""
|
||||
|
||||
def __init__(self, tab):
|
||||
self._tab = tab
|
||||
self._history = None
|
||||
|
||||
def __len__(self):
|
||||
return len(self._history)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._history.items())
|
||||
|
||||
def current_idx(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def back(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def forward(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def can_go_back(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def can_go_forward(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def serialize(self):
|
||||
"""Serialize into an opaque format understood by self.deserialize."""
|
||||
raise NotImplementedError
|
||||
|
||||
def deserialize(self, data):
|
||||
"""Serialize from a format produced by self.serialize."""
|
||||
raise NotImplementedError
|
||||
|
||||
def load_items(self, items):
|
||||
"""Deserialize from a list of WebHistoryItems."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractTab(QWidget):
|
||||
|
||||
"""A wrapper over the given widget to hide its API and expose another one.
|
||||
|
||||
We use this to unify QWebView and QWebEngineView.
|
||||
|
||||
Attributes:
|
||||
history: The AbstractHistory for the current tab.
|
||||
registry: The ObjectRegistry associated with this tab.
|
||||
|
||||
_load_status: loading status of this page
|
||||
Accessible via load_status() method.
|
||||
_has_ssl_errors: Whether SSL errors happened.
|
||||
Needs to be set by subclasses.
|
||||
|
||||
for properties, see WebView/WebEngineView docs.
|
||||
|
||||
Signals:
|
||||
See related Qt signals.
|
||||
|
||||
new_tab_requested: Emitted when a new tab should be opened with the
|
||||
given URL.
|
||||
load_status_changed: The loading status changed
|
||||
"""
|
||||
|
||||
window_close_requested = pyqtSignal()
|
||||
link_hovered = pyqtSignal(str)
|
||||
load_started = pyqtSignal()
|
||||
load_progress = pyqtSignal(int)
|
||||
load_finished = pyqtSignal(bool)
|
||||
icon_changed = pyqtSignal(QIcon)
|
||||
title_changed = pyqtSignal(str)
|
||||
load_status_changed = pyqtSignal(str)
|
||||
new_tab_requested = pyqtSignal(QUrl)
|
||||
url_changed = pyqtSignal(QUrl)
|
||||
shutting_down = pyqtSignal()
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
self.win_id = win_id
|
||||
self.tab_id = next(tab_id_gen)
|
||||
super().__init__(parent)
|
||||
|
||||
self.registry = objreg.ObjectRegistry()
|
||||
tab_registry = objreg.get('tab-registry', scope='window',
|
||||
window=win_id)
|
||||
tab_registry[self.tab_id] = self
|
||||
objreg.register('tab', self, registry=self.registry)
|
||||
|
||||
# self.history = AbstractHistory(self)
|
||||
# self.scroller = AbstractScroller(self, parent=self)
|
||||
# self.caret = AbstractCaret(win_id=win_id, tab=self, mode_manager=...,
|
||||
# parent=self)
|
||||
# self.zoom = AbstractZoom(win_id=win_id)
|
||||
# self.search = AbstractSearch(parent=self)
|
||||
# self.printing = AbstractPrinting()
|
||||
self.data = TabData()
|
||||
self._layout = None
|
||||
self._widget = None
|
||||
self._progress = 0
|
||||
self._has_ssl_errors = False
|
||||
self._load_status = usertypes.LoadStatus.none
|
||||
self.backend = None
|
||||
|
||||
def _set_widget(self, widget):
|
||||
# pylint: disable=protected-access
|
||||
self._layout = WrapperLayout(widget, self)
|
||||
self._widget = 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
|
||||
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."""
|
||||
if not isinstance(val, usertypes.LoadStatus):
|
||||
raise TypeError("Type {} is no LoadStatus member!".format(val))
|
||||
log.webview.debug("load status for {}: {}".format(repr(self), val))
|
||||
self._load_status = val
|
||||
self.load_status_changed.emit(val.name)
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_url_changed(self, url):
|
||||
"""Update title when URL has changed and no title is available."""
|
||||
if url.isValid() and not self.title():
|
||||
self.title_changed.emit(url.toDisplayString())
|
||||
self.url_changed.emit(url)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_load_started(self):
|
||||
self._progress = 0
|
||||
self._has_ssl_errors = False
|
||||
self.data.viewing_source = False
|
||||
self._set_load_status(usertypes.LoadStatus.loading)
|
||||
self.load_started.emit()
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def _on_load_finished(self, ok):
|
||||
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:
|
||||
self._set_load_status(usertypes.LoadStatus.error)
|
||||
self.load_finished.emit(ok)
|
||||
if not self.title():
|
||||
self.title_changed.emit(self.url().toDisplayString())
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _on_load_progress(self, perc):
|
||||
self._progress = perc
|
||||
self.load_progress.emit(perc)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
def url(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def progress(self):
|
||||
return self._progress
|
||||
|
||||
def load_status(self):
|
||||
return self._load_status
|
||||
|
||||
def _openurl_prepare(self, url):
|
||||
qtutils.ensure_valid(url)
|
||||
self.title_changed.emit(url.toDisplayString())
|
||||
|
||||
def openurl(self, url):
|
||||
raise NotImplementedError
|
||||
|
||||
def reload(self, *, force=False):
|
||||
raise NotImplementedError
|
||||
|
||||
def stop(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def clear_ssl_errors(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def dump_async(self, callback, *, plain=False):
|
||||
"""Dump the current page to a file ascync.
|
||||
|
||||
The given callback will be called with the result when dumping is
|
||||
complete.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
def shutdown(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def title(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def icon(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_html(self, html, base_url):
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode),
|
||||
100)
|
||||
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-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,60 +21,33 @@
|
||||
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils, objreg
|
||||
from qutebrowser.utils import utils, standarddir, objreg
|
||||
|
||||
|
||||
class DiskCache(QNetworkDiskCache):
|
||||
|
||||
"""Disk cache which sets correct cache dir and size.
|
||||
"""Disk cache which sets correct cache dir and size."""
|
||||
|
||||
If the cache is deactivated via the command line argument --cachedir="",
|
||||
both attributes _cache_dir and _http_cache_dir are set to None.
|
||||
|
||||
Attributes:
|
||||
_activated: Whether the cache should be used.
|
||||
_cache_dir: The base directory for cache files (standarddir.cache()) or
|
||||
None.
|
||||
_http_cache_dir: the HTTP subfolder in _cache_dir or None.
|
||||
"""
|
||||
|
||||
def __init__(self, cache_dir, parent=None):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._cache_dir = cache_dir
|
||||
if cache_dir is None:
|
||||
self._http_cache_dir = None
|
||||
else:
|
||||
self._http_cache_dir = os.path.join(cache_dir, 'http')
|
||||
self._maybe_activate()
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
cache_dir = standarddir.get(QStandardPaths.CacheLocation)
|
||||
self.setCacheDirectory(os.path.join(cache_dir, 'http'))
|
||||
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
|
||||
objreg.get('config').changed.connect(self.cache_size_changed)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, size=self.cacheSize(),
|
||||
maxsize=self.maximumCacheSize(),
|
||||
path=self.cacheDirectory())
|
||||
|
||||
def _maybe_activate(self):
|
||||
"""Activate/deactivate the cache based on the config."""
|
||||
if (config.get('general', 'private-browsing') or
|
||||
self._cache_dir is None):
|
||||
self._activated = False
|
||||
else:
|
||||
self._activated = True
|
||||
self.setCacheDirectory(self._http_cache_dir)
|
||||
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def on_config_changed(self, section, option):
|
||||
"""Update cache size/activated if the config was changed."""
|
||||
if (section, option) == ('storage', 'cache-size'):
|
||||
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
|
||||
elif (section, option) == ('general', # pragma: no branch
|
||||
'private-browsing'):
|
||||
self._maybe_activate()
|
||||
@config.change_filter('storage', 'cache-size')
|
||||
def cache_size_changed(self):
|
||||
"""Update cache size if the config was changed."""
|
||||
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
|
||||
|
||||
def cacheSize(self):
|
||||
"""Return the current size taken up by the cache.
|
||||
@@ -82,13 +55,13 @@ class DiskCache(QNetworkDiskCache):
|
||||
Return:
|
||||
An int.
|
||||
"""
|
||||
if self._activated:
|
||||
return super().cacheSize()
|
||||
else:
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return 0
|
||||
else:
|
||||
return super().cacheSize()
|
||||
|
||||
def fileMetaData(self, filename):
|
||||
"""Return the QNetworkCacheMetaData for the cache file filename.
|
||||
"""Returns the QNetworkCacheMetaData for the cache file filename.
|
||||
|
||||
Args:
|
||||
filename: The file name as a string.
|
||||
@@ -96,10 +69,10 @@ class DiskCache(QNetworkDiskCache):
|
||||
Return:
|
||||
A QNetworkCacheMetaData object.
|
||||
"""
|
||||
if self._activated:
|
||||
return super().fileMetaData(filename)
|
||||
else:
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return QNetworkCacheMetaData()
|
||||
else:
|
||||
return super().fileMetaData(filename)
|
||||
|
||||
def data(self, url):
|
||||
"""Return the data associated with url.
|
||||
@@ -110,10 +83,10 @@ class DiskCache(QNetworkDiskCache):
|
||||
return:
|
||||
A QIODevice or None.
|
||||
"""
|
||||
if self._activated:
|
||||
return super().data(url)
|
||||
else:
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return None
|
||||
else:
|
||||
return super().data(url)
|
||||
|
||||
def insert(self, device):
|
||||
"""Insert the data in device and the prepared meta data into the cache.
|
||||
@@ -121,10 +94,10 @@ class DiskCache(QNetworkDiskCache):
|
||||
Args:
|
||||
device: A QIODevice.
|
||||
"""
|
||||
if self._activated:
|
||||
super().insert(device)
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return
|
||||
else:
|
||||
return None
|
||||
super().insert(device)
|
||||
|
||||
def metaData(self, url):
|
||||
"""Return the meta data for the url url.
|
||||
@@ -135,10 +108,10 @@ class DiskCache(QNetworkDiskCache):
|
||||
Return:
|
||||
A QNetworkCacheMetaData object.
|
||||
"""
|
||||
if self._activated:
|
||||
return super().metaData(url)
|
||||
else:
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return QNetworkCacheMetaData()
|
||||
else:
|
||||
return super().metaData(url)
|
||||
|
||||
def prepare(self, meta_data):
|
||||
"""Return the device that should be populated with the data.
|
||||
@@ -149,10 +122,10 @@ class DiskCache(QNetworkDiskCache):
|
||||
Return:
|
||||
A QIODevice or None.
|
||||
"""
|
||||
if self._activated:
|
||||
return super().prepare(meta_data)
|
||||
else:
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return None
|
||||
else:
|
||||
return super().prepare(meta_data)
|
||||
|
||||
def remove(self, url):
|
||||
"""Remove the cache entry for url.
|
||||
@@ -160,25 +133,25 @@ class DiskCache(QNetworkDiskCache):
|
||||
Return:
|
||||
True on success, False otherwise.
|
||||
"""
|
||||
if self._activated:
|
||||
return super().remove(url)
|
||||
else:
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return False
|
||||
else:
|
||||
return super().remove(url)
|
||||
|
||||
def updateMetaData(self, meta_data):
|
||||
"""Update the cache meta date for the meta_data's url to meta_data.
|
||||
"""Updates the cache meta date for the meta_data's url to meta_data.
|
||||
|
||||
Args:
|
||||
meta_data: A QNetworkCacheMetaData object.
|
||||
"""
|
||||
if self._activated:
|
||||
super().updateMetaData(meta_data)
|
||||
else:
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return
|
||||
else:
|
||||
super().updateMetaData(meta_data)
|
||||
|
||||
def clear(self):
|
||||
"""Remove all items from the cache."""
|
||||
if self._activated:
|
||||
super().clear()
|
||||
else:
|
||||
"""Removes all items from the cache."""
|
||||
if objreg.get('general', 'private-browsing'):
|
||||
return
|
||||
else:
|
||||
super().clear()
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -20,22 +20,16 @@
|
||||
"""Handling of HTTP cookies."""
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
|
||||
from PyQt5.QtCore import pyqtSignal, QDateTime
|
||||
from PyQt5.QtCore import QStandardPaths, QDateTime
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.config.parsers import line as lineparser
|
||||
from qutebrowser.utils import utils, standarddir, objreg
|
||||
from qutebrowser.misc import lineparser
|
||||
|
||||
|
||||
class RAMCookieJar(QNetworkCookieJar):
|
||||
|
||||
"""An in-RAM cookie jar.
|
||||
|
||||
Signals:
|
||||
changed: Emitted when the cookie store was changed.
|
||||
"""
|
||||
|
||||
changed = pyqtSignal()
|
||||
"""An in-RAM cookie jar."""
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, count=len(self.allCookies()))
|
||||
@@ -53,7 +47,6 @@ class RAMCookieJar(QNetworkCookieJar):
|
||||
if config.get('content', 'cookies-accept') == 'never':
|
||||
return False
|
||||
else:
|
||||
self.changed.emit()
|
||||
return super().setCookiesFromUrl(cookies, url)
|
||||
|
||||
|
||||
@@ -62,34 +55,24 @@ class CookieJar(RAMCookieJar):
|
||||
"""A cookie jar saving cookies to disk.
|
||||
|
||||
Attributes:
|
||||
_lineparser: The LineParser managing the cookies file.
|
||||
_linecp: The LineConfigParser managing the cookies file.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None, *, line_parser=None):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
if line_parser:
|
||||
self._lineparser = line_parser
|
||||
else:
|
||||
self._lineparser = lineparser.LineParser(
|
||||
standarddir.data(), 'cookies', binary=True, parent=self)
|
||||
self.parse_cookies()
|
||||
objreg.get('config').changed.connect(self.cookies_store_changed)
|
||||
objreg.get('save-manager').add_saveable(
|
||||
'cookies', self.save, self.changed,
|
||||
config_opt=('content', 'cookies-store'))
|
||||
|
||||
def parse_cookies(self):
|
||||
"""Parse cookies from lineparser and store them."""
|
||||
datadir = standarddir.get(QStandardPaths.DataLocation)
|
||||
self._linecp = lineparser.LineConfigParser(datadir, 'cookies',
|
||||
binary=True)
|
||||
cookies = []
|
||||
for line in self._lineparser:
|
||||
for line in self._linecp:
|
||||
cookies += QNetworkCookie.parseCookies(line)
|
||||
self.setAllCookies(cookies)
|
||||
objreg.get('config').changed.connect(self.cookies_store_changed)
|
||||
|
||||
def purge_old_cookies(self):
|
||||
"""Purge expired cookies from the cookie jar."""
|
||||
# Based on:
|
||||
# http://doc.qt.io/qt-5/qtwebkitexamples-webkitwidgets-browser-cookiejar-cpp.html
|
||||
# http://qt-project.org/doc/qt-5/qtwebkitexamples-webkitwidgets-browser-cookiejar-cpp.html
|
||||
now = QDateTime.currentDateTime()
|
||||
cookies = [c for c in self.allCookies()
|
||||
if c.isSessionCookie() or c.expirationDate() >= now]
|
||||
@@ -97,18 +80,19 @@ class CookieJar(RAMCookieJar):
|
||||
|
||||
def save(self):
|
||||
"""Save cookies to disk."""
|
||||
if not config.get('content', 'cookies-store'):
|
||||
return
|
||||
self.purge_old_cookies()
|
||||
lines = []
|
||||
for cookie in self.allCookies():
|
||||
if not cookie.isSessionCookie():
|
||||
lines.append(cookie.toRawForm())
|
||||
self._lineparser.data = lines
|
||||
self._lineparser.save()
|
||||
self._linecp.data = lines
|
||||
self._linecp.save()
|
||||
|
||||
@config.change_filter('content', 'cookies-store')
|
||||
def cookies_store_changed(self):
|
||||
"""Delete stored cookies if cookies-store changed."""
|
||||
if not config.get('content', 'cookies-store'):
|
||||
self._lineparser.data = []
|
||||
self._lineparser.save()
|
||||
self.changed.emit()
|
||||
self._linecp.data = []
|
||||
self._linecp.save()
|
||||
908
qutebrowser/browser/downloads.py
Normal file
@@ -0,0 +1,908 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 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 os
|
||||
import sys
|
||||
import os.path
|
||||
import shutil
|
||||
import functools
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QTimer,
|
||||
QStandardPaths, Qt, QVariant, QAbstractListModel,
|
||||
QModelIndex, QUrl)
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
# We need this import so PyQt can use it inside pyqtSlot
|
||||
from PyQt5.QtWebKitWidgets import QWebPage # pylint: disable=unused-import
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import (message, usertypes, log, utils, urlutils,
|
||||
objreg, standarddir, qtutils)
|
||||
from qutebrowser.browser import http
|
||||
from qutebrowser.browser.network import networkmanager
|
||||
|
||||
|
||||
ModelRole = usertypes.enum('ModelRole', ['item'], start=Qt.UserRole,
|
||||
is_int=True)
|
||||
|
||||
|
||||
RetryInfo = collections.namedtuple('RetryInfo', ['request', 'manager'])
|
||||
|
||||
|
||||
class DownloadItemStats(QObject):
|
||||
|
||||
"""Statistics (bytes done, total bytes, time, etc.) about a download.
|
||||
|
||||
Class attributes:
|
||||
SPEED_REFRESH_INTERVAL: How often to refresh the speed, in msec.
|
||||
SPEED_AVG_WINDOW: How many seconds of speed data to average to
|
||||
estimate the remaining time.
|
||||
|
||||
Attributes:
|
||||
done: How many bytes there are already downloaded.
|
||||
total: The total count of bytes. None if the total is unknown.
|
||||
speed: The current download speed, in bytes per second.
|
||||
_speed_avg: A rolling average of speeds.
|
||||
_last_done: The count of bytes which where downloaded when calculating
|
||||
the speed the last time.
|
||||
"""
|
||||
|
||||
SPEED_REFRESH_INTERVAL = 500
|
||||
SPEED_AVG_WINDOW = 30
|
||||
|
||||
updated = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.total = None
|
||||
self.done = 0
|
||||
self.speed = 0
|
||||
self._last_done = 0
|
||||
samples = int(self.SPEED_AVG_WINDOW *
|
||||
(1000 / self.SPEED_REFRESH_INTERVAL))
|
||||
self._speed_avg = collections.deque(maxlen=samples)
|
||||
self.timer = usertypes.Timer(self, 'speed_refresh')
|
||||
self.timer.timeout.connect(self._update_speed)
|
||||
self.timer.setInterval(self.SPEED_REFRESH_INTERVAL)
|
||||
self.timer.start()
|
||||
|
||||
@pyqtSlot()
|
||||
def _update_speed(self):
|
||||
"""Recalculate the current download speed."""
|
||||
delta = self.done - self._last_done
|
||||
self.speed = delta * 1000 / self.SPEED_REFRESH_INTERVAL
|
||||
self._speed_avg.append(self.speed)
|
||||
self._last_done = self.done
|
||||
self.updated.emit()
|
||||
|
||||
def finish(self):
|
||||
"""Set the download stats as finished."""
|
||||
self.timer.stop()
|
||||
self.done = self.total
|
||||
|
||||
def percentage(self):
|
||||
"""The current download percentage, or None if unknown."""
|
||||
if self.total == 0 or self.total is None:
|
||||
return None
|
||||
else:
|
||||
return 100 * self.done / self.total
|
||||
|
||||
def remaining_time(self):
|
||||
"""The remaining download time in seconds, or None."""
|
||||
if self.total is None or not self._speed_avg:
|
||||
# No average yet or we don't know the total size.
|
||||
return None
|
||||
remaining_bytes = self.total - self.done
|
||||
avg = sum(self._speed_avg) / len(self._speed_avg)
|
||||
if avg == 0:
|
||||
# Download stalled
|
||||
return None
|
||||
else:
|
||||
return remaining_bytes / avg
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def on_download_progress(self, bytes_done, bytes_total):
|
||||
"""Upload local variables when the download progress changed.
|
||||
|
||||
Args:
|
||||
bytes_done: How many bytes are downloaded.
|
||||
bytes_total: How many bytes there are to download in total.
|
||||
"""
|
||||
if bytes_total == -1:
|
||||
bytes_total = None
|
||||
self.done = bytes_done
|
||||
self.total = bytes_total
|
||||
self.updated.emit()
|
||||
|
||||
|
||||
class DownloadItem(QObject):
|
||||
|
||||
"""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:
|
||||
done: Whether the download is finished.
|
||||
stats: A DownloadItemStats object.
|
||||
successful: Whether the download has completed sucessfully.
|
||||
error_msg: The current error message, or None
|
||||
autoclose: Whether to close the associated file if the download is
|
||||
done.
|
||||
fileobj: The file object to download the file to.
|
||||
reply: The QNetworkReply associated with this download.
|
||||
_filename: The filename of the download.
|
||||
_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 QTimer which reads the QNetworkReply into self._buffer
|
||||
periodically.
|
||||
_retry_info: A RetryInfo instance.
|
||||
_win_id: The window ID the DownloadItem runs in.
|
||||
|
||||
Signals:
|
||||
data_changed: The downloads metadata changed.
|
||||
finished: The download was finished.
|
||||
cancelled: The download was cancelled.
|
||||
error: An error with the download occured.
|
||||
arg: The error message as string.
|
||||
redirected: Signal emitted when a download was redirected.
|
||||
arg 0: The new QNetworkRequest.
|
||||
arg 1: The old QNetworkReply.
|
||||
do_retry: Emitted when a request should be re-tried.
|
||||
arg: The QNetworkRequest to download.
|
||||
"""
|
||||
|
||||
MAX_REDIRECTS = 10
|
||||
data_changed = pyqtSignal()
|
||||
finished = pyqtSignal()
|
||||
error = pyqtSignal(str)
|
||||
cancelled = pyqtSignal()
|
||||
redirected = pyqtSignal(QNetworkRequest, QNetworkReply)
|
||||
do_retry = pyqtSignal('QNetworkReply')
|
||||
|
||||
def __init__(self, reply, win_id, parent=None):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply to download.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._retry_info = None
|
||||
self.done = False
|
||||
self.stats = DownloadItemStats(self)
|
||||
self.stats.updated.connect(self.data_changed)
|
||||
self.autoclose = True
|
||||
self.reply = None
|
||||
self._buffer = io.BytesIO()
|
||||
self._read_timer = QTimer()
|
||||
self._read_timer.setInterval(500)
|
||||
self._read_timer.timeout.connect(self.on_read_timer_timeout)
|
||||
self._redirects = 0
|
||||
self.error_msg = None
|
||||
self.basename = '???'
|
||||
self.successful = False
|
||||
self.fileobj = None
|
||||
self._filename = None
|
||||
self.init_reply(reply)
|
||||
self._win_id = win_id
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, basename=self.basename)
|
||||
|
||||
def __str__(self):
|
||||
"""Get the download as a string.
|
||||
|
||||
Example: foo.pdf [699.2kB/s|0.34|16%|4.253/25.124]
|
||||
"""
|
||||
speed = utils.format_size(self.stats.speed, suffix='B/s')
|
||||
down = utils.format_size(self.stats.done, suffix='B')
|
||||
perc = self.stats.percentage()
|
||||
remaining = self.stats.remaining_time()
|
||||
if self.error_msg is None:
|
||||
errmsg = ""
|
||||
else:
|
||||
errmsg = " - {}".format(self.error_msg)
|
||||
if all(e is None for e in (perc, remaining, self.stats.total)):
|
||||
return ('{name} [{speed:>10}|{down}]{errmsg}'.format(
|
||||
name=self.basename, speed=speed, down=down, errmsg=errmsg))
|
||||
if perc is None:
|
||||
perc = '??'
|
||||
else:
|
||||
perc = round(perc)
|
||||
if remaining is None:
|
||||
remaining = '?'
|
||||
else:
|
||||
remaining = utils.format_seconds(remaining)
|
||||
total = utils.format_size(self.stats.total, suffix='B')
|
||||
if self.done:
|
||||
return ('{name} [{perc:>2}%|{total}]{errmsg}'.format(
|
||||
name=self.basename, perc=perc, total=total,
|
||||
errmsg=errmsg))
|
||||
else:
|
||||
return ('{name} [{speed:>10}|{remaining:>5}|{perc:>2}%|'
|
||||
'{down}/{total}]{errmsg}'.format(
|
||||
name=self.basename, speed=speed, remaining=remaining,
|
||||
perc=perc, down=down, total=total, errmsg=errmsg))
|
||||
|
||||
def _create_fileobj(self):
|
||||
"""Creates 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 _ask_overwrite_question(self):
|
||||
"""Create a Question object to be asked."""
|
||||
q = usertypes.Question(self)
|
||||
q.text = self._filename + " already exists. Overwrite? (y/n)"
|
||||
q.mode = usertypes.PromptMode.yesno
|
||||
q.answered_yes.connect(self._create_fileobj)
|
||||
q.answered_no.connect(functools.partial(self.cancel, False))
|
||||
q.cancelled.connect(functools.partial(self.cancel, False))
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.ask(q, blocking=False)
|
||||
|
||||
def _die(self, msg):
|
||||
"""Abort the download and emit an error."""
|
||||
assert not self.successful
|
||||
self._read_timer.stop()
|
||||
self.reply.downloadProgress.disconnect()
|
||||
self.reply.finished.disconnect()
|
||||
self.reply.error.disconnect()
|
||||
self.reply.readyRead.disconnect()
|
||||
self.error_msg = msg
|
||||
self.stats.finish()
|
||||
self.error.emit(msg)
|
||||
self.reply.abort()
|
||||
self.reply.deleteLater()
|
||||
self.reply = None
|
||||
self.done = True
|
||||
self.data_changed.emit()
|
||||
|
||||
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)
|
||||
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.error.emit(reply.errorString()))
|
||||
|
||||
def bg_color(self):
|
||||
"""Background color to be shown."""
|
||||
start = config.get('colors', 'downloads.bg.start')
|
||||
stop = config.get('colors', 'downloads.bg.stop')
|
||||
system = config.get('colors', 'downloads.bg.system')
|
||||
error = config.get('colors', 'downloads.bg.error')
|
||||
if self.error_msg is not None:
|
||||
assert not self.successful
|
||||
return error
|
||||
elif self.stats.percentage() is None:
|
||||
return start
|
||||
else:
|
||||
return utils.interpolate_color(
|
||||
start, stop, self.stats.percentage(), system)
|
||||
|
||||
def cancel(self, remove_data=True):
|
||||
"""Cancel the download.
|
||||
|
||||
Args:
|
||||
remove_data: Whether to remove the downloaded data.
|
||||
"""
|
||||
log.downloads.debug("cancelled")
|
||||
self._read_timer.stop()
|
||||
self.cancelled.emit()
|
||||
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()
|
||||
try:
|
||||
if (self._filename is not None and os.path.exists(self._filename)
|
||||
and remove_data):
|
||||
os.remove(self._filename)
|
||||
except OSError:
|
||||
log.downloads.exception("Failed to remove partial file")
|
||||
self.done = True
|
||||
self.finished.emit()
|
||||
self.data_changed.emit()
|
||||
|
||||
def retry(self):
|
||||
"""Retry a failed download."""
|
||||
self.cancel()
|
||||
new_reply = self._retry_info.manager.get(self._retry_info.request)
|
||||
self.do_retry.emit(new_reply)
|
||||
|
||||
def open_file(self):
|
||||
"""Open the downloaded file."""
|
||||
assert self.successful
|
||||
url = QUrl.fromLocalFile(self._filename)
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
def set_filename(self, filename):
|
||||
"""Set the filename to save the download to.
|
||||
|
||||
Args:
|
||||
filename: The full filename to save the download to.
|
||||
None: special value to stop the download.
|
||||
"""
|
||||
if self.fileobj is not None:
|
||||
raise ValueError("fileobj was already set! filename: {}, "
|
||||
"existing: {}, fileobj {}".format(
|
||||
filename, self._filename, self.fileobj))
|
||||
filename = os.path.expanduser(filename)
|
||||
# Remove chars which can't be encoded in the filename encoding.
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/427
|
||||
encoding = sys.getfilesystemencoding()
|
||||
filename = utils.force_encoding(filename, encoding)
|
||||
if os.path.isabs(filename) and os.path.isdir(filename):
|
||||
# We got an absolute directory from the user, so we save it under
|
||||
# the default filename in that directory.
|
||||
self._filename = os.path.join(filename, self.basename)
|
||||
elif os.path.isabs(filename):
|
||||
# We got an absolute filename from the user, so we save it under
|
||||
# that filename.
|
||||
self._filename = filename
|
||||
self.basename = os.path.basename(self._filename)
|
||||
else:
|
||||
# We only got a filename (without directory) from the user, so we
|
||||
# save it under that filename in the default directory.
|
||||
download_dir = config.get('storage', 'download-directory')
|
||||
if download_dir is None:
|
||||
download_dir = standarddir.get(
|
||||
QStandardPaths.DownloadLocation)
|
||||
self._filename = os.path.join(download_dir, filename)
|
||||
self.basename = filename
|
||||
log.downloads.debug("Setting filename to {}".format(filename))
|
||||
if os.path.isfile(self._filename):
|
||||
# The file already exists, so ask the user if it should be
|
||||
# overwritten.
|
||||
self._ask_overwrite_question()
|
||||
else:
|
||||
self._create_fileobj()
|
||||
|
||||
def set_fileobj(self, fileobj):
|
||||
""""Set the file object to write the download to.
|
||||
|
||||
Args:
|
||||
fileobj: A file-like object.
|
||||
"""
|
||||
if self.fileobj is not None:
|
||||
raise ValueError("fileobj was already set! Old: {}, new: "
|
||||
"{}".format(self.fileobj, fileobj))
|
||||
self.fileobj = fileobj
|
||||
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 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")
|
||||
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(int)
|
||||
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)
|
||||
|
||||
def _handle_redirect(self):
|
||||
"""Handle a 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)
|
||||
request = self.reply.request()
|
||||
if new_url == request.url():
|
||||
return False
|
||||
|
||||
if self._redirects > self.MAX_REDIRECTS:
|
||||
self._die("Maximum redirection count reached!")
|
||||
return True # so on_reply_finished aborts
|
||||
|
||||
log.downloads.debug("{}: Handling redirect".format(self))
|
||||
self._redirects += 1
|
||||
request.setUrl(new_url)
|
||||
reply = self.reply
|
||||
reply.finished.disconnect(self.on_reply_finished)
|
||||
self._read_timer.stop()
|
||||
self.reply = None
|
||||
if self.fileobj is not None:
|
||||
self.fileobj.seek(0)
|
||||
self.redirected.emit(request, reply) # this will change self.reply!
|
||||
reply.deleteLater() # the old one
|
||||
return True
|
||||
|
||||
|
||||
class DownloadManager(QAbstractListModel):
|
||||
|
||||
"""Manager and model for currently running downloads.
|
||||
|
||||
Attributes:
|
||||
downloads: A list of active DownloadItems.
|
||||
questions: A list of Question objects to not GC them.
|
||||
_networkmanager: A NetworkManager for generic downloads.
|
||||
_win_id: The window ID the DownloadManager runs in.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self.downloads = []
|
||||
self.questions = []
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id, None, self)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, downloads=len(self.downloads))
|
||||
|
||||
def _prepare_question(self):
|
||||
"""Prepare a Question object to be asked."""
|
||||
q = usertypes.Question(self)
|
||||
q.text = "Save file to:"
|
||||
q.mode = usertypes.PromptMode.text
|
||||
q.completed.connect(q.deleteLater)
|
||||
q.destroyed.connect(functools.partial(self.questions.remove, q))
|
||||
self.questions.append(q)
|
||||
return q
|
||||
|
||||
@cmdutils.register(instance='download-manager', scope='window')
|
||||
def download(self, url, dest=None):
|
||||
"""Download a given URL, given as string.
|
||||
|
||||
Args:
|
||||
url: The URL to download
|
||||
dest: The file path to write the download to, or None to ask.
|
||||
"""
|
||||
url = urlutils.qurl_from_user_input(url)
|
||||
urlutils.raise_cmdexc_if_invalid(url)
|
||||
self.get(url, filename=dest)
|
||||
|
||||
@pyqtSlot('QUrl', 'QWebPage')
|
||||
def get(self, url, page=None, fileobj=None, filename=None,
|
||||
auto_remove=False):
|
||||
"""Start a download with a link URL.
|
||||
|
||||
Args:
|
||||
url: The URL to get, as QUrl
|
||||
page: The QWebPage to get the download from.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
If the download could start immediately, (fileobj/filename given),
|
||||
the created DownloadItem.
|
||||
|
||||
If not, None.
|
||||
"""
|
||||
if fileobj is not None and filename is not None:
|
||||
raise TypeError("Only one of fileobj/filename may be given!")
|
||||
if not url.isValid():
|
||||
urlutils.invalid_url_error(self._win_id, url, "start download")
|
||||
return
|
||||
req = QNetworkRequest(url)
|
||||
return self.get_request(req, page, fileobj, filename, auto_remove)
|
||||
|
||||
def get_request(self, request, page=None, fileobj=None, filename=None,
|
||||
auto_remove=False):
|
||||
"""Start a download with a QNetworkRequest.
|
||||
|
||||
Args:
|
||||
request: The QNetworkRequest to download.
|
||||
page: The QWebPage to use.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
If the download could start immediately, (fileobj/filename given),
|
||||
the created DownloadItem.
|
||||
|
||||
If not, None.
|
||||
"""
|
||||
if fileobj is not None and filename is not None:
|
||||
raise TypeError("Only one of fileobj/filename may be given!")
|
||||
# WORKAROUND for Qt corrupting data loaded from cache:
|
||||
# https://bugreports.qt-project.org/browse/QTBUG-42757
|
||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||
QNetworkRequest.AlwaysNetwork)
|
||||
if fileobj is not None or filename is not None:
|
||||
return self.fetch_request(request, filename, fileobj, page,
|
||||
auto_remove)
|
||||
q = self._prepare_question()
|
||||
filename = urlutils.filename_from_url(request.url())
|
||||
encoding = sys.getfilesystemencoding()
|
||||
filename = utils.force_encoding(filename, encoding)
|
||||
q.default = filename
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
q.answered.connect(
|
||||
lambda fn: self.fetch_request(request, filename=fn, page=page,
|
||||
auto_remove=auto_remove))
|
||||
message_bridge.ask(q, blocking=False)
|
||||
return None
|
||||
|
||||
def fetch_request(self, request, page=None, fileobj=None, filename=None,
|
||||
auto_remove=False):
|
||||
"""Download a QNetworkRequest to disk.
|
||||
|
||||
Args:
|
||||
request: The QNetworkRequest to download.
|
||||
page: The QWebPage to use.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
"""
|
||||
if page is None:
|
||||
nam = self._networkmanager
|
||||
else:
|
||||
nam = page.networkAccessManager()
|
||||
reply = nam.get(request)
|
||||
return self.fetch(reply, fileobj, filename, auto_remove)
|
||||
|
||||
@pyqtSlot('QNetworkReply')
|
||||
def fetch(self, reply, fileobj=None, filename=None, auto_remove=False):
|
||||
"""Download a QNetworkReply to disk.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply to download.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
"""
|
||||
if fileobj is not None and filename is not None:
|
||||
raise TypeError("Only one of fileobj/filename may be given!")
|
||||
if filename is not None:
|
||||
suggested_filename = os.path.basename(filename)
|
||||
elif fileobj is not None and getattr(fileobj, 'name', None):
|
||||
suggested_filename = fileobj.name
|
||||
else:
|
||||
_inline, suggested_filename = http.parse_content_disposition(reply)
|
||||
log.downloads.debug("fetch: {} -> {}".format(reply.url(),
|
||||
suggested_filename))
|
||||
download = DownloadItem(reply, self._win_id, self)
|
||||
download.cancelled.connect(
|
||||
functools.partial(self.remove_item, download))
|
||||
if config.get('ui', 'remove-finished-downloads') or auto_remove:
|
||||
download.finished.connect(
|
||||
functools.partial(self.remove_item, download))
|
||||
download.data_changed.connect(
|
||||
functools.partial(self.on_data_changed, download))
|
||||
download.error.connect(self.on_error)
|
||||
download.redirected.connect(
|
||||
functools.partial(self.on_redirect, download))
|
||||
download.do_retry.connect(self.fetch)
|
||||
download.basename = suggested_filename
|
||||
idx = len(self.downloads) + 1
|
||||
self.beginInsertRows(QModelIndex(), idx, idx)
|
||||
self.downloads.append(download)
|
||||
self.endInsertRows()
|
||||
|
||||
if filename is not None:
|
||||
download.set_filename(filename)
|
||||
elif fileobj is not None:
|
||||
download.set_fileobj(fileobj)
|
||||
download.autoclose = False
|
||||
else:
|
||||
q = self._prepare_question()
|
||||
encoding = sys.getfilesystemencoding()
|
||||
suggested_filename = utils.force_encoding(suggested_filename,
|
||||
encoding)
|
||||
q.default = suggested_filename
|
||||
q.answered.connect(download.set_filename)
|
||||
q.cancelled.connect(download.cancel)
|
||||
download.cancelled.connect(q.abort)
|
||||
download.error.connect(q.abort)
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.ask(q, blocking=False)
|
||||
|
||||
return download
|
||||
|
||||
@cmdutils.register(instance='download-manager', scope='window')
|
||||
def cancel_download(self, count: {'special': 'count'}=1):
|
||||
"""Cancel the first/[count]th download.
|
||||
|
||||
Args:
|
||||
count: The index of the download to cancel.
|
||||
"""
|
||||
if count == 0:
|
||||
return
|
||||
try:
|
||||
download = self.downloads[count - 1]
|
||||
except IndexError:
|
||||
raise cmdexc.CommandError("There's no download {}!".format(count))
|
||||
download.cancel()
|
||||
|
||||
@pyqtSlot(QNetworkRequest, QNetworkReply)
|
||||
def on_redirect(self, download, request, reply):
|
||||
"""Handle a HTTP redirect of a download.
|
||||
|
||||
Args:
|
||||
download: The old DownloadItem.
|
||||
request: The new QNetworkRequest.
|
||||
reply: The old QNetworkReply.
|
||||
"""
|
||||
log.downloads.debug("redirected: {} -> {}".format(
|
||||
reply.url(), request.url()))
|
||||
new_reply = reply.manager().get(request)
|
||||
download.init_reply(new_reply)
|
||||
|
||||
@pyqtSlot(DownloadItem)
|
||||
def on_data_changed(self, download):
|
||||
"""Emit data_changed signal when download data changed."""
|
||||
try:
|
||||
idx = self.downloads.index(download)
|
||||
except ValueError:
|
||||
# download has been deleted in the meantime
|
||||
return
|
||||
model_idx = self.index(idx, 0)
|
||||
qtutils.ensure_valid(model_idx)
|
||||
self.dataChanged.emit(model_idx, model_idx)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_error(self, msg):
|
||||
"""Display error message on download errors."""
|
||||
message.error(self._win_id, "Download error: {}".format(msg))
|
||||
|
||||
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.
|
||||
"""
|
||||
for download in self.downloads:
|
||||
if download.reply is not None and download.reply.manager() is nam:
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_clear(self):
|
||||
"""Check if there are finished downloads to clear."""
|
||||
if self.downloads:
|
||||
return any(download.done for download in self.downloads)
|
||||
else:
|
||||
return False
|
||||
|
||||
def clear(self):
|
||||
"""Remove all finished downloads."""
|
||||
self.remove_items(d for d in self.downloads if d.done)
|
||||
|
||||
def last_index(self):
|
||||
"""Get the last index in the model.
|
||||
|
||||
Return:
|
||||
A (possibly invalid) QModelIndex.
|
||||
"""
|
||||
idx = self.index(self.rowCount() - 1)
|
||||
return idx
|
||||
|
||||
def remove_item(self, download):
|
||||
"""Remove a given download."""
|
||||
try:
|
||||
idx = self.downloads.index(download)
|
||||
except ValueError:
|
||||
# already removed
|
||||
return
|
||||
self.beginRemoveRows(QModelIndex(), idx, idx)
|
||||
del self.downloads[idx]
|
||||
self.endRemoveRows()
|
||||
download.deleteLater()
|
||||
|
||||
def remove_items(self, downloads):
|
||||
"""Remove an iterable of downloads."""
|
||||
# On the first pass, we only generate the indices so we get the
|
||||
# first/last one for beginRemoveRows.
|
||||
indices = []
|
||||
# We need to iterate over downloads twice, which won't work if it's a
|
||||
# generator.
|
||||
downloads = list(downloads)
|
||||
for download in downloads:
|
||||
try:
|
||||
indices.append(self.downloads.index(download))
|
||||
except ValueError:
|
||||
# already removed
|
||||
pass
|
||||
if not indices:
|
||||
return
|
||||
indices.sort()
|
||||
self.beginRemoveRows(QModelIndex(), indices[0], indices[-1])
|
||||
for download in downloads:
|
||||
try:
|
||||
self.downloads.remove(download)
|
||||
except ValueError:
|
||||
# already removed
|
||||
pass
|
||||
else:
|
||||
download.deleteLater()
|
||||
self.endRemoveRows()
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
"""Simple constant header."""
|
||||
if (section == 0 and orientation == Qt.Horizontal and
|
||||
role == Qt.DisplayRole):
|
||||
return "Downloads"
|
||||
else:
|
||||
return ""
|
||||
|
||||
def data(self, index, role):
|
||||
"""Download data from DownloadManager."""
|
||||
qtutils.ensure_valid(index)
|
||||
if index.parent().isValid() or index.column() != 0:
|
||||
return QVariant()
|
||||
|
||||
item = self.downloads[index.row()]
|
||||
if role == Qt.DisplayRole:
|
||||
data = str(item)
|
||||
elif role == Qt.ForegroundRole:
|
||||
data = config.get('colors', 'downloads.fg')
|
||||
elif role == Qt.BackgroundRole:
|
||||
data = item.bg_color()
|
||||
elif role == ModelRole.item:
|
||||
data = item
|
||||
elif role == Qt.ToolTipRole:
|
||||
if item.error_msg is None:
|
||||
data = QVariant()
|
||||
else:
|
||||
return item.error_msg
|
||||
else:
|
||||
data = QVariant()
|
||||
return data
|
||||
|
||||
def flags(self, _index):
|
||||
"""Override flags so items aren't selectable.
|
||||
|
||||
The default would be Qt.ItemIsEnabled | Qt.ItemIsSelectable."""
|
||||
return Qt.ItemIsEnabled
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
"""Get count of active downloads."""
|
||||
if parent.isValid():
|
||||
# We don't have children
|
||||
return 0
|
||||
return len(self.downloads)
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -25,13 +25,15 @@ import sip
|
||||
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu
|
||||
|
||||
from qutebrowser.browser.webkit import downloads
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import style
|
||||
from qutebrowser.utils import qtutils, utils, objreg
|
||||
|
||||
|
||||
def update_geometry(obj):
|
||||
"""Weird WORKAROUND for some weird PyQt bug (probably).
|
||||
"""WORKAROUND
|
||||
|
||||
This is a horrible workaround for some weird PyQt bug (probably).
|
||||
|
||||
This actually should be a method of DownloadView, but for some reason the
|
||||
rowsInserted/rowsRemoved signals don't get disconnected from this method
|
||||
@@ -42,6 +44,7 @@ def update_geometry(obj):
|
||||
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."""
|
||||
if sip.isdeleted(obj):
|
||||
@@ -64,8 +67,8 @@ class DownloadView(QListView):
|
||||
|
||||
STYLESHEET = """
|
||||
QListView {
|
||||
background-color: {{ color['downloads.bg.bar'] }};
|
||||
font: {{ font['downloads'] }};
|
||||
{{ color['downloads.bg.bar'] }}
|
||||
{{ font['downloads'] }}
|
||||
}
|
||||
|
||||
QListView::item {
|
||||
@@ -79,7 +82,6 @@ class DownloadView(QListView):
|
||||
self.setResizeMode(QListView.Adjust)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
self.setFlow(QListView.LeftToRight)
|
||||
self.setSpacing(1)
|
||||
self._menu = None
|
||||
@@ -124,9 +126,8 @@ class DownloadView(QListView):
|
||||
Return:
|
||||
A list of either:
|
||||
- (QAction, callable) tuples.
|
||||
- (None, None) for a separator
|
||||
- (None, None) for a seperator
|
||||
"""
|
||||
model = self.model()
|
||||
actions = []
|
||||
if item is None:
|
||||
pass
|
||||
@@ -136,12 +137,12 @@ class DownloadView(QListView):
|
||||
else:
|
||||
actions.append(("Retry", item.retry))
|
||||
actions.append(("Remove",
|
||||
functools.partial(model.remove_item, item)))
|
||||
functools.partial(self.model().remove_item, item)))
|
||||
else:
|
||||
actions.append(("Cancel", item.cancel))
|
||||
if model.can_clear():
|
||||
if self.model().can_clear():
|
||||
actions.append((None, None))
|
||||
actions.append(("Remove all finished", model.download_clear))
|
||||
actions.append(("Remove all finished", self.model().clear))
|
||||
return actions
|
||||
|
||||
@pyqtSlot('QPoint')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -17,13 +17,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Parsing functions for various HTTP headers."""
|
||||
"""Other utilities which don't fit anywhere else. """
|
||||
|
||||
|
||||
import os.path
|
||||
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.browser.webkit import rfc6266
|
||||
from qutebrowser.browser import rfc6266
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
|
||||
@@ -46,19 +46,16 @@ def parse_content_disposition(reply):
|
||||
# We use the unsafe variant of the filename as we sanitize it via
|
||||
# os.path.basename later.
|
||||
try:
|
||||
value = bytes(reply.rawHeader(content_disposition_header))
|
||||
log.rfc6266.debug("Parsing Content-Disposition: {}".format(value))
|
||||
content_disposition = rfc6266.parse_headers(value)
|
||||
content_disposition = rfc6266.parse_headers(
|
||||
bytes(reply.rawHeader(content_disposition_header)))
|
||||
filename = content_disposition.filename()
|
||||
except (SyntaxError, UnicodeDecodeError, rfc6266.Error):
|
||||
log.rfc6266.exception("Error while parsing filename")
|
||||
except UnicodeDecodeError:
|
||||
log.misc.exception("Error while decoding filename")
|
||||
else:
|
||||
is_inline = content_disposition.is_inline()
|
||||
# Then try to get filename from url
|
||||
if not filename:
|
||||
path = reply.url().path()
|
||||
if path is not None:
|
||||
filename = path.rstrip('/')
|
||||
filename = reply.url().path()
|
||||
# If that fails as well, use a fallback
|
||||
if not filename:
|
||||
filename = 'qutebrowser-download'
|
||||