Compare commits

..

23 Commits

Author SHA1 Message Date
Florian Bruhin
d9d5b2df0c Release v0.1.2 2015-01-09 22:30:04 +01:00
Florian Bruhin
d6fd5a817e Regenerate docs. 2015-01-09 22:28:25 +01:00
Florian Bruhin
301186d407 Use qurl_from_user_input() in urlutils.is_url().
It seems 354018efcd broke IPv6 IPs on older Qt
versions:

======================================================================
FAIL: test_urls (qutebrowser.test.utils.test_urlutils.IsUrlTests) (url='2001:41d0:2:6c11::1')
Test things which are URLs.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/var/lib/buildbot/slaves/slave/ubuntu-utopic/build/qutebrowser/test/utils/test_urlutils.py", line 168, in test_urls
    self.assertTrue(urlutils.is_url(url), url)
AssertionError: False is not true : 2001:41d0:2:6c11::1
2015-01-09 22:26:35 +01:00
Florian Bruhin
ab121a98da Enter KeyMode.normal directly in ModeManager.
We used to enter KeyMode.none and then with a zero-time singleShot QTimer enter
the normal mode. This doesn't really make sense, and caused an exception if a
keypress was processed before the timer fired.

Fixes #433.
2015-01-09 22:26:35 +01:00
Florian Bruhin
a463038834 Make sure QUrl::fromUserInput is valid in is_url.
Fixes #460.

Without this fix, it's possible for URLs to be valid according to is_url, but
not according to QUrl::fromUserInput, e.g. "http:foo:0". This caused an
exception later because fuzzy_url runs qtutils.ensure_valid.
2015-01-09 22:26:35 +01:00
Florian Bruhin
22761b4373 Switch Qt style to Fusion on OS X on Qt 5.4.
Fixes #462.
See #459.

Upstream bugs:

https://bugreports.qt.io/browse/QTBUG-42948
https://bugreports.qt.io/browse/QTBUG-43070
2015-01-09 19:27:44 +01:00
Florian Bruhin
78f6f3a0e1 Fix error handling for local files in :adblock-update 2015-01-09 07:17:47 +01:00
Florian Bruhin
6166ea51e2 Hide 2 more Qt warnings. 2015-01-09 07:17:47 +01:00
Florian Bruhin
4d4065dfac Add !important to all hint properties. 2015-01-09 07:17:47 +01:00
Error 800
81f350ee99 Added !important to hint styles
Prevents websites from overriding hint styles
2015-01-09 07:16:44 +01:00
Florian Bruhin
4b98e6e9ce Make init_venv.py work with multiple sip .so files.
On my Debian jessie there's a sip.cpython-34m-x86_64-linux-gnu.so and a
sip.cpython-34dm-x86_64-linux-gnu.so.
2015-01-09 07:16:44 +01:00
Florian Bruhin
11f8ab1f85 Fix maxsplit-splitting with empty args (""/'').
Fixes #453.
2015-01-09 07:16:44 +01:00
Florian Bruhin
0449da048f Uncheck sending of debug log with private browsing.
Fixes #436.
2015-01-09 07:16:44 +01:00
Error 800
c78e938dea Added !important to hint styles
Prevents websites from overriding hint styles
2015-01-09 07:16:44 +01:00
Error 800
99fb8a5d87 Fixed uppercase hints option
Corrected CSS property from 'texttransform' to 'text-transform'
2015-01-09 07:16:44 +01:00
Matthias Lisin
b1e0b8f119 Commas are awesome
Fixes #438
Fixes #439
2015-01-09 07:16:43 +01:00
Florian Bruhin
8d49e001e9 Abort blocking questions when new page is loaded.
Fixes #430.
Fixes #431.
Hopefully fixes #354.
Hopefully fixes #434.

Conflicts:
	qutebrowser/browser/network/networkmanager.py
2015-01-09 07:16:32 +01:00
Florian Bruhin
965c176acf Fix validation of ShellCommand config type.
Fixes #432.
2015-01-09 07:15:44 +01:00
Florian Bruhin
896da1c27e Add SSL info to version info. 2015-01-09 07:15:44 +01:00
Florian Bruhin
8f33fcfc52 Replace unencodable chars in download filenames.
Fixes #427.
2015-01-09 07:15:44 +01:00
Florian Bruhin
91b0a33ab0 Update copyright years 2015-01-09 07:15:43 +01:00
Florian Bruhin
b059f4058f Remove hosts-file.net from blocker default lists. 2015-01-09 07:15:43 +01:00
Florian Bruhin
b63ce438b4 Fix user-stylesheet setting with an empty value. 2015-01-09 07:15:43 +01:00
593 changed files with 20822 additions and 73668 deletions

View File

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

View File

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

View File

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

View File

@@ -1 +0,0 @@
qutebrowser/3rdparty/pdfjs/*

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

114
doc/notes
View File

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

View File

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

View File

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

View File

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

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 945 B

After

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 128 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

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

View File

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

View File

@@ -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 &quot;previous&quot;-link on page</flowPara><flowPara
@@ -2710,11 +2705,11 @@
id="flowPara5703-8-2">]] - click &quot;next&quot;-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">&lt;Ctrl-A&gt; - 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">&lt;Ctrl-F&gt; - page down</flowPara><flowPara
@@ -2797,59 +2792,59 @@
id="flowPara5962">&lt;Ctrl-D&gt; - half page down</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara5711-7">&lt;Ctrl-U&gt; - 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">&lt;Alt-num&gt; - select tab</flowPara><flowPara
@@ -3150,11 +3141,11 @@
id="flowPara4138">&lt;Ctrl-S&gt; - stop loading</flowPara><flowPara
style="font-size:10px;fill:#000000"
id="flowPara4140">&lt;Ctrl-Alt-P&gt; - 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">&lt;Ctrl-E&gt; - 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">&lt;Ctrl-P&gt; - 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +0,0 @@
check-manifest

View File

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

View File

@@ -1 +0,0 @@
codecov

View File

@@ -1,3 +0,0 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
cx-Freeze==4.3.4

View File

@@ -1 +0,0 @@
cx_Freeze

View File

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

View File

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

View File

@@ -1 +0,0 @@
pip==8.1.2

View File

@@ -1,3 +0,0 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyInstaller==3.2

View File

@@ -1 +0,0 @@
PyInstaller

View File

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

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
pylint
./scripts/dev/pylint_checkers
requests
# fix qute-pylint location
#@ replace: qute-pylint==.* ./scripts/dev/pylint_checkers

View File

@@ -1,4 +0,0 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
docutils==0.12
pyroma==2.0.2

View File

@@ -1 +0,0 @@
pyroma

View File

@@ -1,6 +0,0 @@
Jinja2
Pygments
pyPEG2
PyYAML
colorama
cssutils

View File

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

View File

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

View File

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

View File

@@ -1 +0,0 @@
tox

View File

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

View File

@@ -1 +0,0 @@
vulture

View File

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

View File

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

View File

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

View File

@@ -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//&/&amp;}"'</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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-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.

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-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()

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-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'

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