Merge branch 'qutebrowser-master'

This commit is contained in:
Penaz91 2017-09-20 13:23:40 +02:00
commit f5c15b6ce8
311 changed files with 15765 additions and 16868 deletions

View File

@ -7,7 +7,7 @@ environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python36\python.exe
matrix:
- TESTENV: py36-pyqt58
- TESTENV: py36-pyqt59
- TESTENV: pylint
install:

View File

@ -43,6 +43,8 @@ putty-ignore =
tests/helpers/fixtures.py : +N806
tests/unit/browser/webkit/http/test_content_disposition.py : +D400
scripts/dev/ci/appveyor_install.py : +FI53
# FIXME:conf
tests/unit/completion/test_models.py : +F821
copyright-check = True
copyright-regexp = # Copyright [\d-]+ .*
copyright-min-file-size = 110

9
.github/CONTRIBUTING.asciidoc vendored Normal file
View File

@ -0,0 +1,9 @@
- Before you start to work on something, please leave a comment on the relevant
issue (or open one). This makes sure there is no duplicate work done.
- Either run the testsuite locally, or keep an eye on Travis CI / AppVeyor
after pushing changes.
See the full contribution docs for details:
include::../doc/contributing.asciidoc[]

5
.gitignore vendored
View File

@ -15,11 +15,8 @@ __pycache__
/qutebrowser/3rdparty
/doc/*.html
/README.html
/CHANGELOG.html
/CONTRIBUTING.html
/FAQ.html
/INSTALL.html
/qutebrowser/html/doc/
/qutebrowser/html/*.html
/.venv*
/.coverage
/htmlcov

View File

@ -30,6 +30,7 @@ disable=no-self-use,
broad-except,
bare-except,
eval-used,
exec-used,
ungrouped-imports,
suppressed-message,
too-many-return-statements,
@ -42,6 +43,7 @@ function-rgx=[a-z_][a-z0-9_]{2,50}$
const-rgx=[A-Za-z_][A-Za-z0-9_]{0,30}$
method-rgx=[a-z_][A-Za-z0-9_]{1,50}$
attr-rgx=[a-z_][a-z0-9_]{0,30}$
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{1,30}|(__.*__))$
argument-rgx=[a-z_][a-z0-9_]{0,30}$
variable-rgx=[a-z_][a-z0-9_]{0,30}$
docstring-min-length=3
@ -63,6 +65,7 @@ valid-metaclass-classmethod-first-arg=cls
[TYPECHECK]
ignored-modules=PyQt5,PyQt5.QtWebKit
ignored-classes=_CountingAttr
[IMPORTS]
# WORKAROUND

View File

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

View File

@ -1,15 +1,11 @@
sudo: required
sudo: false
dist: trusty
language: generic
language: python
group: edge
python: 3.6
matrix:
include:
- os: linux
env: TESTENV=py34-cov
- os: linux
env: DOCKER=debian-jessie
services: docker
- os: linux
env: DOCKER=archlinux
services: docker
@ -17,39 +13,32 @@ matrix:
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
services: docker
- os: linux
env: DOCKER=ubuntu-xenial
services: docker
- os: linux
language: python
python: 3.6
env: TESTENV=py36-pyqt571
- os: linux
language: python
python: 3.6
env: TESTENV=py36-pyqt58
- os: linux
language: python
python: 3.5
env: TESTENV=py35-pyqt59
- os: linux
language: python
python: 3.6
env: TESTENV=py36-pyqt59
env: TESTENV=py36-pyqt59-cov
- os: osx
env: TESTENV=py36 OSX=elcapitan
osx_image: xcode7.3
env: TESTENV=py36 OSX=sierra
osx_image: xcode8.3
language: generic
# https://github.com/qutebrowser/qutebrowser/issues/2013
# - os: osx
# env: TESTENV=py35 OSX=yosemite
# osx_image: xcode6.4
- os: linux
env: TESTENV=pylint PYTHON=python3.6
language: python
python: 3.6
- os: linux
env: TESTENV=flake8
- os: linux
env: TESTENV=docs
addons:
apt:
packages:
- asciidoc
- os: linux
env: TESTENV=vulture
- os: linux
@ -60,10 +49,9 @@ matrix:
env: TESTENV=check-manifest
- os: linux
env: TESTENV=eslint
allow_failures:
- os: osx
env: TESTENV=py36 OSX=elcapitan
osx_image: xcode7.3
language: node_js
python: null
node_js: node
fast_finish: true
cache:
@ -71,10 +59,6 @@ cache:
- $HOME/.cache/pip
- $HOME/build/qutebrowser/qutebrowser/.cache
before_install:
# We need to do this so we pick up the system-wide python properly
- 'export PATH="/usr/bin:$PATH"'
install:
- bash scripts/dev/ci/travis_install.sh
- ulimit -c unlimited

View File

View File

@ -11,12 +11,13 @@ graft misc/userscripts
recursive-include scripts *.py *.sh
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 LICENSE doc/* README.asciidoc
include misc/qutebrowser.desktop
include requirements.txt
include tox.ini
include qutebrowser.py
include misc/cheatsheet.svg
include qutebrowser/config/configdata.yml
prune www
prune scripts/dev
@ -36,7 +37,6 @@ exclude qutebrowser/javascript/.eslintrc.yaml
exclude qutebrowser/javascript/.eslintignore
exclude doc/help
exclude .*
exclude codecov.yml
exclude misc/appveyor_install.py
exclude misc/qutebrowser.spec
exclude misc/qutebrowser.nsi

View File

@ -9,7 +9,7 @@ qutebrowser
// QUTE_WEB_HIDE
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/COPYING"]
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/LICENSE"]
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
image:https://requires.io/github/qutebrowser/qutebrowser/requirements.svg?branch=master["requirements badge",link="https://requires.io/github/qutebrowser/qutebrowser/requirements/?branch=master"]
image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"]
@ -36,7 +36,7 @@ Downloads
---------
See the https://github.com/qutebrowser/qutebrowser/releases[github releases
page] for available downloads and the link:INSTALL.asciidoc[INSTALL] file for
page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for
detailed instructions on how to get qutebrowser running on various platforms.
Documentation
@ -49,10 +49,11 @@ available:
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
* link:doc/quickstart.asciidoc[Quick start guide]
* A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings.
* link:FAQ.asciidoc[Frequently asked questions]
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
* link:INSTALL.asciidoc[INSTALL]
* link:CHANGELOG.asciidoc[Change Log]
* link:doc/faq.asciidoc[Frequently asked questions]
* link:doc/help/configuring.asciidoc[Configuring qutebrowser]
* link:doc/contributing.asciidoc[Contributing to qutebrowser]
* link:doc/install.asciidoc[Installing qutebrowser]
* link:doc/changelog.asciidoc[Change Log]
* link:doc/stacktrace.asciidoc[Reporting segfaults]
* link:doc/userscripts.asciidoc[How to write userscripts]
@ -78,7 +79,7 @@ Contributions / Bugs
--------------------
You want to contribute to qutebrowser? Awesome! Please read
link:CONTRIBUTING.asciidoc[the contribution guidelines] for details and
link:doc/contributing.asciidoc[the contribution guidelines] for details and
useful hints.
If you found a bug or have a feature request, you can report it in several
@ -98,26 +99,24 @@ Requirements
The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.4 or newer (3.6 recommended) - note that
support for Python 3.4
https://github.com/qutebrowser/qutebrowser/issues/2742[will be dropped soon].
* http://qt.io/[Qt] 5.2.0 or newer (5.9 recommended - note that support for Qt
< 5.7.1 will be dropped soon) with the following modules:
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
* http://qt.io/[Qt] 5.7.1 or newer with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
- QtOpenGL
- QtWebEngine, or
- QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG).
Note that support for legacy QtWebKit (before 5.212) will be
dropped soon.
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
(5.9 recommended) for Python 3. Note that support for PyQt < 5.7 will be
dropped soon.
- QtWebKit - only the
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
supported.
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
(5.9 recommended) for Python 3.
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]
* http://pygments.org/[pygments]
* http://pyyaml.org/wiki/PyYAML[PyYAML]
* http://www.attrs.org/[attrs]
The following libraries are optional:
@ -128,8 +127,8 @@ The following libraries are optional:
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
command, when using the git repository (rather than a release).
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
and its dependencies.
See link:doc/install.asciidoc[the documentation] for directions on how to
install qutebrowser and its dependencies.
Donating
--------

View File

@ -20,16 +20,26 @@ v1.0.0 (unreleased)
Breaking changes
~~~~~~~~~~~~~~~~
- Support for legacy QtWebKit (before 5.212 which is distributed
independently from Qt) is dropped.
- Support for Python 3.4 is dropped.
- Support for Qt before 5.7 is dropped.
- New dependency on the QtSql module and Qt sqlite support.
- New dependency on ruamel.yaml; dropped PyYAML dependency.
- The QtWebEngine backend is now used by default if available.
- Dependency changes
- Support for legacy QtWebKit (before 5.212 which is distributed independently
from Qt) is dropped.
- Support for Python 3.4 is dropped.
- Support for Qt before 5.7.1 and PyQt before 5.7 is dropped.
- (TODO) New dependency on ruamel.yaml; dropped PyYAML dependency.
- New dependency on the QtSql module and Qt sqlite support.
- New dependency on the `attrs` Python module.
- The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
that PyQt5.QtOpenGL is still a dependency.
- PyQt5.QtOpenGL is now always required, even with QtWebKit.
- (TODO) The QtWebEngine backend is now used by default if available.
- New config system which ignores the old config file.
- The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
that PyQt5.QtOpenGL is still a dependency.
- Various documentation files got moved to the doc/ subfolder,
`qutebrowser.desktop` got moved to misc/.
- Migrating QtWebEngine data written by versions before 2016-11-15 (before
v0.9.0) is now not supported anymore.
- Upgrading qutebrowser with a version older than v0.4.0 still running now won't
work properly anymore.
- The `--harfbuzz` commandline argument got dropped.
Major changes
~~~~~~~~~~~~~
@ -42,12 +52,15 @@ Added
~~~~~
- New back/forward indicator in the statusbar
- New `bindings.key_mappings` setting to map keys to other keys
- New `qt_args` setting to pass additional arguments to Qt/Chromium
- New `backend` setting to select the backend to use (auto/webengine/webkit).
Together with the previous setting, this should make wrapper scripts
unnecessary.
Changed
~~~~~~~
- Upgrading qutebrowser with a version older than v0.4.0 still running now won't
work properly anymore.
- Using `:download` now uses the page's title as filename.
- Using `:back` or `:forward` with a count now skips intermediate pages.
- When there are multiple messages shown, the timeout is increased.

View File

@ -5,12 +5,6 @@ The Compiler <mail@qutebrowser.org>
:data-uri:
:toc:
IMPORTANT: I'm currently (July 2017) more busy than usual until September,
because of exams coming up. In addition to that, a new config system is coming
which will conflict with many non-trivial contributions. Because of that, please
refrain from contributing new features until then. If you're reading this note
after mid-September, please open an issue.
I `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
This document contains guidelines for contributing to qutebrowser, as well as
@ -104,10 +98,9 @@ unittests and several linters/checkers.
Currently, the following tox environments are available:
* 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.
- `py35`, `py36`: Run pytest for python 3.5/3.6 with the system-wide PyQt.
- `py36-pyqt57`, ..., `py36-pyqt59`: Run pytest with the given PyQt version (`py35-*` also works)
- `py36-pyqt59-cov`: Run with coverage support (other Python/PyQt versions work too)
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
https://pypi.python.org/pypi/pyflakes[pyflakes],
https://pypi.python.org/pypi/pep8[pep8],
@ -187,7 +180,7 @@ In the _scripts/_ subfolder there's a `run_profile.py` which profiles the code
and shows a graphical representation of what takes how much time.
It uses the built-in Python
https://docs.python.org/3.4/library/profile.html[cProfile] module and can show
https://docs.python.org/3.6/library/profile.html[cProfile] module and can show
the output in four different ways:
* Raw profile file (`--profile-tool=none`)
@ -479,7 +472,8 @@ The following arguments are supported for `@cmdutils.argument`:
- `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.
- `completion`: A completion function (see `qutebrowser.completions.models.*`)
to use when completing arguments for the given command.
- `choices`: The allowed string choices for the argument.
The name of an argument will always be the parameter name, with any trailing
@ -541,11 +535,11 @@ ____
Setting up a Windows Development Environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Install https://www.python.org/downloads/release/python-344/[Python 3.4]
* Install https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/[PyQt 5.5]
* Create a file at `C:\Windows\system32\python3.bat` with the following content:
`@C:\Python34\python %*`
This will make the Python 3.4 interpreter available as `python3`, which is used by various development scripts.
* Install https://www.python.org/downloads/release/python-362/[Python 3.6].
* Install PyQt via `pip install PyQt5`
* Create a file at `C:\Windows\system32\python3.bat` with the following content (adjust the path as necessary):
`@C:\Python36\python %*`
This will make the Python 3.6 interpreter available as `python3`, which is used by various development scripts.
* Install git from the https://git-scm.com/download/win[git-scm downloads page]
Try not to enable `core.autocrlf`, since that will cause `flake8` to complain a lot. Use an editor that can deal with plain line feeds instead.
* Clone your favourite qutebrowser repository.

View File

@ -193,7 +193,7 @@ Configuration not saved after modifying config.::
Unable to view flash content.::
If you have flash installed for on your system, it's necessary to enable plugins
to use the flash plugin. Using the command `:set content allow-plugins true`
to use the flash plugin. Using the command `:set content.plugins true`
in qutebrowser will enable plugins. Packages for flash should
be provided for your platform or it can be obtained from
http://get.adobe.com/flashplayer/[Adobe].
@ -205,26 +205,6 @@ Experiencing freezing on sites like duckduckgo and youtube.::
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
for more details.
Experiencing segfaults (crashes) on Debian systems.::
For Debian it's highly recommended to install the `gstreamer0.10-plugins-base` package.
This is a workaround for a bug in Qt, it has been fixed upstream in Qt 5.4
More details can be found
https://bugs.webkit.org/show_bug.cgi?id=119951[here].
Segfaults on Facebook, Medium, Amazon, ...::
If you are on a Debian or Ubuntu based system, you might experience some crashes
visiting these sites. This is caused by various bugs in Qt which have been
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
some packages. On Debian Jessie, it's recommended to use the experimental
repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/INSTALL.asciidoc#on-debian--ubuntu[INSTALL].
+
Since Ubuntu Trusty (using Qt 5.2.1),
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over
70 important bugs] have been fixed in QtWebKit. For Debian Jessie (using Qt 5.3.2)
it's still
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[nearly
20 important bugs].
When using QtWebEngine, qutebrowser reports "Render Process Crashed" and the console prints a traceback on Gentoo Linux or another Source-Based Distro::
As stated in https://gcc.gnu.org/gcc-6/changes.html[GCC's Website] GCC 6 has introduced some optimizations that could break non-conforming codebases, like QtWebEngine. +
As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +

View File

@ -88,7 +88,6 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|<<undo,undo>>|Re-open a closed tab.
|<<view-source,view-source>>|Show the source of the current page in a new tab.
|<<window-only,window-only>>|Close all windows except for the current one.
|<<wq,wq>>|Save open pages and quit.
|<<yank,yank>>|Yank something to the clipboard or primary selection.
|<<zoom,zoom>>|Set the zoom level for the current tab.
|<<zoom-in,zoom-in>>|Increase the zoom level for the current tab.
@ -126,7 +125,8 @@ Bind a key to a command.
==== optional arguments
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`).
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the
available modes.
* +*-f*+, +*--force*+: Rebind the key if it is already bound.
@ -281,7 +281,7 @@ Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+
Navigate to a url formed in an external editor.
The editor which should be launched can be configured via the `general -> editor` config option.
The editor which should be launched can be configured via the `editor.command` config option.
==== positional arguments
* +'url'+: URL to edit; defaults to the current page url.
@ -338,7 +338,7 @@ Show help about a command or setting.
* +'topic'+: The topic to show help for.
- :__command__ for commands.
- __section__\->__option__ for settings.
- __section__.__option__ for settings.
==== optional arguments
@ -368,7 +368,7 @@ Start hinting.
- `normal`: Open the link.
- `current`: Open the link in the current tab.
- `tab`: Open the link in a new tab (honoring the
background-tabs setting).
`tabs.background_tabs` setting).
- `tab-fg`: Open the link in a new foreground tab.
- `tab-bg`: Open the link in a new background tab.
- `window`: Open the link in a new window.
@ -402,13 +402,13 @@ Start hinting.
==== optional arguments
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with background-tabs=true), `tab-bg`,
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
`window`, `run`, `hover`, `userscript` and `spawn`.
* +*-m*+, +*--mode*+: The hinting mode to use.
- `number`: Use numeric hints.
- `letter`: Use the chars in the hints->chars settings.
- `letter`: Use the chars in the hints.chars setting.
- `word`: Use hint words based on the html elements and the
extra words.
@ -545,7 +545,7 @@ For `increment` and `decrement`, the number to change the URL by. For `up`, the
[[open]]
=== open
Syntax: +:open [*--implicit*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
Syntax: +:open [*--related*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
['url']+
Open a URL in the current/[count]th tab.
@ -556,7 +556,7 @@ If the URL contains newlines, each line gets opened in its own tab.
* +'url'+: The URL to open.
==== optional arguments
* +*-i*+, +*--implicit*+: If opening a new tab, treat the tab as implicit (like clicking on a link).
* +*-r*+, +*--related*+: If opening a new tab, position the tab as related to the current one (like clicking on a link).
* +*-b*+, +*--bg*+: Open in a new background tab.
* +*-t*+, +*--tab*+: Open in a new tab.
@ -632,8 +632,17 @@ Save the current page as a quickmark.
[[quit]]
=== quit
Syntax: +:quit [*--save*] ['session']+
Quit qutebrowser.
==== positional arguments
* +'session'+: The name of the session to save.
==== optional arguments
* +*-s*+, +*--save*+: When given, save the open windows even if auto_save.session is turned off.
[[record-macro]]
=== record-macro
Syntax: +:record-macro ['register']+
@ -752,7 +761,7 @@ Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-win
Save a session.
==== positional arguments
* +'name'+: The name of the session. If not given, the session configured in general -> session-default-name is saved.
* +'name'+: The name of the session. If not given, the session configured in session_default_name is saved.
==== optional arguments
@ -764,19 +773,18 @@ Save a session.
[[set]]
=== set
Syntax: +:set [*--temp*] [*--print*] ['section'] ['option'] ['values' ['values' ...]]+
Syntax: +:set [*--temp*] [*--print*] ['option'] ['values' ['values' ...]]+
Set an option.
If the option name ends with '?', the value of the option is shown instead. If the option name ends with '!' and it is a boolean value, toggle it.
==== positional arguments
* +'section'+: The section where the option is in.
* +'option'+: The name of the option.
* +'values'+: The value to set, or the values to cycle through.
==== optional arguments
* +*-t*+, +*--temp*+: Set value temporarily.
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
* +*-p*+, +*--print*+: Print the value after setting.
[[set-cmd-text]]
@ -843,7 +851,7 @@ Close the current/[count]th tab.
==== optional arguments
* +*-p*+, +*--prev*+: Force selecting the tab before the current tab.
* +*-n*+, +*--next*+: Force selecting the tab after the current tab.
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs->select-on-remove'.
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs.select_on_remove'.
* +*-f*+, +*--force*+: Avoid confirmation for pinned tabs.
@ -911,7 +919,7 @@ Close all tabs except for the current one.
=== tab-pin
Pin/Unpin the current/[count]th tab.
Pinning a tab shrinks it to tabs->pinned-width size. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
Pinning a tab shrinks it to `tabs.width.pinned` size. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
==== count
The tab index to pin or unpin
@ -925,13 +933,15 @@ How many tabs to switch back.
[[unbind]]
=== unbind
Syntax: +:unbind 'key' ['mode']+
Syntax: +:unbind [*--mode* 'mode'] 'key'+
Unbind a keychain.
==== positional arguments
* +'key'+: The keychain or special key (inside <...>) to unbind.
* +'mode'+: A comma-separated list of modes to unbind the key in (default: `normal`).
==== optional arguments
* +*-m*+, +*--mode*+: A mode to unbind the key in (default: `normal`). See `:help bindings.commands` for the available modes.
[[undo]]
@ -946,15 +956,6 @@ Show the source of the current page in a new tab.
=== window-only
Close all windows except for the current one.
[[wq]]
=== wq
Syntax: +:wq ['name']+
Save open pages and quit.
==== positional arguments
* +'name'+: The name of the session.
[[yank]]
=== yank
Syntax: +:yank [*--sel*] [*--keep*] ['what']+
@ -1043,6 +1044,7 @@ How many steps to zoom out.
|<<move-to-start-of-line,move-to-start-of-line>>|Move the cursor or selection to the start of the line.
|<<move-to-start-of-next-block,move-to-start-of-next-block>>|Move the cursor or selection to the start of next block.
|<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block.
|<<nop,nop>>|Do nothing.
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
@ -1290,11 +1292,15 @@ Move the cursor or selection to the start of previous block.
==== count
How many blocks to move.
[[nop]]
=== nop
Do nothing.
[[open-editor]]
=== open-editor
Open an external editor with the currently selected form field.
The editor which should be launched can be configured via the `general -> editor` config option.
The editor which should be launched can be configured via the `editor.command` config option.
[[prompt-accept]]
=== prompt-accept

View File

@ -0,0 +1,223 @@
Configuring qutebrowser
=======================
IMPORTANT: qutebrowser's configuration system was completely rewritten in
September 2017. This information is not applicable to older releases, and older
information elsewhere might be outdated. **If you had an old configuration
around and upgraded, this page will automatically open once**. To view it at a
later time, use the `:help` command.
Migrating older configurations
------------------------------
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff[] page in qutebrowser, which will show
you the changes you did in your old configuration, compared to the old defaults.
Other changes in default settings:
- `<Up>` and `<Down>` in the completion now navigate through command history
instead of selecting completion items. You can get back the old behavior by
doing:
+
----
:bind -f -m command <Up> completion-item-focus prev
:bind -f -m command <Down> completion-item-focus next
----
- The default for `completion.web_history_max_items` is now set to `-1`, showing
an unlimited number of items in the completion for `:open` as the new
sqlite-based completion is much faster. If the `:open` completion is too slow
on your machine, set an appropriate limit again.
Configuring qutebrowser via the user interface
----------------------------------------------
The easy (but less flexible) way to configure qutebrowser is using its user
interface or command line. Changes you make this way are immediately active
(with the exception of a few settings, where this is pointed out in the
documentation) and are persisted in an `autoconfig.yml` file.
The `autoconfig.yml` file is located in the "config" folder listed on the
link:qute://version[] page. On macOS, the "auto config" folder is used, which is
different from where hand-written config files are kept.
However, **do not** edit `autoconfig.yml` by hand. Instead, see the next
section.
If you want to customize many settings, you can open the link:qute://settings[]
page by running `:set` without any arguments, where all settings are listed and
customizable.
Using the link:commands.html#set[`:set`] command and command completion, you
can quickly set settings interactively, for example `:set tabs.position left`.
To get more help about a setting, use e.g. `:help tabs.position`.
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
link:commands.html#unbind[`:unbind`] commands:
- Binding the key chain "`,`, `v`" to the `:spawn mpv {url}` command:
`:bind ,v spawn mpv {url}`
- Unbinding the same key chain: `:unbind ,v`
- Changing an existing binding: `bind --force ,v message-info foo`. Without
`--force`, qutebrowser will show an error because `,v` is already bound.
Key chains starting with a comma are ideal for custom bindings, as the comma key
will never be used in a default keybinding.
See the help pages linked above (or `:help :bind`, `:help :unbind`) for more
information.
Configuring qutebrowser via config.py
-------------------------------------
For more powerful configuration possibilities, you can create a `config.py`
file. Since it's a Python file, you have much more flexibility for
configuration. Note that qutebrowser will never touch this file - this means
you'll be responsible for updating it when upgrading to a newer qutebrowser
version.
The file should be located in the "config" location listed on
link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
Linux, `~/.qutebrowser/config.py` on macOS, and
`%APPDATA%/qutebrowser/config.py` on Windows.
Two global objects are pre-defined when running `config.py`: `c` and `config`.
Changing settings
~~~~~~~~~~~~~~~~~
`c` is a shorthand object to easily set settings like this:
.config.py:
[source,python]
----
c.tabs.position = "left"
c.completion.shrink = True
----
Note that qutebrowser does some Python magic so it's able to warn you about
mistyped config settings. As an example, if you do `c.tabs.possition = "left"`,
you'll get an error when starting.
See the link:settings.html[settings help page] for all available settings. The
accepted values depend on the type of the option. Commonly used are:
- Strings: `c.tabs.position = "left"`
- Booleans: `c.completion.shrink = True`
- Integers: `c.messages.timeout = 5000`
- Dictionaries:
* `c.headers.custom = {'X-Hello': 'World', 'X-Awesome': 'yes'}` to override
any other values in the dictionary.
* `c.aliases['foo'] = ':message-info foo'` to add a single value.
- Lists:
* `c.url.start_pages = ["https://www.qutebrowser.org/"]` to override any
previous elements.
* `c.url.start_pages.append("https://www.python.org/")` to add a new value.
Any other config types (e.g. a color) are specified as a string. The only
exception is the `Regex` type, which can take either a string (with an `r`
prefix to preserve backslashes) or a Python regex object:
- `c.hints.next_regexes.append(r'\bvor\b')`
- `c.hints.prev_regexes.append(re.compile(r'\bzurück\b'))`
If you want to read a setting, you can use the `c` object to do so as well:
`c.colors.tabs.even.bg = c.colors.tabs.odd.bg`.
Using strings for setting names
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to set settings based on their name as a string, use the
`config.set` method:
.config.py:
[source,python]
----
config.set('content.javascript.enabled', False)
----
To read a setting, use the `config.get` method:
[source,python]
----
color = config.get('colors.completion.fg')
----
Binding keys
~~~~~~~~~~~~
While it's possible to change the `bindings.commands` setting to bind keys, it's
preferred to use the `config.bind` command. Doing so ensures the commands are
valid and normalizes different expressions which map to the same key.
For details on how to specify keys and the available modes, see the
link:settings.html#bindings.commands[documentation] for the `bindings.commands`
setting.
To bind a key:
.config.py:
[source,python]
----
config.bind('<Ctrl-v>', 'spawn mpv {url}')
----
To bind a key in a mode other than `'normal'`, add a `mode` argument:
[source,python]
----
config.bind('<Ctrl-y>', 'prompt-yes', mode='prompt')
----
If the key is already bound, `force=True` needs to be given to rebind it:
[source,python]
----
config.bind('<Ctrl-v>', 'message-info foo', force=True)
----
To unbind a key (either a key which has been bound before, or a default binding):
[source,python]
----
config.unbind('<Ctrl-v>', mode='normal')
----
To bind keys without modifiers, specify a key chain to bind as a string. Key
chains starting with a comma are ideal for custom bindings, as the comma key
will never be used in a default keybinding.
[source,python]
----
config.bind(',v', 'spawn mpv {url}')
----
To suppress loading of any default keybindings, you can set
`c.bindings.default = {}`.
Prevent loading `autoconfig.yml`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want all customization done via `:set`, `:bind` and `:unbind` to be
temporary, you can suppress loading `autoconfig.yml` in your `config.py` by
doing:
.config.py:
[source,python]
----
config.load_autoconfig = False
----
Handling errors
~~~~~~~~~~~~~~~
If there are errors in your `config.py`, qutebrowser will try to apply as much
of it as possible, and show an error dialog before starting.
qutebrowser tries to display errors which are easy to understand even for people
who are not used to writing Python. If you see a config error which you find
confusing or you think qutebrowser could handle better, please
https://github.com/qutebrowser/qutebrowser/issues[open an issue]!

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -3,39 +3,50 @@ Installing qutebrowser
toc::[]
NOTE: qutebrowser recently had some bigger dependency changes for v1.0.0, which
means those instructions might be out of date in some places.
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[Please help]
updating them if you notice something being broken!
On Debian / Ubuntu
------------------
qutebrowser should run on these systems:
How to install qutebrowser depends a lot on the version of Debian/Ubuntu you're
running.
* Debian jessie or newer
* Ubuntu Trusty (14.04 LTS) or newer
* Any other distribution based on these (e.g. Linux Mint 17+)
Debian Jessie / Ubuntu 14.04 LTS / Linux Mint < 18
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is
still relatively easy!
Those distributions only have Python 3.4 and a too old Qt version available,
while qutebrowser requires Python 3.5 and Qt 5.7.1 or newer.
You can use packages that are built for every release or build it yourself from git.
It should be possible to install Python 3.5 e.g. from the
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via_ipca
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
On Ubuntu 16.04 and 16.10 it's recommended to <<tox,install qutebrowser via tox>>
instead in order to be able to use the new QtWebEngine backend. Newer versions
have a QtWebEngine package in the repositories.
If you get qutebrowser running on those distributions, please
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[contribute]
to update this documentation!
Using the packages
~~~~~~~~~~~~~~~~~~
Ubuntu 16.04 LTS / Linux Mint 18
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
QtWebEngine). However, it comes with Python 3.5, so you can
<<tox,install qutebrowser via tox>>.
Debian Stretch / Ubuntu 17.04 and newer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Those versions come with QtWebEngine in the repositories. This makes it possible
to install qutebrowser via the Debian package.
Install the dependencies via apt-get:
----
# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml python3-pyqt5.qtsql libqt5sql5-sqlite
# apt install python-tox python3-{lxml,pyqt5,sip,jinja2,pygments,yaml} python3-pyqt5.qt{webengine,quick,opengl,sql} libqt5sql5-sqlite
----
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to use the
newer QtWebEngine backend.
To do so, install `python3-pyqt5.qtwebengine` and `python3-pyqt5.qtopengl`, then
start qutebrowser with `--backend webengine`.
Get the qutebrowser package from the
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
@ -47,35 +58,27 @@ Install the packages:
# dpkg -i qutebrowser_*_all.deb
----
Build it from git
~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
Some additional hints:
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
QtWebEngine version.
- If running from git, run the following to generate the documentation for the
`:help` command:
+
----
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev python3-pyqt5.qtsql libqt5sql5-sqlite
----
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to install
`python3-pyqt5.qtwebengine` and start qutebrowser with `--backend webengine` in
order to use the new backend.
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
----
# apt-get install asciidoc source-highlight
# apt-get install --no-install-recommends asciidoc source-highlight
$ python3 scripts/asciidoc2html.py
----
If video or sound don't seem to work, try installing the gstreamer plugins:
- If you prefer using QtWebKit, there's an up-to-date version available in
Debian experimental, or from http://repo.paretje.be/unstable/[this repository]
for Debian Stretch.
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
+
----
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
----
Then <<tox,install qutebrowser via tox>>.
On Fedora
---------
@ -85,7 +88,7 @@ qutebrowser is available in the official repositories for Fedora 22 and newer.
# dnf install qutebrowser
----
It's also recommended to install `qt5-qtwebengine` and start with `--backend
It's also recommended to install `python3-qt5-webengine` and start with `--backend
webengine` to use the new backend.
On Archlinux
@ -116,7 +119,7 @@ $ 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:
If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
----
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
@ -125,6 +128,8 @@ If video or sound don't seem to work, try installing the gstreamer plugins:
On Gentoo
---------
WARNING: The Gentoo packages (even the live version) are lagging behind a lot, which means those instructions probably won't work anymore. Until things are looking better, it's recommended to <<tox,install qutebrowser via tox>>.
A version of qutebrowser is available in the main repository and can be installed with:
----
@ -161,20 +166,22 @@ To update to the last Live version, remember to do
To include qutebrowser among the updates.
Make sure you have `python3_4` in your `PYTHON_TARGETS`
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
necessary.
You'll also need to install `dev-qt/qtwebengine` or a newer QtWebKit using
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
It's also recommended to install QtWebKit-NG via
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild],
or install Qt >= 5.7.1 with QtWebEngine in order to use an up-to-date backend.
If video or sound don't seem to work, try installing the gstreamer plugins:
If video or sound don't work with QtWebKit, try installing the gstreamer
plugins:
----
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
----
To be able to play videos with proprietary codecs with QtWebEngine, you will
need to turn off the `bindist` flag for `dev-qt/qtwebengine`.
See the https://wiki.gentoo.org/wiki/Qutebrowser#USE_flags[Gentoo Wiki] for
more information.
On Void Linux
-------------
@ -206,17 +213,9 @@ It's recommended to install `qt5.qtwebengine` and start with
On openSUSE
-----------
There are prebuilt RPMs available for Tumbleweed and Leap 42.1:
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
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
----
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
On OpenBSD
----------
@ -332,6 +331,9 @@ it as part of the packaging process.
Installing qutebrowser with tox
-------------------------------
Getting the repository
~~~~~~~~~~~~~~~~~~~~~~
First of all, clone the repository using http://git-scm.org/[git] and switch
into the repository folder:
@ -340,6 +342,8 @@ $ git clone https://github.com/qutebrowser/qutebrowser.git
$ cd qutebrowser
----
Installing depdendencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
@ -348,20 +352,39 @@ https://docs.python.org/3/library/venv.html[virtual environment]:
$ tox -e mkvenv-pypi
----
If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux), you'll
need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
qutebrowser.
This installs all needed Python dependencies in a `.venv` subfolder.
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few
caveats:
- Make sure your `python3` is Python 3.5 or newer, otherwise you'll get a "No
matching distribution found" error. Note that qutebrowser itself also requires
this.
- It only works on 64-bit x86 systems, with other architectures you'll get the
same error.
- If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux),
you'll need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
qutebrowser if you want SSL to work in certain downloads (e.g. for
`:adblock-update` or `:download`).
- It comes with a QtWebEngine compiled without proprietary codec support (such
as h.264).
See the next section for an alternative.
Installing dependencies (system-wide Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
local Qt install instead of installing PyQt in the virtualenv. However, unless
you have QtWebKit-NG or QtWebEngine available, qutebrowser will use the legacy
QtWebKit backend.
you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It
also typically means you'll be using an older release of QtWebEngine.
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
Python3 is in your PATH before running tox.
This installs all needed Python dependencies in a `.venv` subfolder.
Creating a wrapper script
~~~~~~~~~~~~~~~~~~~~~~~~~
You can then create a simple wrapper script to start qutebrowser somewhere in
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):

View File

@ -44,7 +44,7 @@ show it.
*-V*, *--version*::
Show version and quit.
*-s* 'SECTION' 'OPTION' 'VALUE', *--set* 'SECTION' 'OPTION' 'VALUE'::
*-s* 'OPTION' 'VALUE', *--set* 'OPTION' 'VALUE'::
Set a temporary setting for this session.
*-r* 'SESSION', *--restore* 'SESSION'::
@ -84,9 +84,6 @@ show it.
*--force-color*::
Force colored logging
*--harfbuzz* '{old,new,system,auto}'::
HarfBuzz engine version to use. Default: auto.
*--relaxed-config*::
Silently remove unknown config options.

View File

@ -23,7 +23,7 @@ SetCompressor /solid lzma
!define MUI_ICON "../icons/qutebrowser.ico"
!define MUI_UNICON "../icons/qutebrowser.ico"
!insertmacro MUI_PAGE_LICENSE "..\COPYING"
!insertmacro MUI_PAGE_LICENSE "..\LICENSE"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM

View File

@ -15,7 +15,8 @@ def get_data_files():
('../qutebrowser/img', 'img'),
('../qutebrowser/javascript', 'javascript'),
('../qutebrowser/html/doc', 'html/doc'),
('../qutebrowser/git-commit-id', '')
('../qutebrowser/git-commit-id', ''),
('../qutebrowser/config/configdata.yml', 'config'),
]
# if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):

View File

@ -4,6 +4,6 @@ certifi==2017.7.27.1
chardet==3.0.4
codecov==2.0.9
coverage==4.4.1
idna==2.5
requests==2.18.2
idna==2.6
requests==2.18.4
urllib3==1.22

View File

@ -18,6 +18,6 @@ packaging==16.8
pep8-naming==0.4.1
pycodestyle==2.3.1
pydocstyle==1.1.1 # rq.filter: < 2.0.0
pyflakes==1.5.0
pyflakes==1.6.0
pyparsing==2.2.0
six==1.10.0
six==1.11.0

View File

@ -3,6 +3,6 @@
appdirs==1.4.3
packaging==16.8
pyparsing==2.2.0
setuptools==36.2.5
setuptools==36.2.7
six==1.10.0
wheel==0.29.0

View File

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

View File

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

View File

@ -4,15 +4,15 @@
certifi==2017.7.27.1
chardet==3.0.4
github3.py==0.9.6
idna==2.5
idna==2.6
isort==4.2.15
lazy-object-proxy==1.3.1
mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
./scripts/dev/pylint_checkers
requests==2.18.2
six==1.10.0
requests==2.18.4
six==1.11.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.22
wrapt==1.10.10
wrapt==1.10.11

View File

@ -4,15 +4,15 @@ astroid==1.5.3
certifi==2017.7.27.1
chardet==3.0.4
github3.py==0.9.6
idna==2.5
idna==2.6
isort==4.2.15
lazy-object-proxy==1.3.1
mccabe==0.6.1
pylint==1.7.2
./scripts/dev/pylint_checkers
requests==2.18.2
six==1.10.0
requests==2.18.4
six==1.11.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.22
wrapt==1.10.10
wrapt==1.10.11

View File

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

View File

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

View File

@ -4,7 +4,6 @@ hg+https://bitbucket.org/ned/coveragepy
git+https://github.com/micheles/decorator.git
git+https://github.com/pallets/flask.git
git+https://github.com/miracle2k/python-glob2.git
git+https://github.com/Runscope/httpbin.git
git+https://github.com/HypothesisWorks/hypothesis-python.git
git+https://github.com/pallets/itsdangerous.git
git+https://bitbucket.org/zzzeek/mako.git
@ -41,6 +40,7 @@ git+https://github.com/pallets/jinja.git
git+https://github.com/pallets/markupsafe.git
hg+http://bitbucket.org/birkenfeld/pygments-main
hg+https://bitbucket.org/fdik/pypeg
git+https://github.com/python-attrs/attrs.git
# Fails to build:
# gcc: error: ext/_yaml.c: No such file or directory

View File

@ -1,18 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==17.2.0
beautifulsoup4==4.6.0
cheroot==5.7.0
cheroot==5.8.3
click==6.7
# colorama==0.3.9
coverage==4.4.1
decorator==4.1.2
EasyProcess==0.2.3
fields==5.0.0
Flask==0.12.2
glob2==0.5
httpbin==0.5.0
hunter==1.4.1
hypothesis==3.14.0
glob2==0.6
hunter==2.0.1
hypothesis==3.28.3
itsdangerous==0.24
# Jinja2==2.9.6
Mako==1.0.7
@ -20,20 +19,21 @@ Mako==1.0.7
parse==1.8.2
parse-type==0.3.4
py==1.4.34
pytest==3.1.3
py-cpuinfo==3.3.0
pytest==3.2.2
pytest-bdd==2.18.2
pytest-benchmark==3.1.1
pytest-catchlog==1.2.2
pytest-cov==2.5.1
pytest-faulthandler==1.3.1
pytest-instafail==0.3.0
pytest-mock==1.6.2
pytest-qt==2.1.2
pytest-mock==1.6.3
pytest-qt==2.2.0
pytest-repeat==0.4.1
pytest-rerunfailures==2.2
pytest-rerunfailures==3.1
pytest-travis-fold==1.2.0
pytest-xvfb==1.0.0
PyVirtualDisplay==0.2.1
six==1.10.0
vulture==0.21
six==1.11.0
vulture==0.26
Werkzeug==0.12.2

View File

@ -3,7 +3,6 @@ cheroot
coverage
Flask
hunter
httpbin
hypothesis
pytest
pytest-bdd

View File

@ -2,5 +2,5 @@
pluggy==0.4.0
py==1.4.34
tox==2.7.0
tox==2.8.2
virtualenv==15.1.0

View File

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

View File

@ -23,7 +23,7 @@
# If run from qutebrowser as a userscript, it runs :open on the URL
# If not, it opens a new qutebrowser window at the URL
#
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
# :bind o spawn --userscript dmenu_qutebrowser
#
# Use the hotkey to open in new tab/window, press 'o' to open URL in current tab/window

47
misc/userscripts/format_json Executable file
View File

@ -0,0 +1,47 @@
#!/bin/sh
#
# Behavior:
# Userscript for qutebrowser which will take the raw JSON text of the current
# page, format it using `jq`, will add syntax highlighting using `pygments`,
# and open the syntax highlighted pretty printed html in a new tab. If the file
# is larger than 10MB then this script will only indent the json and will forego
# syntax highlighting using pygments.
#
# In order to use this script, just start it using `spawn --userscript` from
# qutebrowser. I recommend using an alias, e.g. put this in the
# [alias]-section of qutebrowser.conf:
#
# json = spawn --userscript /path/to/json_format
#
# Note that the color style defaults to monokai, but a different pygments style
# can be passed as the first parameter to the script. A full list of the pygments
# styles can be found at: https://help.farbox.com/pygments.html
#
# Bryan Gilbert, 2017
# default style to monokai if none is provided
STYLE=${1:-monokai}
# format json using jq
FORMATTED_JSON="$(cat "$QUTE_TEXT" | jq '.')"
# if jq command failed or formatted json is empty, assume failure and terminate
if [ $? -ne 0 ] || [ -z "$FORMATTED_JSON" ]; then
echo "Invalid json, aborting..."
exit 1
fi
# calculate the filesize of the json document
FILE_SIZE=$(ls -s --block-size=1048576 "$QUTE_TEXT" | cut -d' ' -f1)
# use pygments to pretty-up the json (syntax highlight) if file is less than 10MB
if [ "$FILE_SIZE" -lt "10" ]; then
FORMATTED_JSON="$(echo "$FORMATTED_JSON" | pygmentize -l json -f html -O full,style=$STYLE)"
fi
# create a temp file and write the formatted json to that file
TEMP_FILE="$(mktemp --suffix '.html')"
echo "$FORMATTED_JSON" > $TEMP_FILE
# send the command to qutebrowser to open the new file containing the formatted json
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"

View File

@ -12,7 +12,7 @@
# - rofi (in a recent version)
# - xdg-open and xdg-mime
# - You should configure qutebrowser to download files to a single directory
# - It comes in handy if you enable remove-finished-downloads. If you want to
# - It comes in handy if you enable downloads.remove_finished. If you want to
# see the recent downloads, just press "sd".
#
# Thorsten Wißmann, 2015 (thorsten` on freenode)

View File

@ -21,7 +21,7 @@
# Opens all links to feeds defined in the head of a site
#
# Ideal for use with tabs-are-windows. Set a hotkey to launch this script, then:
# Ideal for use with tabs_are_windows. Set a hotkey to launch this script, then:
# :bind gF spawn --userscript openfeeds
#
# Use the hotkey to open the feeds in new tab/window, press 'gF' to open

View File

@ -327,6 +327,17 @@ open_entry "$file"
js() {
cat <<EOF
function isVisible(elem) {
var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null);
if (style.getPropertyValue("visibility") !== "visible" ||
style.getPropertyValue("display") === "none" ||
style.getPropertyValue("opacity") === "0") {
return false;
}
return elem.offsetWidth > 0 && elem.offsetHeight > 0;
};
function hasPasswordField(form) {
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
@ -341,7 +352,7 @@ cat <<EOF
var inputs = form.getElementsByTagName("input");
for (var j = 0; j < inputs.length; j++) {
var input = inputs[j];
if (input.type == "text" || input.type == "email") {
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
input.focus();
input.value = "$(javascript_escape "${username}")";
input.blur();

View File

@ -15,11 +15,10 @@ markers =
end2end: End to end tests which run qutebrowser as subprocess
xfail_norun: xfail the test with out running it
ci: Tests which should only run on CI.
no_ci: Tests which should not run on CI.
qtwebengine_todo: Features still missing with QtWebEngine
qtwebengine_skip: Tests not applicable with QtWebEngine
qtwebkit_skip: Tests not applicable with QtWebKit
qtwebkit_ng_xfail: Tests failing with QtWebKit-NG
qtwebkit_ng_skip: Tests skipped with QtWebKit-NG
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
js_prompt: Tests needing to display a javascript prompt
@ -44,16 +43,14 @@ qt_log_ignore =
^QWaitCondition: Destroyed while threads are still waiting
^QXcbXSettings::QXcbXSettings\(QXcbScreen\*\) Failed to get selection owner for XSETTINGS_S atom
^QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to .*
^QGeoclueMaster error creating GeoclueMasterClient\.
^Geoclue error: Process org\.freedesktop\.Geoclue\.Master exited with status 127
^QDBusConnection: name 'org.freedesktop.Geoclue.Master' had owner '' but we thought it was ':1.1'
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
^QXcbClipboard: Cannot transfer data, no data available
^load glyph failed
^Error when parsing the netrc file
^Image of format '' blocked because it is not considered safe. If you are sure it is safe to do so, you can white-list the format by setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST=
^QPainter::end: Painter ended with \d+ saved states
^QSslSocket: cannot resolve SSLv[23]_(client|server)_method
^QSslSocket: cannot resolve *
^Incompatible version of OpenSSL
^QQuickWidget::invalidateRenderControl could not make context current
^libpng warning: iCCP: known incorrect sRGB profile
xfail_strict = true

View File

@ -22,7 +22,6 @@
import os
import sys
import subprocess
import configparser
import functools
import json
import shutil
@ -44,8 +43,7 @@ import qutebrowser
import qutebrowser.resources
from qutebrowser.completion.models import miscmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import style, config, websettings, configexc
from qutebrowser.config.parsers import keyconf
from qutebrowser.config import config, websettings, configexc, configfiles
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
downloads)
from qutebrowser.browser.network import proxy
@ -54,11 +52,14 @@ from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.keyinput import macros
from qutebrowser.mainwindow import mainwindow, prompt
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
crashsignal, earlyinit, objects, sql)
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
objreg, usertypes, standarddir, error)
# We import utilcmds to run the cmdutils.register decorators.
crashsignal, earlyinit, sql, cmdhistory)
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
usertypes, standarddir, error)
# pylint: disable=unused-import
# We import those to run the cmdutils.register decorators.
from qutebrowser.mainwindow.statusbar import command
from qutebrowser.misc import utilcmds
# pylint: enable=unused-import
qApp = None
@ -72,6 +73,12 @@ def run(args):
quitter = Quitter(args)
objreg.register('quitter', quitter)
log.init.debug("Initializing directories...")
standarddir.init(args)
log.init.debug("Initializing config...")
config.early_init(args)
global qApp
qApp = Application(args)
qApp.setOrganizationName("qutebrowser")
@ -79,9 +86,6 @@ def run(args):
qApp.setApplicationVersion(qutebrowser.__version__)
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
log.init.debug("Initializing directories...")
standarddir.init(args)
if args.version:
print(version.version())
sys.exit(usertypes.Exit.ok)
@ -148,8 +152,6 @@ def init(args, crash_handler):
objreg.register('event-filter', event_filter)
log.init.debug("Connecting signals...")
config_obj = objreg.get('config')
config_obj.style_changed.connect(style.get_stylesheet.cache_clear)
qApp.focusChanged.connect(on_focus_changed)
_process_args(args)
@ -184,11 +186,10 @@ def _init_icon():
def _process_args(args):
"""Open startpage etc. and process commandline args."""
config_obj = objreg.get('config')
for sect, opt, val in args.temp_settings:
for opt, val in args.temp_settings:
try:
config_obj.set('temp', sect, opt, val)
except (configexc.Error, configparser.Error) as e:
config.instance.set_str(opt, val)
except configexc.Error as e:
message.error("set: {} - {}".format(e.__class__.__name__, e))
if not args.override_restore:
@ -215,13 +216,12 @@ def _load_session(name):
Args:
name: The name of the session to load, or None to read state file.
"""
state_config = objreg.get('state-config')
session_manager = objreg.get('session-manager')
if name is None and session_manager.exists('_autosave'):
name = '_autosave'
elif name is None:
try:
name = state_config['general']['session']
name = configfiles.state['general']['session']
except KeyError:
# No session given as argument and none in the session file ->
# start without loading a session
@ -234,7 +234,7 @@ def _load_session(name):
except sessions.SessionError as e:
message.error("Failed to load session {}: {}".format(name, e))
try:
del state_config['general']['session']
del configfiles.state['general']['session']
except KeyError:
pass
# If this was a _restart session, delete it.
@ -274,7 +274,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
if via_ipc and target_arg and target_arg != 'auto':
open_target = target_arg
else:
open_target = config.get('general', 'new-instance-open-target')
open_target = config.val.new_instance_open_target
win_id = mainwindow.get_window(via_ipc, force_target=open_target)
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
@ -289,7 +289,7 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
else:
background = open_target in ['tab-bg', 'tab-bg-silent']
tabbed_browser.tabopen(url, background=background,
explicit=True)
related=False)
def _open_startpage(win_id=None):
@ -309,15 +309,9 @@ def _open_startpage(win_id=None):
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=cur_win_id)
if tabbed_browser.count() == 0:
log.init.debug("Opening startpage")
for urlstr in config.get('general', 'startpage'):
try:
url = urlutils.fuzzy_url(urlstr, do_search=False)
except urlutils.InvalidUrlError as e:
message.error("Error when opening startpage: {}".format(e))
tabbed_browser.tabopen(QUrl('about:blank'))
else:
tabbed_browser.tabopen(url)
log.init.debug("Opening start pages")
for url in config.val.url.start_pages:
tabbed_browser.tabopen(url)
def _open_special_pages(args):
@ -334,36 +328,29 @@ def _open_special_pages(args):
# With --basedir given, don't open anything.
return
state_config = objreg.get('state-config')
general_sect = configfiles.state['general']
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
# Legacy QtWebKit warning
needs_warning = (objects.backend == usertypes.Backend.QtWebKit and
not qtutils.is_qtwebkit_ng())
warning_shown = state_config['general'].get('backend-warning-shown') == '1'
if not warning_shown and needs_warning:
tabbed_browser.tabopen(QUrl('qute://backend-warning'),
background=False)
state_config['general']['backend-warning-shown'] = '1'
# Quickstart page
quickstart_done = state_config['general'].get('quickstart-done') == '1'
quickstart_done = general_sect.get('quickstart-done') == '1'
if not quickstart_done:
tabbed_browser.tabopen(
QUrl('https://www.qutebrowser.org/quickstart.html'))
state_config['general']['quickstart-done'] = '1'
general_sect['quickstart-done'] = '1'
# Setting migration page
def _save_version():
"""Save the current version to the state config."""
state_config = objreg.get('state-config', None)
if state_config is not None:
state_config['general']['version'] = qutebrowser.__version__
needs_migration = os.path.exists(
os.path.join(standarddir.config(), 'qutebrowser.conf'))
migration_shown = general_sect.get('config-migration-shown') == '1'
if needs_migration and not migration_shown:
tabbed_browser.tabopen(QUrl('qute://help/configuring.html'),
background=False)
general_sect['config-migration-shown'] = '1'
def on_focus_changed(_old, new):
@ -406,7 +393,7 @@ def _init_modules(args, crash_handler):
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(qApp)
objreg.register('save-manager', save_manager)
save_manager.add_saveable('version', _save_version)
config.late_init(save_manager)
log.init.debug("Initializing network...")
networkmanager.init()
@ -418,13 +405,6 @@ def _init_modules(args, crash_handler):
readline_bridge = readline.ReadlineBridge()
objreg.register('readline-bridge', readline_bridge)
log.init.debug("Initializing config...")
config.init(qApp)
save_manager.init_autosave()
log.init.debug("Initializing keys...")
keyconf.init(qApp)
log.init.debug("Initializing sql...")
try:
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
@ -433,6 +413,9 @@ def _init_modules(args, crash_handler):
pre_text='Error initializing SQL')
sys.exit(usertypes.Exit.err_init)
log.init.debug("Initializing command history...")
cmdhistory.init()
log.init.debug("Initializing web history...")
history.init(qApp)
@ -470,7 +453,7 @@ def _init_modules(args, crash_handler):
objreg.register('cache', diskcache)
log.init.debug("Misc initialization...")
if config.get('ui', 'hide-wayland-decoration'):
if config.val.window.hide_wayland_decoration:
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
else:
os.environ.pop('QT_WAYLAND_DISABLE_WINDOWDECORATION', None)
@ -640,8 +623,25 @@ class Quitter:
else:
return True
@cmdutils.register(instance='quitter', name=['quit', 'q'],
ignore_args=True)
@cmdutils.register(instance='quitter', name='quit')
@cmdutils.argument('session', completion=miscmodels.session)
def quit(self, save=False, session=None):
"""Quit qutebrowser.
Args:
save: When given, save the open windows even if auto_save.session
is turned off.
session: The name of the session to save.
"""
if session is not None and not save:
raise cmdexc.CommandError("Session name given without --save!")
if save:
if session is None:
session = sessions.default
self.shutdown(session=session)
else:
self.shutdown()
def shutdown(self, status=0, session=None, last_window=False,
restart=False):
"""Quit qutebrowser.
@ -663,7 +663,7 @@ class Quitter:
if session is not None:
session_manager.save(session, last_window=last_window,
load_next_time=True)
elif config.get('general', 'save-session'):
elif config.val.auto_save.session:
session_manager.save(sessions.default, last_window=last_window,
load_next_time=True)
@ -742,16 +742,6 @@ class Quitter:
# segfaults.
QTimer.singleShot(0, functools.partial(qApp.exit, status))
@cmdutils.register(instance='quitter', name='wq')
@cmdutils.argument('name', completion=miscmodels.session)
def save_and_quit(self, name=sessions.default):
"""Save open pages and quit.
Args:
name: The name of the session.
"""
self.shutdown(session=name)
class Application(QApplication):
@ -772,7 +762,7 @@ class Application(QApplication):
"""
self._last_focus_object = None
qt_args = qtutils.get_args(args)
qt_args = config.qt_args(args)
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
super().__init__(qt_args)

View File

@ -67,11 +67,7 @@ def is_whitelisted_host(host):
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:
for pattern in config.val.content.host_blocking.whitelist:
if fnmatch.fnmatch(host, pattern.lower()):
return True
return False
@ -114,16 +110,16 @@ class HostBlocker:
data_dir = standarddir.data()
self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts')
self.on_config_changed()
self._update_files()
config_dir = standarddir.config()
self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts')
objreg.get('config').changed.connect(self.on_config_changed)
config.instance.changed.connect(self._update_files)
def is_blocked(self, url):
"""Check if the given URL (as QUrl) is blocked."""
if not config.get('content', 'host-blocking-enabled'):
if not config.val.content.host_blocking.enabled:
return False
host = url.host()
return ((host in self._blocked_hosts or
@ -164,9 +160,9 @@ class HostBlocker:
if not found:
args = objreg.get('args')
if (config.get('content', 'host-block-lists') is not None and
if (config.val.content.host_blocking.lists and
args.basedir is None and
config.get('content', 'host-blocking-enabled')):
config.val.content.host_blocking.enabled):
message.info("Run :adblock-update to get adblock lists.")
@cmdutils.register(instance='host-blocker')
@ -180,18 +176,16 @@ class HostBlocker:
self._config_blocked_hosts)
self._blocked_hosts = set()
self._done_count = 0
urls = config.get('content', 'host-block-lists')
download_manager = objreg.get('qtnetwork-download-manager',
scope='window', window='last-focused')
if urls is None:
return
for url in urls:
for url in config.val.content.host_blocking.lists:
if url.scheme() == 'file':
filename = url.toLocalFile()
try:
fileobj = open(url.path(), 'rb')
fileobj = open(filename, 'rb')
except OSError as e:
message.error("adblock: Error while reading {}: {}".format(
url.path(), e.strerror))
filename, e.strerror))
continue
download = FakeDownload(fileobj)
self._in_progress.append(download)
@ -292,11 +286,10 @@ class HostBlocker:
message.info("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):
@config.change_filter('content.host_blocking.lists')
def _update_files(self):
"""Update files when the config changed."""
urls = config.get('content', 'host-block-lists')
if urls is None:
if not config.val.content.host_blocking.lists:
try:
os.remove(self._local_hosts_file)
except FileNotFoundError:

View File

@ -21,6 +21,7 @@
import itertools
import attr
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QApplication
@ -61,9 +62,6 @@ def init():
if objects.backend == usertypes.Backend.QtWebEngine:
from qutebrowser.browser.webengine import webenginetab
webenginetab.init()
else:
from qutebrowser.browser.webkit import webkittab
webkittab.init()
class WebTabError(Exception):
@ -85,6 +83,7 @@ TerminationStatus = usertypes.enum('TerminationStatus', [
])
@attr.s
class TabData:
"""A simple namespace with a fixed set of attributes.
@ -100,13 +99,12 @@ class TabData:
fullscreen: Whether the tab has a video shown fullscreen currently.
"""
def __init__(self):
self.keep_icon = False
self.viewing_source = False
self.inspector = None
self.override_target = None
self.pinned = False
self.fullscreen = False
keep_icon = attr.ib(False)
viewing_source = attr.ib(False)
inspector = attr.ib(None)
override_target = attr.ib(None)
pinned = attr.ib(False)
fullscreen = attr.ib(False)
class AbstractAction:
@ -188,13 +186,28 @@ class AbstractSearch(QObject):
self.text = None
self.search_displayed = False
def search(self, text, *, ignore_case=False, reverse=False,
def _is_case_sensitive(self, ignore_case):
"""Check if case-sensitivity should be used.
This assumes self.text is already set properly.
Arguments:
ignore_case: The ignore_case value from the config.
"""
mapping = {
'smart': not self.text.islower(),
'never': True,
'always': False,
}
return mapping[ignore_case]
def search(self, text, *, ignore_case='never', reverse=False,
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')
ignore_case: Search case-insensitively. ('always'/'never/'smart')
reverse: Reverse search direction.
result_cb: Called with a bool indicating whether a match was found.
"""
@ -236,7 +249,7 @@ class AbstractZoom(QObject):
self._win_id = win_id
self._default_zoom_changed = False
self._init_neighborlist()
objreg.get('config').changed.connect(self._on_config_changed)
config.instance.changed.connect(self._on_config_changed)
# # FIXME:qtwebengine is this needed?
# # For some reason, this signal doesn't get disconnected automatically
@ -245,21 +258,21 @@ class AbstractZoom(QObject):
# 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']:
@pyqtSlot(str)
def _on_config_changed(self, option):
if option in ['zoom.levels', 'zoom.default']:
if not self._default_zoom_changed:
factor = float(config.get('ui', 'default-zoom')) / 100
factor = float(config.val.zoom.default) / 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')
levels = config.val.zoom.levels
self._neighborlist = usertypes.NeighborList(
levels, mode=usertypes.NeighborList.Modes.edge)
self._neighborlist.fuzzyval = config.get('ui', 'default-zoom')
self._neighborlist.fuzzyval = config.val.zoom.default
def offset(self, offset):
"""Increase/Decrease the zoom level by the given offset.
@ -295,8 +308,7 @@ class AbstractZoom(QObject):
raise NotImplementedError
def set_default(self):
default_zoom = config.get('ui', 'default-zoom')
self._set_factor_internal(float(default_zoom) / 100)
self._set_factor_internal(float(config.val.zoom.default) / 100)
class AbstractCaret(QObject):
@ -705,10 +717,8 @@ class AbstractTab(QWidget):
self.load_started.emit()
def _handle_auto_insert_mode(self, ok):
"""Handle auto-insert-mode after loading finished."""
# Checks if the tab is in foreground first, then eventually sets the mode
foreground = self is objreg.get('tabbed-browser', scope='window', window=self.win_id).currentWidget()
if not config.get('input', 'auto-insert-mode') or not foreground or not ok:
"""Handle `input.insert_mode.auto_load` after loading finished."""
if not config.val.input.insert_mode.auto_load or not ok:
return
cur_mode = self._mode_manager.mode

View File

@ -24,6 +24,7 @@ import sys
import os.path
import shlex
import functools
import typing
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
@ -34,15 +35,16 @@ import pygments.lexers
import pygments.formatters
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
from qutebrowser.config import config, configexc
from qutebrowser.config import config, configdata
from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
webelem, downloads)
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils, typing, debug)
objreg, utils, debug)
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import urlmodel, miscmodels
from qutebrowser.mainwindow import mainwindow
class CommandDispatcher:
@ -70,7 +72,6 @@ class CommandDispatcher:
def _new_tabbed_browser(self, private):
"""Get a tabbed-browser from a new window."""
from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow(private=private)
new_window.show()
return new_window.tabbed_browser
@ -111,7 +112,7 @@ class CommandDispatcher:
return widget
def _open(self, url, tab=False, background=False, window=False,
explicit=True, private=None):
related=False, private=None):
"""Helper function to open a page.
Args:
@ -132,9 +133,9 @@ class CommandDispatcher:
tabbed_browser = self._new_tabbed_browser(private)
tabbed_browser.tabopen(url)
elif tab:
tabbed_browser.tabopen(url, background=False, explicit=explicit)
tabbed_browser.tabopen(url, background=False, related=related)
elif background:
tabbed_browser.tabopen(url, background=True, explicit=explicit)
tabbed_browser.tabopen(url, background=True, related=related)
else:
widget = self._current_widget()
widget.openurl(url)
@ -179,7 +180,7 @@ class CommandDispatcher:
prev: Force selecting the tab before the current tab.
next_: Force selecting the tab after the current tab.
opposite: Force selecting the tab in the opposite direction of
what's configured in 'tabs->select-on-remove'.
what's configured in 'tabs.select_on_remove'.
Return:
QTabBar.SelectLeftTab, QTabBar.SelectRightTab, or None if no change
@ -191,17 +192,17 @@ class CommandDispatcher:
elif next_:
return QTabBar.SelectRightTab
elif opposite:
conf_selection = config.get('tabs', 'select-on-remove')
conf_selection = config.val.tabs.select_on_remove
if conf_selection == QTabBar.SelectLeftTab:
return QTabBar.SelectRightTab
elif conf_selection == QTabBar.SelectRightTab:
return QTabBar.SelectLeftTab
elif conf_selection == QTabBar.SelectPreviousTab:
raise cmdexc.CommandError(
"-o is not supported with 'tabs->select-on-remove' set to "
"-o is not supported with 'tabs.select_on_remove' set to "
"'last-used'!")
else: # pragma: no cover
raise ValueError("Invalid select-on-remove value "
raise ValueError("Invalid select_on_remove value "
"{!r}!".format(conf_selection))
return None
@ -213,7 +214,7 @@ class CommandDispatcher:
prev: Force selecting the tab before the current tab.
next_: Force selecting the tab after the current tab.
opposite: Force selecting the tab in the opposite direction of
what's configured in 'tabs->select-on-remove'.
what's configured in 'tabs.select_on_remove'.
count: The tab index to close, or None
"""
tabbar = self._tabbed_browser.tabBar()
@ -238,7 +239,7 @@ class CommandDispatcher:
prev: Force selecting the tab before the current tab.
next_: Force selecting the tab after the current tab.
opposite: Force selecting the tab in the opposite direction of
what's configured in 'tabs->select-on-remove'.
what's configured in 'tabs.select_on_remove'.
force: Avoid confirmation for pinned tabs.
count: The tab index to close, or None
"""
@ -256,7 +257,7 @@ class CommandDispatcher:
def tab_pin(self, count=None):
"""Pin/Unpin the current/[count]th tab.
Pinning a tab shrinks it to tabs->pinned-width size.
Pinning a tab shrinks it to `tabs.width.pinned` size.
Attempting to close a pinned tab will cause a confirmation,
unless --force is passed.
@ -274,7 +275,7 @@ class CommandDispatcher:
maxsplit=0, scope='window')
@cmdutils.argument('url', completion=urlmodel.url)
@cmdutils.argument('count', count=True)
def openurl(self, url=None, implicit=False,
def openurl(self, url=None, related=False,
bg=False, tab=False, window=False, count=None, secure=False,
private=False):
"""Open a URL in the current/[count]th tab.
@ -286,14 +287,14 @@ class CommandDispatcher:
bg: Open in a new background tab.
tab: Open in a new tab.
window: Open in a new window.
implicit: If opening a new tab, treat the tab as implicit (like
clicking on a link).
related: If opening a new tab, position the tab as related to the
current one (like clicking on a link).
count: The tab index to open the URL in, or None.
secure: Force HTTPS.
private: Open a new window in private browsing mode.
"""
if url is None:
urls = [config.get('general', 'default-page')]
urls = [config.val.url.default_page]
else:
urls = self._parse_url_input(url)
@ -305,7 +306,7 @@ class CommandDispatcher:
bg = True
if tab or bg or window or private:
self._open(cur_url, tab, bg, window, explicit=not implicit,
self._open(cur_url, tab, bg, window, related=related,
private=private)
else:
curtab = self._cntwidget(count)
@ -490,7 +491,7 @@ class CommandDispatcher:
raise cmdexc.CommandError(e)
# The new tab could be in a new tabbed_browser (e.g. because of
# tabs-are-windows being set)
# tabs.tabs_are_windows being set)
if window:
new_tabbed_browser = self._new_tabbed_browser(
private=self._tabbed_browser.private)
@ -502,9 +503,9 @@ class CommandDispatcher:
idx = new_tabbed_browser.indexOf(newtab)
new_tabbed_browser.set_page_title(idx, cur_title)
if config.get('tabs', 'show-favicons'):
if config.val.tabs.favicons.show:
new_tabbed_browser.setTabIcon(idx, curtab.icon())
if config.get('tabs', 'tabs-are-windows'):
if config.val.tabs.tabs_are_windows:
new_tabbed_browser.window().setWindowIcon(curtab.icon())
newtab.data.keep_icon = True
@ -622,7 +623,7 @@ class CommandDispatcher:
tab=tab, background=bg, window=window)
elif where in ['up', 'increment', 'decrement']:
new_url = handlers[where](url, count)
self._open(new_url, tab, bg, window, explicit=False)
self._open(new_url, tab, bg, window, related=True)
else: # pragma: no cover
raise ValueError("Got called with invalid value {} for "
"`where'.".format(where))
@ -771,7 +772,7 @@ class CommandDispatcher:
url_query.setQueryDelimiters('=', ';')
url_query.setQuery(url_query_str)
for key in dict(url_query.queryItems()):
if key in config.get('general', 'yank-ignored-url-parameters'):
if key in config.val.url.yank_ignored_parameters:
url_query.removeQueryItem(key)
url.setQuery(url_query)
return url.toString(flags)
@ -842,7 +843,7 @@ class CommandDispatcher:
perc = tab.zoom.offset(count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info("Zoom level: {}%".format(perc), replace=True)
message.info("Zoom level: {}%".format(int(perc)), replace=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@ -857,7 +858,7 @@ class CommandDispatcher:
perc = tab.zoom.offset(-count)
except ValueError as e:
raise cmdexc.CommandError(e)
message.info("Zoom level: {}%".format(perc), replace=True)
message.info("Zoom level: {}%".format(int(perc)), replace=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@ -881,14 +882,14 @@ class CommandDispatcher:
level = count if count is not None else zoom
if level is None:
level = config.get('ui', 'default-zoom')
level = config.val.zoom.default
tab = self._current_widget()
try:
tab.zoom.set_factor(float(level) / 100)
except ValueError:
raise cmdexc.CommandError("Can't zoom {}%!".format(level))
message.info("Zoom level: {}%".format(level), replace=True)
message.info("Zoom level: {}%".format(int(level)), replace=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_only(self, prev=False, next_=False, force=False):
@ -947,7 +948,7 @@ class CommandDispatcher:
newidx = self._current_index() - count
if newidx >= 0:
self._set_current_index(newidx)
elif config.get('tabs', 'wrap'):
elif config.val.tabs.wrap:
self._set_current_index(newidx % self._count())
else:
raise cmdexc.CommandError("First tab")
@ -967,7 +968,7 @@ class CommandDispatcher:
newidx = self._current_index() + count
if newidx < self._count():
self._set_current_index(newidx)
elif config.get('tabs', 'wrap'):
elif config.val.tabs.wrap:
self._set_current_index(newidx % self._count())
else:
raise cmdexc.CommandError("Last tab")
@ -1124,7 +1125,7 @@ class CommandDispatcher:
elif index == '+': # pragma: no branch
new_idx += delta
if config.get('tabs', 'wrap'):
if config.val.tabs.wrap:
new_idx %= self._count()
else:
# absolute moving
@ -1186,7 +1187,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window')
def home(self):
"""Open main startpage in current tab."""
self.openurl(config.get('general', 'startpage')[0])
self._current_widget().openurl(config.val.url.start_pages[0])
def _run_userscript(self, cmd, *args, verbose=False):
"""Run a userscript given as argument.
@ -1532,7 +1533,7 @@ class CommandDispatcher:
topic: The topic to show help for.
- :__command__ for commands.
- __section__\->__option__ for settings.
- __section__.__option__ for settings.
"""
if topic is None:
path = 'index.html'
@ -1542,20 +1543,8 @@ class CommandDispatcher:
raise cmdexc.CommandError("Invalid command {}!".format(
command))
path = 'commands.html#{}'.format(command)
elif '->' in topic:
parts = topic.split('->')
if len(parts) != 2:
raise cmdexc.CommandError("Invalid help topic {}!".format(
topic))
try:
config.get(*parts)
except configexc.NoSectionError:
raise cmdexc.CommandError("Invalid section {}!".format(
parts[0]))
except configexc.NoOptionError:
raise cmdexc.CommandError("Invalid option {}!".format(
parts[1]))
path = 'settings.html#{}'.format(topic.replace('->', '-'))
elif topic in configdata.DATA:
path = 'settings.html#{}'.format(topic)
else:
raise cmdexc.CommandError("Invalid help topic {}!".format(topic))
url = QUrl('qute://help/{}'.format(path))
@ -1608,7 +1597,7 @@ class CommandDispatcher:
"""Open an external editor with the currently selected form field.
The editor which should be launched can be configured via the
`general -> editor` config option.
`editor.command` config option.
"""
tab = self._current_widget()
tab.elements.find_focused(self._open_editor_cb)
@ -1749,7 +1738,7 @@ class CommandDispatcher:
return
options = {
'ignore_case': config.get('general', 'ignore-case'),
'ignore_case': config.val.ignore_case,
'reverse': reverse,
}
@ -2048,8 +2037,9 @@ class CommandDispatcher:
message.info(out)
if file:
path = os.path.expanduser(js_code)
try:
with open(js_code, 'r', encoding='utf-8') as f:
with open(path, 'r', encoding='utf-8') as f:
js_code = f.read()
except OSError as e:
raise cmdexc.CommandError(str(e))
@ -2102,7 +2092,7 @@ class CommandDispatcher:
"""Navigate to a url formed in an external editor.
The editor which should be launched can be configured via the
`general -> editor` config option.
`editor.command` config option.
Args:
url: URL to edit; defaults to the current page url.

View File

@ -69,15 +69,22 @@ class UnsupportedOperationError(Exception):
def download_dir():
"""Get the download directory to use."""
directory = config.get('storage', 'download-directory')
remember_dir = config.get('storage', 'remember-download-directory')
directory = config.val.downloads.location.directory
remember_dir = config.val.downloads.location.remember
if remember_dir and last_used_directory is not None:
return last_used_directory
ddir = last_used_directory
elif directory is None:
return standarddir.download()
ddir = standarddir.download()
else:
return directory
ddir = directory
try:
os.makedirs(ddir)
except FileExistsError:
pass
return ddir
def immediate_download_path(prompt_download_directory=None):
@ -88,11 +95,10 @@ def immediate_download_path(prompt_download_directory=None):
Args:
prompt_download_directory: If this is something else than None, it
will overwrite the
storage->prompt-download-directory setting.
downloads.location.prompt setting.
"""
if prompt_download_directory is None:
prompt_download_directory = config.get('storage',
'prompt-download-directory')
prompt_download_directory = config.val.downloads.location.prompt
if not prompt_download_directory:
return download_dir()
@ -104,7 +110,7 @@ def _path_suggestion(filename):
Args:
filename: The filename to use if included in the suggestion.
"""
suggestion = config.get('completion', 'download-path-suggestion')
suggestion = config.val.downloads.location.suggestion
if suggestion == 'path':
# add trailing '/' if not present
return os.path.join(download_dir(), '')
@ -494,13 +500,13 @@ class AbstractDownloadItem(QObject):
Args:
position: The color type requested, can be 'fg' or 'bg'.
"""
# pylint: disable=bad-config-call
# WORKAROUND for https://bitbucket.org/logilab/astroid/issue/104/
assert position in ["fg", "bg"]
start = config.get('colors', 'downloads.{}.start'.format(position))
stop = config.get('colors', 'downloads.{}.stop'.format(position))
system = config.get('colors', 'downloads.{}.system'.format(position))
error = config.get('colors', 'downloads.{}.error'.format(position))
# pylint: disable=bad-config-option
start = getattr(config.val.colors.downloads.start, position)
stop = getattr(config.val.colors.downloads.stop, position)
system = getattr(config.val.colors.downloads.system, position)
error = getattr(config.val.colors.downloads.error, position)
# pylint: enable=bad-config-option
if self.error_msg is not None:
assert not self.successful
return error
@ -572,7 +578,7 @@ class AbstractDownloadItem(QObject):
Args:
cmdline: The command to use as string. A `{}` is expanded to the
filename. None means to use the system's default
application or `default-open-dispatcher` if set. If no
application or `downloads.open_dispatcher` if set. If no
`{}` is found, the filename is appended to the cmdline.
"""
assert self.successful
@ -757,7 +763,7 @@ class AbstractDownloadManager(QObject):
download.remove_requested.connect(functools.partial(
self._remove_item, download))
delay = config.get('ui', 'remove-finished-downloads')
delay = config.val.downloads.remove_finished
if delay > -1:
download.finished.connect(
lambda: QTimer.singleShot(delay, download.remove))

View File

@ -26,7 +26,7 @@ from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
from qutebrowser.browser import downloads
from qutebrowser.config import style
from qutebrowser.config import config
from qutebrowser.utils import qtutils, utils, objreg
@ -64,8 +64,8 @@ class DownloadView(QListView):
STYLESHEET = """
QListView {
background-color: {{ color['downloads.bg.bar'] }};
font: {{ font['downloads'] }};
background-color: {{ conf.colors.downloads.bar.bg }};
font: {{ conf.fonts.downloads }};
}
QListView::item {
@ -76,7 +76,7 @@ class DownloadView(QListView):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self.setStyle(QStyleFactory.create('Fusion'))
style.set_register_stylesheet(self)
config.set_register_stylesheet(self)
self.setResizeMode(QListView.Adjust)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)

View File

@ -26,10 +26,11 @@ import re
import html
from string import ascii_lowercase
import attr
from PyQt5.QtCore import pyqtSlot, QObject, Qt, QUrl
from PyQt5.QtWidgets import QLabel
from qutebrowser.config import config, style
from qutebrowser.config import config
from qutebrowser.keyinput import modeman, modeparsers
from qutebrowser.browser import webelem
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
@ -65,10 +66,10 @@ class HintLabel(QLabel):
STYLESHEET = """
QLabel {
background-color: {{ color['hints.bg'] }};
color: {{ color['hints.fg'] }};
font: {{ font['hints'] }};
border: {{ config.get('hints', 'border') }};
background-color: {{ conf.colors.hints.bg }};
color: {{ conf.colors.hints.fg }};
font: {{ conf.fonts.hints }};
border: {{ conf.hints.border }};
padding-left: -3px;
padding-right: -3px;
}
@ -80,7 +81,7 @@ class HintLabel(QLabel):
self.elem = elem
self.setAttribute(Qt.WA_StyledBackground, True)
style.set_register_stylesheet(self)
config.set_register_stylesheet(self)
self._context.tab.contents_size_changed.connect(self._move_to_elem)
self._move_to_elem()
@ -100,7 +101,7 @@ class HintLabel(QLabel):
matched: The part of the text which was typed.
unmatched: The part of the text which was not typed yet.
"""
if (config.get('hints', 'uppercase') and
if (config.val.hints.uppercase and
self._context.hint_mode in ['letter', 'word']):
matched = html.escape(matched.upper())
unmatched = html.escape(unmatched.upper())
@ -108,7 +109,7 @@ class HintLabel(QLabel):
matched = html.escape(matched)
unmatched = html.escape(unmatched)
match_color = html.escape(config.get('colors', 'hints.fg.match'))
match_color = html.escape(config.val.colors.hints.match.fg)
self.setText('<font color="{}">{}</font>{}'.format(
match_color, matched, unmatched))
self.adjustSize()
@ -121,7 +122,7 @@ class HintLabel(QLabel):
log.hints.debug("Frame for {!r} vanished!".format(self))
self.hide()
return
no_js = config.get('hints', 'find-implementation') != 'javascript'
no_js = config.val.hints.find_implementation != 'javascript'
rect = self.elem.rect_on_view(no_js=no_js)
self.move(rect.x(), rect.y())
@ -131,6 +132,7 @@ class HintLabel(QLabel):
self.deleteLater()
@attr.s
class HintContext:
"""Context namespace used for hinting.
@ -158,19 +160,18 @@ class HintContext:
group: The group of web elements to hint.
"""
def __init__(self):
self.all_labels = []
self.labels = {}
self.target = None
self.baseurl = None
self.to_follow = None
self.rapid = False
self.add_history = False
self.filterstr = None
self.args = []
self.tab = None
self.group = None
self.hint_mode = None
all_labels = attr.ib(attr.Factory(list))
labels = attr.ib(attr.Factory(dict))
target = attr.ib(None)
baseurl = attr.ib(None)
to_follow = attr.ib(None)
rapid = attr.ib(False)
add_history = attr.ib(False)
filterstr = attr.ib(None)
args = attr.ib(attr.Factory(list))
tab = attr.ib(None)
group = attr.ib(None)
hint_mode = attr.ib(None)
def get_args(self, urlstr):
"""Get the arguments, with {hint-url} replaced by the given URL."""
@ -203,7 +204,7 @@ class HintActions:
Target.window: usertypes.ClickTarget.window,
Target.hover: usertypes.ClickTarget.normal,
}
if config.get('tabs', 'background-tabs'):
if config.val.tabs.background:
target_mapping[Target.tab] = usertypes.ClickTarget.tab_bg
else:
target_mapping[Target.tab] = usertypes.ClickTarget.tab
@ -389,6 +390,7 @@ class HintManager(QObject):
def _cleanup(self):
"""Clean up after hinting."""
# pylint: disable=not-an-iterable
for label in self._context.all_labels:
label.cleanup()
@ -421,9 +423,9 @@ class HintManager(QObject):
if hint_mode == 'number':
chars = '0123456789'
else:
chars = config.get('hints', 'chars')
min_chars = config.get('hints', 'min-chars')
if config.get('hints', 'scatter') and hint_mode != 'number':
chars = config.val.hints.chars
min_chars = config.val.hints.min_chars
if config.val.hints.scatter and hint_mode != 'number':
return self._hint_scattered(min_chars, chars, elems)
else:
return self._hint_linear(min_chars, chars, elems)
@ -603,7 +605,7 @@ class HintManager(QObject):
modeman.enter(self._win_id, usertypes.KeyMode.hint,
'HintManager.start')
# to make auto-follow == 'always' work
# to make auto_follow == 'always' work
self._handle_auto_follow()
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
@ -615,7 +617,7 @@ class HintManager(QObject):
Args:
rapid: Whether to do rapid hinting. This is only possible with
targets `tab` (with background-tabs=true), `tab-bg`,
targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
`window`, `run`, `hover`, `userscript` and `spawn`.
add_history: Whether to add the spawned or yanked link to the
browsing history.
@ -631,7 +633,7 @@ class HintManager(QObject):
- `normal`: Open the link.
- `current`: Open the link in the current tab.
- `tab`: Open the link in a new tab (honoring the
background-tabs setting).
`tabs.background_tabs` setting).
- `tab-fg`: Open the link in a new foreground tab.
- `tab-bg`: Open the link in a new background tab.
- `window`: Open the link in a new window.
@ -649,7 +651,7 @@ class HintManager(QObject):
mode: The hinting mode to use.
- `number`: Use numeric hints.
- `letter`: Use the chars in the hints->chars settings.
- `letter`: Use the chars in the hints.chars setting.
- `word`: Use hint words based on the html elements and the
extra words.
@ -684,8 +686,7 @@ class HintManager(QObject):
Target.hover, Target.userscript, Target.spawn,
Target.download, Target.normal, Target.current]:
pass
elif (target == Target.tab and
config.get('tabs', 'background-tabs')):
elif target == Target.tab and config.val.tabs.background:
pass
else:
name = target.name.replace('_', '-')
@ -693,7 +694,7 @@ class HintManager(QObject):
"target {}!".format(name))
if mode is None:
mode = config.get('hints', 'mode')
mode = config.val.hints.mode
self._check_args(target, *args)
self._context = HintContext()
@ -720,7 +721,7 @@ class HintManager(QObject):
return self._context.hint_mode
def _handle_auto_follow(self, keystr="", filterstr="", visible=None):
"""Handle the auto-follow option."""
"""Handle the auto_follow option."""
if visible is None:
visible = {string: label
for string, label in self._context.labels.items()
@ -729,7 +730,7 @@ class HintManager(QObject):
if len(visible) != 1:
return
auto_follow = config.get('hints', 'auto-follow')
auto_follow = config.val.hints.auto_follow
if auto_follow == "always":
follow = True
@ -746,8 +747,8 @@ class HintManager(QObject):
self._context.to_follow = list(visible.keys())[0]
if follow:
# apply auto-follow-timeout
timeout = config.get('hints', 'auto-follow-timeout')
# apply auto_follow_timeout
timeout = config.val.hints.auto_follow_timeout
keyparsers = objreg.get('keyparsers', scope='window',
window=self._win_id)
normal_parser = keyparsers[usertypes.KeyMode.normal]
@ -771,9 +772,9 @@ class HintManager(QObject):
label.show()
else:
# element doesn't match anymore -> hide it, unless in rapid
# mode and hide-unmatched-rapid-hints is false (see #1799)
# mode and hide_unmatched_rapid_hints is false (see #1799)
if (not self._context.rapid or
config.get('hints', 'hide-unmatched-rapid-hints')):
config.val.hints.hide_unmatched_rapid_hints):
label.hide()
except webelem.Error:
pass
@ -793,7 +794,10 @@ class HintManager(QObject):
else:
self._context.filterstr = filterstr
log.hints.debug("Filtering hints on {!r}".format(filterstr))
visible = []
# pylint: disable=not-an-iterable
for label in self._context.all_labels:
try:
if self._filter_matches(filterstr, str(label.elem)):
@ -938,7 +942,7 @@ class WordHinter:
def ensure_initialized(self):
"""Generate the used words if yet uninitialized."""
dictionary = config.get("hints", "dictionary")
dictionary = config.val.hints.dictionary
if not self.words or self.dictionary != dictionary:
self.words.clear()
self.dictionary = dictionary

View File

@ -30,6 +30,10 @@ from qutebrowser.utils import (utils, objreg, log, usertypes, message,
from qutebrowser.misc import objects, sql
# increment to indicate that HistoryCompletion must be regenerated
_USER_VERSION = 1
class CompletionHistory(sql.SqlTable):
"""History which only has the newest entry for each URL."""
@ -48,6 +52,11 @@ class WebHistory(sql.SqlTable):
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
parent=parent)
self.completion = CompletionHistory(parent=self)
if sql.Query('pragma user_version').run().value() < _USER_VERSION:
self.completion.delete_all()
if not self.completion:
# either the table is out-of-date or the user wiped it manually
self._rebuild_completion()
self.create_index('HistoryIndex', 'url')
self.create_index('HistoryAtimeIndex', 'atime')
self._contains_query = self.contains_query('url')
@ -71,6 +80,18 @@ class WebHistory(sql.SqlTable):
def __contains__(self, url):
return self._contains_query.run(val=url).value()
def _rebuild_completion(self):
data = {'url': [], 'title': [], 'last_atime': []}
# select the latest entry for each url
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
'WHERE NOT redirect GROUP BY url ORDER BY atime asc')
for entry in q.run():
data['url'].append(self._format_completion_url(QUrl(entry.url)))
data['title'].append(entry.title)
data['last_atime'].append(entry.atime)
self.completion.insert_batch(data, replace=True)
sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run()
def get_recent(self):
"""Get the most recent history entries."""
return self.select(sort_by='atime', sort_order='desc', limit=100)
@ -111,7 +132,7 @@ class WebHistory(sql.SqlTable):
self._do_clear()
else:
message.confirm_async(self._do_clear, title="Clear all browsing "
"history?")
"history?")
def _do_clear(self):
self.delete_all()
@ -152,20 +173,20 @@ class WebHistory(sql.SqlTable):
(hidden in completion)
atime: Override the atime used to add the entry
"""
if not url.isValid(): # pragma: no cover
# the no cover pragma is a WORKAROUND for this not being covered in
# old Qt versions.
if not url.isValid():
log.misc.warning("Ignoring invalid URL being added to history")
return
if 'no-sql-history' in objreg.get('args').debug_flags:
return
atime = int(atime) if (atime is not None) else int(time.time())
url_str = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
self.insert({'url': url_str,
self.insert({'url': self._format_url(url),
'title': title,
'atime': atime,
'redirect': redirect})
if not redirect:
self.completion.insert({'url': url_str,
self.completion.insert({'url': self._format_completion_url(url),
'title': title,
'last_atime': atime},
replace=True)
@ -185,7 +206,8 @@ class WebHistory(sql.SqlTable):
# http://xn--pple-43d.com/ with
# https://bugreports.qt.io/browse/QTBUG-60364
if url in ['http://.com/', 'https://www..com/']:
if url in ['http://.com/', 'https://.com/',
'http://www..com/', 'https://www..com/']:
return None
url = QUrl(url)
@ -248,12 +270,13 @@ class WebHistory(sql.SqlTable):
if parsed is None:
continue
url, title, atime, redirect = parsed
data['url'].append(url)
data['url'].append(self._format_url(url))
data['title'].append(title)
data['atime'].append(atime)
data['redirect'].append(redirect)
if not redirect:
completion_data['url'].append(url)
completion_data['url'].append(
self._format_completion_url(url))
completion_data['title'].append(title)
completion_data['last_atime'].append(atime)
except ValueError as ex:
@ -262,6 +285,12 @@ class WebHistory(sql.SqlTable):
self.insert_batch(data)
self.completion.insert_batch(completion_data, replace=True)
def _format_url(self, url):
return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
def _format_completion_url(self, url):
return url.toString(QUrl.RemovePassword)
@cmdutils.register(instance='web-history', debug=True)
def debug_dump_history(self, dest):
"""Dump the history to a file in the old pre-SQL format.
@ -292,6 +321,6 @@ def init(parent=None):
history = WebHistory(parent=parent)
objreg.register('web-history', history)
if objects.backend == usertypes.Backend.QtWebKit:
if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover
from qutebrowser.browser.webkit import webkithistory
webkithistory.init(history)

View File

@ -24,7 +24,8 @@ import binascii
from PyQt5.QtWidgets import QWidget
from qutebrowser.utils import log, objreg, usertypes
from qutebrowser.config import configfiles
from qutebrowser.utils import log, usertypes
from qutebrowser.misc import miscwidgets, objects
@ -67,9 +68,8 @@ class AbstractWebInspector(QWidget):
def _load_state_geometry(self):
"""Load the geometry from the state file."""
state_config = objreg.get('state-config')
try:
data = state_config['geometry']['inspector']
data = configfiles.state['geometry']['inspector']
geom = base64.b64decode(data, validate=True)
except KeyError:
# First start
@ -84,10 +84,9 @@ class AbstractWebInspector(QWidget):
def closeEvent(self, e):
"""Save the geometry when closed."""
state_config = objreg.get('state-config')
data = bytes(self.saveGeometry())
geom = base64.b64encode(data).decode('ASCII')
state_config['geometry']['inspector'] = geom
configfiles.state['geometry']['inspector'] = geom
super().closeEvent(e)
def inspect(self, page):

View File

@ -85,7 +85,7 @@ class MouseEventFilter(QObject):
def _handle_mouse_press(self, e):
"""Handle pressing of a mouse button."""
is_rocker_gesture = (config.get('input', 'rocker-gestures') and
is_rocker_gesture = (config.val.input.rocker_gestures and
e.buttons() == Qt.LeftButton | Qt.RightButton)
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
@ -119,7 +119,7 @@ class MouseEventFilter(QObject):
return True
if e.modifiers() & Qt.ControlModifier:
divider = config.get('input', 'mouse-zoom-divider')
divider = config.val.zoom.mouse_divider
if divider == 0:
return False
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
@ -139,7 +139,7 @@ class MouseEventFilter(QObject):
def _handle_context_menu(self, _e):
"""Suppress context menus if rocker gestures are turned on."""
return config.get('input', 'rocker-gestures')
return config.val.input.rocker_gestures
def _mousepress_insertmode_cb(self, elem):
"""Check if the clicked element is editable."""
@ -157,7 +157,7 @@ class MouseEventFilter(QObject):
'click', only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element!")
if config.get('input', 'auto-leave-insert-mode'):
if config.val.input.insert_mode.auto_leave:
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
'click', maybe=True)
@ -179,7 +179,7 @@ class MouseEventFilter(QObject):
'click-delayed', only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element (delayed)!")
if config.get('input', 'auto-leave-insert-mode'):
if config.val.input.insert_mode.auto_leave:
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
'click-delayed', maybe=True)

View File

@ -24,6 +24,7 @@ import posixpath
from qutebrowser.browser import webelem
from qutebrowser.config import config
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
from qutebrowser.mainwindow import mainwindow
class Error(Exception):
@ -42,7 +43,7 @@ def incdec(url, count, inc_or_dec):
background: Open the link in a new background tab.
window: Open the link in a new window.
"""
segments = set(config.get('general', 'url-incdec-segments'))
segments = set(config.val.url.incdec_segments)
try:
new_url = urlutils.incdec_number(url, inc_or_dec, count,
segments=segments)
@ -80,10 +81,13 @@ def _find_prevnext(prev, elems):
# Then check for regular links/buttons.
elems = [e for e in elems if e.tag_name() != 'link']
option = 'prev-regexes' if prev else 'next-regexes'
option = 'prev_regexes' if prev else 'next_regexes'
if not elems:
return None
for regex in config.get('hints', option):
# pylint: disable=bad-config-option
for regex in getattr(config.val.hints, option):
# pylint: enable=bad-config-option
log.hints.vdebug("== Checking regex '{}'.".format(regex.pattern))
for e in elems:
text = str(e)
@ -131,7 +135,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
window=win_id)
if window:
from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow(
private=cur_tabbed_browser.private)
new_window.show()

View File

@ -247,10 +247,21 @@ class PACFetcher(QObject):
self._pac_url = url
self._manager = QNetworkAccessManager()
self._manager.setProxy(QNetworkProxy(QNetworkProxy.NoProxy))
self._reply = self._manager.get(QNetworkRequest(url))
self._reply.finished.connect(self._finish)
self._pac = None
self._error_message = None
self._reply = None
def __eq__(self, other):
# pylint: disable=protected-access
return self._pac_url == other._pac_url
def __repr__(self):
return utils.get_repr(self, url=self._pac_url, constructor=True)
def fetch(self):
"""Fetch the proxy from the remote URL."""
self._reply = self._manager.get(QNetworkRequest(self._pac_url))
self._reply.finished.connect(self._finish)
@pyqtSlot()
def _finish(self):

View File

@ -44,7 +44,7 @@ class ProxyFactory(QNetworkProxyFactory):
Return:
None if proxy is correct, otherwise an error message.
"""
proxy = config.get('network', 'proxy')
proxy = config.val.content.proxy
if isinstance(proxy, pac.PACFetcher):
return proxy.fetch_error()
else:
@ -59,7 +59,7 @@ class ProxyFactory(QNetworkProxyFactory):
Return:
A list of QNetworkProxy objects in order of preference.
"""
proxy = config.get('network', 'proxy')
proxy = config.val.content.proxy
if proxy is configtypes.SYSTEM_PROXY:
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
elif isinstance(proxy, pac.PACFetcher):
@ -69,7 +69,7 @@ class ProxyFactory(QNetworkProxyFactory):
for p in proxies:
if p.type() != QNetworkProxy.NoProxy:
capabilities = p.capabilities()
if config.get('network', 'proxy-dns-requests'):
if config.val.content.proxy_dns_requests:
capabilities |= QNetworkProxy.HostNameLookupCapability
else:
capabilities &= ~QNetworkProxy.HostNameLookupCapability

View File

@ -22,8 +22,8 @@
import io
import shutil
import functools
import collections
import attr
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
@ -34,7 +34,11 @@ from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
_RetryInfo = collections.namedtuple('_RetryInfo', ['request', 'manager'])
@attr.s
class _RetryInfo:
request = attr.ib()
manager = attr.ib()
class DownloadItem(downloads.AbstractDownloadItem):
@ -368,7 +372,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
super().__init__(parent)
self._networkmanager = networkmanager.NetworkManager(
win_id=win_id, tab_id=None,
private=config.get('general', 'private-browsing'), parent=self)
private=config.val.content.private_browsing, parent=self)
@pyqtSlot('QUrl')
def get(self, url, *, user_agent=None, **kwargs):
@ -483,7 +487,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
reply: The QNetworkReply to download.
target: Where to save the download as downloads.DownloadTarget.
auto_remove: Whether to remove the download even if
ui -> remove-finished-downloads is set to -1.
downloads.remove_finished is set to -1.
Return:
The created DownloadItem.

View File

@ -28,15 +28,15 @@ import json
import os
import time
import urllib.parse
import datetime
import textwrap
import pkg_resources
from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser
from qutebrowser.config import config
from qutebrowser.config import config, configdata, configexc, configdiff
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg, usertypes, qtutils)
objreg)
from qutebrowser.misc import objects
@ -122,8 +122,7 @@ class add_handler: # pylint: disable=invalid-name
title="Error while opening qute://url",
url=url.toDisplayString(),
error='{} is not available with this '
'backend'.format(url.toDisplayString()),
icon='')
'backend'.format(url.toDisplayString()))
return 'text/html', html
@ -224,50 +223,13 @@ def qute_history(url):
return 'text/html', json.dumps(history_data(start_time, offset))
else:
if (
config.get('content', 'allow-javascript') and
(objects.backend == usertypes.Backend.QtWebEngine or
qtutils.is_qtwebkit_ng())
):
return 'text/html', jinja.render(
'history.html',
title='History',
session_interval=config.get('ui', 'history-session-interval')
)
else:
# Get current date from query parameter, if not given choose today.
curr_date = datetime.date.today()
try:
query_date = QUrlQuery(url).queryItemValue("date")
if query_date:
curr_date = datetime.datetime.strptime(query_date,
"%Y-%m-%d").date()
except ValueError:
log.misc.debug("Invalid date passed to qute:history: " +
query_date)
one_day = datetime.timedelta(days=1)
next_date = curr_date + one_day
prev_date = curr_date - one_day
# start_time is the last second of curr_date
start_time = time.mktime(next_date.timetuple()) - 1
history = [
(i["url"], i["title"],
datetime.datetime.fromtimestamp(i["time"]),
QUrl(i["url"]).host())
for i in history_data(start_time)
]
return 'text/html', jinja.render(
'history_nojs.html',
title='History',
history=history,
curr_date=curr_date,
next_date=next_date,
prev_date=prev_date,
today=datetime.date.today(),
)
if not config.val.content.javascript.enabled:
return 'text/plain', b'JavaScript is required for qute://history'
return 'text/html', jinja.render(
'history.html',
title='History',
gap_interval=config.val.history_gap_interval
)
@add_handler('javascript')
@ -345,26 +307,12 @@ def qute_log(url):
@add_handler('gpl')
def qute_gpl(_url):
"""Handler for qute://gpl. Return HTML content as string."""
return 'text/html', utils.read_file('html/COPYING.html')
return 'text/html', utils.read_file('html/LICENSE.html')
@add_handler('help')
def qute_help(url):
"""Handler for qute://help."""
try:
utils.read_file('html/doc/index.html')
except OSError:
html = jinja.render(
'error.html',
title="Error while loading documentation",
url=url.toDisplayString(),
error="This most likely means the documentation was not generated "
"properly. If you are running qutebrowser from the git "
"repository, please run scripts/asciidoc2html.py. "
"If you're running a released version this is a bug, please "
"use :report to report it.",
icon='')
return 'text/html', html
urlpath = url.path()
if not urlpath or urlpath == '/':
urlpath = 'index.html'
@ -373,11 +321,45 @@ def qute_help(url):
if not docutils.docs_up_to_date(urlpath):
message.error("Your documentation is outdated! Please re-run "
"scripts/asciidoc2html.py.")
path = 'html/doc/{}'.format(urlpath)
if urlpath.endswith('.png'):
return 'image/png', utils.read_file(path, binary=True)
else:
try:
data = utils.read_file(path)
except OSError:
# No .html around, let's see if we find the asciidoc
asciidoc_path = path.replace('.html', '.asciidoc')
if asciidoc_path.startswith('html/doc/'):
asciidoc_path = asciidoc_path.replace('html/doc/', '../doc/help/')
try:
asciidoc = utils.read_file(asciidoc_path)
except OSError:
asciidoc = None
if asciidoc is None:
raise
preamble = textwrap.dedent("""
There was an error loading the documentation!
This most likely means the documentation was not generated
properly. If you are running qutebrowser from the git repository,
please (re)run scripts/asciidoc2html.py and reload this page.
If you're running a released version this is a bug, please use
:report to report it.
Falling back to the plaintext version.
---------------------------------------------------------------
""")
return 'text/plain', (preamble + asciidoc).encode('utf-8')
else:
return 'text/html', data
@ -390,3 +372,47 @@ def qute_backend_warning(_url):
version=pkg_resources.parse_version,
title="Legacy backend warning")
return 'text/html', html
def _qute_settings_set(url):
"""Handler for qute://settings/set."""
query = QUrlQuery(url)
option = query.queryItemValue('option', QUrl.FullyDecoded)
value = query.queryItemValue('value', QUrl.FullyDecoded)
# https://github.com/qutebrowser/qutebrowser/issues/727
if option == 'content.javascript.enabled' and value == 'false':
msg = ("Refusing to disable javascript via qute://settings "
"as it needs javascript support.")
message.error(msg)
return 'text/html', b'error: ' + msg.encode('utf-8')
try:
config.instance.set_str(option, value, save_yaml=True)
return 'text/html', b'ok'
except configexc.Error as e:
message.error(str(e))
return 'text/html', b'error: ' + str(e).encode('utf-8')
@add_handler('settings')
def qute_settings(url):
"""Handler for qute://settings. View/change qute configuration."""
if url.path() == '/set':
return _qute_settings_set(url)
html = jinja.render('settings.html', title='settings',
configdata=configdata,
confget=config.instance.get_str)
return 'text/html', html
@add_handler('configdiff')
def qute_configdiff(_url):
"""Handler for qute://configdiff."""
try:
return 'text/html', configdiff.get_diff()
except OSError as e:
error = (b'Failed to read old config: ' +
str(e.strerror).encode('utf-8'))
return 'text/plain', error

View File

@ -21,10 +21,9 @@
import html
import jinja2
from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg
from qutebrowser.utils import usertypes, message, log, objreg, jinja
from qutebrowser.mainwindow import mainwindow
class CallSuper(Exception):
@ -35,16 +34,18 @@ class CallSuper(Exception):
def custom_headers():
"""Get the combined custom headers."""
headers = {}
dnt = b'1' if config.get('network', 'do-not-track') else b'0'
headers[b'DNT'] = dnt
headers[b'X-Do-Not-Track'] = dnt
config_headers = config.get('network', 'custom-headers')
if config_headers is not None:
for header, value in config_headers.items():
headers[header.encode('ascii')] = value.encode('ascii')
dnt_config = config.val.content.headers.do_not_track
if dnt_config is not None:
dnt = b'1' if dnt_config else b'0'
headers[b'DNT'] = dnt
headers[b'X-Do-Not-Track'] = dnt
accept_language = config.get('network', 'accept-language')
conf_headers = config.val.content.headers.custom
for header, value in conf_headers.items():
headers[header.encode('ascii')] = value.encode('ascii')
accept_language = config.val.content.headers.accept_language
if accept_language is not None:
headers[b'Accept-Language'] = accept_language.encode('ascii')
@ -72,7 +73,7 @@ def authentication_required(url, authenticator, abort_on):
def javascript_confirm(url, js_msg, abort_on):
"""Display a javascript confirm prompt."""
log.js.debug("confirm: {}".format(js_msg))
if config.get('ui', 'modal-js-dialog'):
if config.val.content.javascript.modal_dialog:
raise CallSuper
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
@ -86,9 +87,9 @@ def javascript_confirm(url, js_msg, abort_on):
def javascript_prompt(url, js_msg, default, abort_on):
"""Display a javascript prompt."""
log.js.debug("prompt: {}".format(js_msg))
if config.get('ui', 'modal-js-dialog'):
if config.val.content.javascript.modal_dialog:
raise CallSuper
if config.get('content', 'ignore-javascript-prompt'):
if not config.val.content.javascript.prompt:
return (False, "")
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
@ -107,10 +108,10 @@ def javascript_prompt(url, js_msg, default, abort_on):
def javascript_alert(url, js_msg, abort_on):
"""Display a javascript alert."""
log.js.debug("alert: {}".format(js_msg))
if config.get('ui', 'modal-js-dialog'):
if config.val.content.javascript.modal_dialog:
raise CallSuper
if config.get('content', 'ignore-javascript-alert'):
if not config.val.content.javascript.alert:
return
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
@ -119,6 +120,22 @@ def javascript_alert(url, js_msg, abort_on):
abort_on=abort_on)
def javascript_log_message(level, source, line, msg):
"""Display a JavaScript log message."""
logstring = "[{}:{}] {}".format(source, line, msg)
# Needs to line up with the values allowed for the
# content.javascript.log setting.
logmap = {
'none': lambda arg: None,
'debug': log.js.debug,
'info': log.js.info,
'warning': log.js.warning,
'error': log.js.error,
}
logger = logmap[config.val.content.javascript.log[level.name]]
logger(logstring)
def ignore_certificate_errors(url, errors, abort_on):
"""Display a certificate error question.
@ -129,7 +146,7 @@ def ignore_certificate_errors(url, errors, abort_on):
Return:
True if the error should be ignored, False otherwise.
"""
ssl_strict = config.get('network', 'ssl-strict')
ssl_strict = config.val.content.ssl_strict
log.webview.debug("Certificate errors {!r}, strict {}".format(
errors, ssl_strict))
@ -137,7 +154,7 @@ def ignore_certificate_errors(url, errors, abort_on):
assert error.is_overridable(), repr(error)
if ssl_strict == 'ask':
err_template = jinja2.Template("""
err_template = jinja.environment.from_string("""
Errors while loading <b>{{url.toDisplayString()}}</b>:<br/>
<ul>
{% for err in errors %}
@ -155,7 +172,7 @@ def ignore_certificate_errors(url, errors, abort_on):
ignore = False
return ignore
elif ssl_strict is False:
log.webview.debug("ssl-strict is False, only warning about errors")
log.webview.debug("ssl_strict is False, only warning about errors")
for err in errors:
# FIXME we might want to use warn here (non-fatal error)
# https://github.com/qutebrowser/qutebrowser/issues/114
@ -173,7 +190,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
Args:
url: The URL the request was done for.
option: A (section, option) tuple for the option to check.
option: An option name to check.
msg: A string like "show notifications"
yes_action: A callable to call if the request was approved
no_action: A callable to call if the request was denied
@ -182,7 +199,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
Return:
The Question object if a question was asked, None otherwise.
"""
config_val = config.get(*option)
config_val = config.instance.get(option)
if config_val == 'ask':
if url.isValid():
text = "Allow the website at <b>{}</b> to {}?".format(
@ -218,7 +235,6 @@ def get_tab(win_id, target):
elif target == usertypes.ClickTarget.window:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show()
win_id = window.win_id
@ -233,15 +249,14 @@ def get_tab(win_id, target):
def get_user_stylesheet():
"""Get the combined user-stylesheet."""
filename = config.get('ui', 'user-stylesheet')
css = ''
stylesheets = config.val.content.user_stylesheets
if filename is None:
css = ''
else:
for filename in stylesheets:
with open(filename, 'r', encoding='utf-8') as f:
css = f.read()
css += f.read()
if config.get('ui', 'hide-scrollbar'):
if not config.val.scrolling.bar:
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
return css

View File

@ -31,6 +31,7 @@ from PyQt5.QtGui import QMouseEvent
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.mainwindow import mainwindow
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
@ -182,7 +183,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
# at least a classid attribute. Oh, and let's hope images/...
# DON'T have a classid attribute. HTML sucks.
log.webelem.debug("<object type='{}'> clicked.".format(objtype))
return config.get('input', 'insert-mode-on-plugins')
return config.val.input.insert_mode.plugins
else:
# Image/Audio/...
return False
@ -247,7 +248,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
return self.is_writable()
elif tag in ['embed', 'applet']:
# Flash/Java/...
return config.get('input', 'insert-mode-on-plugins') and not strict
return config.val.input.insert_mode.plugins and not strict
elif tag == 'object':
return self._is_editable_object() and not strict
elif tag in ['div', 'pre']:
@ -329,7 +330,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
usertypes.ClickTarget.tab: Qt.ControlModifier,
usertypes.ClickTarget.tab_bg: Qt.ControlModifier,
}
if config.get('tabs', 'background-tabs'):
if config.val.tabs.background:
modifiers[usertypes.ClickTarget.tab] |= Qt.ShiftModifier
else:
modifiers[usertypes.ClickTarget.tab_bg] |= Qt.ShiftModifier
@ -372,7 +373,6 @@ class AbstractWebElement(collections.abc.MutableMapping):
background = click_target == usertypes.ClickTarget.tab_bg
tabbed_browser.tabopen(url, background=background)
elif click_target == usertypes.ClickTarget.window:
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show()
window.tabbed_browser.tabopen(url)

View File

@ -63,6 +63,6 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
for header, value in shared.custom_headers():
info.setHttpHeader(header, value)
user_agent = config.get('network', 'user-agent')
user_agent = config.val.content.headers.user_agent
if user_agent is not None:
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))

View File

@ -162,7 +162,7 @@ class WebEngineElement(webelem.AbstractWebElement):
top = rect['top']
if width > 1 and height > 1:
# Fix coordinates according to zoom level
# We're not checking for zoom-text-only here as that doesn't
# We're not checking for zoom.text_only here as that doesn't
# exist for QtWebEngine.
zoom = self._tab.zoom.factor()
rect = QRect(left * zoom, top * zoom,

View File

@ -38,7 +38,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
from qutebrowser.browser import shared
from qutebrowser.config import config, websettings
from qutebrowser.utils import objreg, utils, standarddir, javascript, qtutils
from qutebrowser.utils import utils, standarddir, javascript, qtutils
# The default QWebEngineProfile
@ -112,7 +112,7 @@ class DefaultProfileSetter(websettings.Base):
class PersistentCookiePolicy(DefaultProfileSetter):
"""The cookies -> store setting is different from other settings."""
"""The content.cookies.store setting is different from other settings."""
def __init__(self):
super().__init__('setPersistentCookiesPolicy')
@ -158,26 +158,29 @@ def _init_stylesheet(profile):
profile.scripts().insert(script)
def _set_user_agent(profile):
"""Set the user agent for the given profile.
def _set_http_headers(profile):
"""Set the user agent and accept-language for the given profile.
We override this per request in the URL interceptor (to allow for
per-domain user agents), but this one still gets used for things like
window.navigator.userAgent in JS.
We override those per request in the URL interceptor (to allow for
per-domain values), but this one still gets used for things like
window.navigator.userAgent/.languages in JS.
"""
user_agent = config.get('network', 'user-agent')
profile.setHttpUserAgent(user_agent)
profile.setHttpUserAgent(config.val.content.headers.user_agent)
accept_language = config.val.content.headers.accept_language
if accept_language is not None:
profile.setHttpAcceptLanguage(accept_language)
def update_settings(section, option):
def _update_settings(option):
"""Update global settings when qwebsettings changed."""
websettings.update_mappings(MAPPINGS, section, option)
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
websettings.update_mappings(MAPPINGS, option)
if option in ['scrollbar.hide', 'content.user_stylesheets']:
_init_stylesheet(default_profile)
_init_stylesheet(private_profile)
elif section == 'network' and option == 'user-agent':
_set_user_agent(default_profile)
_set_user_agent(private_profile)
elif option in ['content.headers.user_agent',
'content.headers.accept_language']:
_set_http_headers(default_profile)
_set_http_headers(private_profile)
def _init_profiles():
@ -189,12 +192,12 @@ def _init_profiles():
default_profile.setPersistentStoragePath(
os.path.join(standarddir.data(), 'webengine'))
_init_stylesheet(default_profile)
_set_user_agent(default_profile)
_set_http_headers(default_profile)
private_profile = QWebEngineProfile()
assert private_profile.isOffTheRecord()
_init_stylesheet(private_profile)
_set_user_agent(private_profile)
_set_http_headers(private_profile)
def init(args):
@ -212,11 +215,11 @@ def init(args):
# We need to do this here as a WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58650
if not qtutils.version_check('5.9'):
PersistentCookiePolicy().set(config.get('content', 'cookies-store'))
PersistentCookiePolicy().set(config.val.content.cookies.store)
Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
websettings.init_mappings(MAPPINGS)
objreg.get('config').changed.connect(update_settings)
config.instance.changed.connect(_update_settings)
def shutdown():
@ -237,79 +240,70 @@ def shutdown():
MAPPINGS = {
'content': {
'allow-images':
Attribute(QWebEngineSettings.AutoLoadImages),
'allow-javascript':
Attribute(QWebEngineSettings.JavascriptEnabled),
'javascript-can-open-windows-automatically':
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
'javascript-can-access-clipboard':
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
'allow-plugins':
Attribute(QWebEngineSettings.PluginsEnabled),
'hyperlink-auditing':
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
'local-content-can-access-remote-urls':
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
'local-content-can-access-file-urls':
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
'webgl':
Attribute(QWebEngineSettings.WebGLEnabled),
},
'input': {
'spatial-navigation':
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
'links-included-in-focus-chain':
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
},
'fonts': {
'web-family-standard':
FontFamilySetter(QWebEngineSettings.StandardFont),
'web-family-fixed':
FontFamilySetter(QWebEngineSettings.FixedFont),
'web-family-serif':
FontFamilySetter(QWebEngineSettings.SerifFont),
'web-family-sans-serif':
FontFamilySetter(QWebEngineSettings.SansSerifFont),
'web-family-cursive':
FontFamilySetter(QWebEngineSettings.CursiveFont),
'web-family-fantasy':
FontFamilySetter(QWebEngineSettings.FantasyFont),
'web-size-minimum':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumFontSize]),
'web-size-minimum-logical':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumLogicalFontSize]),
'web-size-default':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFontSize]),
'web-size-default-fixed':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFixedFontSize]),
},
'ui': {
'smooth-scrolling':
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
},
'storage': {
'local-storage':
Attribute(QWebEngineSettings.LocalStorageEnabled),
'cache-size':
# 0: automatically managed by QtWebEngine
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
},
'general': {
'xss-auditing':
Attribute(QWebEngineSettings.XSSAuditingEnabled),
'default-encoding':
Setter(QWebEngineSettings.setDefaultTextEncoding),
}
'content.images':
Attribute(QWebEngineSettings.AutoLoadImages),
'content.javascript.enabled':
Attribute(QWebEngineSettings.JavascriptEnabled),
'content.javascript.can_open_tabs_automatically':
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
'content.javascript.can_access_clipboard':
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
'content.plugins':
Attribute(QWebEngineSettings.PluginsEnabled),
'content.hyperlink_auditing':
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
'content.local_content_can_access_remote_urls':
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
'content.local_content_can_access_file_urls':
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
'content.webgl':
Attribute(QWebEngineSettings.WebGLEnabled),
'content.local_storage':
Attribute(QWebEngineSettings.LocalStorageEnabled),
'content.cache.size':
# 0: automatically managed by QtWebEngine
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
'content.xss_auditing':
Attribute(QWebEngineSettings.XSSAuditingEnabled),
'content.default_encoding':
Setter(QWebEngineSettings.setDefaultTextEncoding),
'input.spatial_navigation':
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
'input.links_included_in_focus_chain':
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
'fonts.web.family.standard':
FontFamilySetter(QWebEngineSettings.StandardFont),
'fonts.web.family.fixed':
FontFamilySetter(QWebEngineSettings.FixedFont),
'fonts.web.family.serif':
FontFamilySetter(QWebEngineSettings.SerifFont),
'fonts.web.family.sans_serif':
FontFamilySetter(QWebEngineSettings.SansSerifFont),
'fonts.web.family.cursive':
FontFamilySetter(QWebEngineSettings.CursiveFont),
'fonts.web.family.fantasy':
FontFamilySetter(QWebEngineSettings.FantasyFont),
'fonts.web.size.minimum':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumFontSize]),
'fonts.web.size.minimum_logical':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumLogicalFontSize]),
'fonts.web.size.default':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFontSize]),
'fonts.web.size.default_fixed':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFixedFontSize]),
'scrolling.smooth':
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
}
try:
MAPPINGS['general']['print-element-backgrounds'] = Attribute(
MAPPINGS['content.print_element_backgrounds'] = Attribute(
QWebEngineSettings.PrintElementBackgrounds)
except AttributeError:
# Added in Qt 5.8
@ -318,4 +312,4 @@ except AttributeError:
if qtutils.version_check('5.9'):
# https://bugreports.qt.io/browse/QTBUG-58650
MAPPINGS['content']['cookies-store'] = PersistentCookiePolicy()
MAPPINGS['content.cookies.store'] = PersistentCookiePolicy()

View File

@ -153,20 +153,16 @@ class WebEngineSearch(browsertab.AbstractSearch):
callback(found)
self._widget.findText(text, flags, wrapped_callback)
def search(self, text, *, ignore_case=False, reverse=False,
def search(self, text, *, ignore_case='never', reverse=False,
result_cb=None):
flags = QWebEnginePage.FindFlags(0)
if ignore_case == 'smart':
if not text.islower():
flags |= QWebEnginePage.FindCaseSensitively
elif not ignore_case:
flags |= QWebEnginePage.FindCaseSensitively
if reverse:
flags |= QWebEnginePage.FindBackward
self.text = text
self._flags = flags
self._find(text, flags, result_cb, 'search')
self._flags = QWebEnginePage.FindFlags(0)
if self._is_case_sensitive(ignore_case):
self._flags |= QWebEnginePage.FindCaseSensitively
if reverse:
self._flags |= QWebEnginePage.FindBackward
self._find(text, self._flags, result_cb, 'search')
def clear(self):
self.search_displayed = False
@ -699,7 +695,7 @@ class WebEngineTab(browsertab.AbstractTab):
error_page = jinja.render(
'error.html',
title="Error loading page: {}".format(url_string),
url=url_string, error="Authentication required", icon='')
url=url_string, error="Authentication required")
self.set_html(error_page)
@pyqtSlot('QWebEngineFullScreenRequest')

View File

@ -28,8 +28,7 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from qutebrowser.browser import shared
from qutebrowser.browser.webengine import certificateerror, webenginesettings
from qutebrowser.config import config
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message,
objreg)
from qutebrowser.utils import log, debug, usertypes, jinja, urlutils, message
class WebEngineView(QWebEngineView):
@ -80,10 +79,10 @@ class WebEngineView(QWebEngineView):
The new QWebEngineView object.
"""
debug_type = debug.qenum_key(QWebEnginePage, wintype)
background_tabs = config.get('tabs', 'background-tabs')
background = config.val.tabs.background
log.webview.debug("createWindow with type {}, background_tabs "
"{}".format(debug_type, background_tabs))
log.webview.debug("createWindow with type {}, background {}".format(
debug_type, background))
if wintype == QWebEnginePage.WebBrowserWindow:
# Shift-Alt-Click
@ -95,13 +94,13 @@ class WebEngineView(QWebEngineView):
elif wintype == QWebEnginePage.WebBrowserTab:
# Middle-click / Ctrl-Click with Shift
# FIXME:qtwebengine this also affects target=_blank links...
if background_tabs:
if background:
target = usertypes.ClickTarget.tab
else:
target = usertypes.ClickTarget.tab_bg
elif wintype == QWebEnginePage.WebBrowserBackgroundTab:
# Middle-click / Ctrl-Click
if background_tabs:
if background:
target = usertypes.ClickTarget.tab_bg
else:
target = usertypes.ClickTarget.tab
@ -135,11 +134,11 @@ class WebEnginePage(QWebEnginePage):
self._on_feature_permission_requested)
self._theme_color = theme_color
self._set_bg_color()
objreg.get('config').changed.connect(self._set_bg_color)
config.instance.changed.connect(self._set_bg_color)
@config.change_filter('colors', 'webpage.bg')
@config.change_filter('colors.webpage.bg')
def _set_bg_color(self):
col = config.get('colors', 'webpage.bg')
col = config.val.colors.webpage.bg
if col is None:
col = self._theme_color
self.setBackgroundColor(col)
@ -148,11 +147,10 @@ class WebEnginePage(QWebEnginePage):
def _on_feature_permission_requested(self, url, feature):
"""Ask the user for approval for geolocation/media/etc.."""
options = {
QWebEnginePage.Geolocation: ('content', 'geolocation'),
QWebEnginePage.MediaAudioCapture: ('content', 'media-capture'),
QWebEnginePage.MediaVideoCapture: ('content', 'media-capture'),
QWebEnginePage.MediaAudioVideoCapture:
('content', 'media-capture'),
QWebEnginePage.Geolocation: 'content.geolocation',
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
}
messages = {
QWebEnginePage.Geolocation: 'access your location',
@ -214,7 +212,7 @@ class WebEnginePage(QWebEnginePage):
url_string = url.toDisplayString()
error_page = jinja.render(
'error.html', title="Error loading page: {}".format(url_string),
url=url_string, error=str(error), icon='')
url=url_string, error=str(error))
if error.is_overridable():
ignore = shared.ignore_certificate_errors(
@ -276,19 +274,12 @@ class WebEnginePage(QWebEnginePage):
def javaScriptConsoleMessage(self, level, msg, line, source):
"""Log javascript messages to qutebrowser's log."""
# FIXME:qtwebengine maybe unify this in the tab api somehow?
setting = config.get('general', 'log-javascript-console')
if setting == 'none':
return
level_to_logger = {
QWebEnginePage.InfoMessageLevel: log.js.info,
QWebEnginePage.WarningMessageLevel: log.js.warning,
QWebEnginePage.ErrorMessageLevel: log.js.error,
level_map = {
QWebEnginePage.InfoMessageLevel: usertypes.JsLogLevel.info,
QWebEnginePage.WarningMessageLevel: usertypes.JsLogLevel.warning,
QWebEnginePage.ErrorMessageLevel: usertypes.JsLogLevel.error,
}
logstring = "[{}:{}] {}".format(source, line, msg)
logger = level_to_logger[level]
logger(logstring)
shared.javascript_log_message(level_map[level], source, line, msg)
def acceptNavigationRequest(self,
url: QUrl,

View File

@ -24,7 +24,7 @@ import os.path
from PyQt5.QtNetwork import QNetworkDiskCache
from qutebrowser.config import config
from qutebrowser.utils import utils, objreg, qtutils
from qutebrowser.utils import utils, qtutils
class DiskCache(QNetworkDiskCache):
@ -35,21 +35,20 @@ class DiskCache(QNetworkDiskCache):
super().__init__(parent)
self.setCacheDirectory(os.path.join(cache_dir, 'http'))
self._set_cache_size()
objreg.get('config').changed.connect(self._set_cache_size)
config.instance.changed.connect(self._set_cache_size)
def __repr__(self):
return utils.get_repr(self, size=self.cacheSize(),
maxsize=self.maximumCacheSize(),
path=self.cacheDirectory())
@config.change_filter('storage', 'cache-size')
@config.change_filter('content.cache.size')
def _set_cache_size(self):
"""Set the cache size based on the config."""
size = config.get('storage', 'cache-size')
size = config.val.content.cache.size
if size is None:
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
if (qtutils.version_check('5.7.1') and
not qtutils.version_check('5.9')): # pragma: no cover
if not qtutils.version_check('5.9'): # pragma: no cover
size = 0
self.setMaximumCacheSize(size)

View File

@ -38,12 +38,7 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
string=str(self))
def __hash__(self):
try:
# Qt >= 5.4
return hash(self._error)
except TypeError: # pragma: no cover
return hash((self._error.certificate().toDer(),
self._error.error()))
return hash(self._error)
def __eq__(self, other):
return self._error == other._error # pylint: disable=protected-access

View File

@ -50,7 +50,7 @@ class RAMCookieJar(QNetworkCookieJar):
Return:
True if one or more cookies are set for 'url', otherwise False.
"""
if config.get('content', 'cookies-accept') == 'never':
if config.val.content.cookies.accept == 'never':
return False
else:
self.changed.emit()
@ -74,10 +74,10 @@ class CookieJar(RAMCookieJar):
self._lineparser = lineparser.LineParser(
standarddir.data(), 'cookies', binary=True, parent=self)
self.parse_cookies()
objreg.get('config').changed.connect(self.cookies_store_changed)
config.instance.changed.connect(self._on_cookies_store_changed)
objreg.get('save-manager').add_saveable(
'cookies', self.save, self.changed,
config_opt=('content', 'cookies-store'))
config_opt='content.cookies.store')
def parse_cookies(self):
"""Parse cookies from lineparser and store them."""
@ -105,10 +105,10 @@ class CookieJar(RAMCookieJar):
self._lineparser.data = lines
self._lineparser.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'):
@config.change_filter('content.cookies.store')
def _on_cookies_store_changed(self):
"""Delete stored cookies if cookies.store changed."""
if not config.val.content.cookies.store:
self._lineparser.data = []
self._lineparser.save()
self.changed.emit()

View File

@ -25,7 +25,6 @@ import io
import os
import re
import sys
import collections
import uuid
import email.policy
import email.generator
@ -34,15 +33,21 @@ import email.mime.multipart
import email.message
import quopri
import attr
from PyQt5.QtCore import QUrl
from qutebrowser.browser import downloads
from qutebrowser.browser.webkit import webkitelem
from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils
_File = collections.namedtuple('_File',
['content', 'content_type', 'content_location',
'transfer_encoding'])
@attr.s
class _File:
content = attr.ib()
content_type = attr.ib()
content_location = attr.ib()
transfer_encoding = attr.ib()
_CSS_URL_PATTERNS = [re.compile(x) for x in [
@ -174,7 +179,7 @@ class MHTMLWriter:
root_content: The root content as bytes.
content_location: The url of the page as str.
content_type: The MIME-type of the root content as str.
_files: Mapping of location->_File namedtuple.
_files: Mapping of location->_File object.
"""
def __init__(self, root_content, content_location, content_type):

View File

@ -101,13 +101,12 @@ def dirbrowser_html(path):
except OSError as e:
html = jinja.render('error.html',
title="Error while reading directory",
url='file:///{}'.format(path), error=str(e),
icon='')
url='file:///{}'.format(path), error=str(e))
return html.encode('UTF-8', errors='xmlcharrefreplace')
files = get_file_list(path, all_files, os.path.isfile)
directories = get_file_list(path, all_files, os.path.isdir)
html = jinja.render('dirbrowser.html', title=title, url=path, icon='',
html = jinja.render('dirbrowser.html', title=title, url=path,
parent=parent, files=files, directories=directories)
return html.encode('UTF-8', errors='xmlcharrefreplace')

View File

@ -24,13 +24,13 @@ import collections
import netrc
import html
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
QUrl, QByteArray)
import attr
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
QByteArray)
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
from qutebrowser.config import config
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
urlutils)
from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
from qutebrowser.browser import shared
from qutebrowser.browser.webkit import certificateerror
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
@ -38,10 +38,19 @@ from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%'
ProxyId = collections.namedtuple('ProxyId', 'type, hostname, port')
_proxy_auth_cache = {}
@attr.s(frozen=True)
class ProxyId:
"""Information identifying a proxy server."""
type = attr.ib()
hostname = attr.ib()
port = attr.ib()
def _is_secure_cipher(cipher):
"""Check if a given SSL cipher (hopefully) isn't broken yet."""
tokens = [e.upper() for e in cipher.name().split('-')]
@ -88,15 +97,9 @@ def _is_secure_cipher(cipher):
def init():
"""Disable insecure SSL ciphers on old Qt versions."""
if qtutils.version_check('5.3.0'):
default_ciphers = QSslSocket.defaultCiphers()
log.init.debug("Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
else:
# https://codereview.qt-project.org/#/c/75943/
default_ciphers = QSslSocket.supportedCiphers()
log.init.debug("Supported Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
default_ciphers = QSslSocket.defaultCiphers()
log.init.debug("Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
good_ciphers = []
bad_ciphers = []
@ -274,7 +277,7 @@ class NetworkManager(QNetworkAccessManager):
# altogether.
reply.netrc_used = True
try:
net = netrc.netrc(config.get('network', 'netrc-file'))
net = netrc.netrc(config.val.content.netrc_file)
authenticators = net.authenticators(reply.url().host())
if authenticators is not None:
(user, _account, password) = authenticators
@ -338,7 +341,7 @@ class NetworkManager(QNetworkAccessManager):
def set_referer(self, req, current_url):
"""Set the referer header."""
referer_header_conf = config.get('network', 'referer-header')
referer_header_conf = config.val.content.headers.referer
try:
if referer_header_conf == 'never':
@ -409,24 +412,11 @@ class NetworkManager(QNetworkAccessManager):
tab = objreg.get('tab', scope='tab', window=self._win_id,
tab=self._tab_id)
current_url = tab.url()
except (KeyError, RuntimeError, TypeError):
except (KeyError, RuntimeError):
# https://github.com/qutebrowser/qutebrowser/issues/889
# Catching RuntimeError and TypeError because we could be in
# the middle of the webpage shutdown here.
# Catching RuntimeError because we could be in the middle of
# the webpage shutdown here.
current_url = QUrl()
self.set_referer(req, current_url)
if PYQT_VERSION < 0x050301:
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
#
# If we don't disable our message handler, we get a freeze if a
# warning is printed due to a PyQt bug, e.g. when clicking a
# currency on http://ch.mouser.com/localsites/
#
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034420.html
with log.disable_qt_msghandler():
reply = super().createRequest(op, req, outgoing_data)
else:
reply = super().createRequest(op, req, outgoing_data)
return reply
return super().createRequest(op, req, outgoing_data)

View File

@ -20,16 +20,12 @@
"""QtWebKit specific qute://* handlers and glue code."""
import mimetypes
import functools
import configparser
from PyQt5.QtCore import pyqtSlot, QObject
from PyQt5.QtNetwork import QNetworkReply
from qutebrowser.browser import pdfjs, qutescheme
from qutebrowser.browser.webkit.network import schemehandler, networkreply
from qutebrowser.utils import jinja, log, message, objreg, usertypes, qtutils
from qutebrowser.config import configexc, configdata
from qutebrowser.utils import log, usertypes, qtutils
class QuteSchemeHandler(schemehandler.SchemeHandler):
@ -70,34 +66,6 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
self.parent())
class JSBridge(QObject):
"""Javascript-bridge for special qute://... pages."""
@pyqtSlot(str, str, str)
def set(self, sectname, optname, value):
"""Slot to set a setting from qute://settings."""
# https://github.com/qutebrowser/qutebrowser/issues/727
if ((sectname, optname) == ('content', 'allow-javascript') and
value == 'false'):
message.error("Refusing to disable javascript via qute://settings "
"as it needs javascript support.")
return
try:
objreg.get('config').set('conf', sectname, optname, value)
except (configexc.Error, configparser.Error) as e:
message.error(str(e))
@qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit)
def qute_settings(_url):
"""Handler for qute://settings. View/change qute configuration."""
config_getter = functools.partial(objreg.get('config').get, raw=True)
html = jinja.render('settings.html', title='settings', config=configdata,
confget=config_getter)
return 'text/html', html
@qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit)
def qute_pdfjs(url):
"""Handler for qute://pdfjs. Return the pdf.js viewer."""

View File

@ -19,11 +19,11 @@
"""pyPEG parsing for the RFC 6266 (Content-Disposition) header."""
import collections
import urllib.parse
import string
import re
import attr
import pypeg2 as peg
from qutebrowser.utils import utils
@ -210,7 +210,13 @@ class ContentDispositionValue:
peg.optional(';'))
LangTagged = collections.namedtuple('LangTagged', ['string', 'langtag'])
@attr.s
class LangTagged:
"""A string with an associated language."""
string = attr.ib()
langtag = attr.ib()
class Error(Exception):

View File

@ -25,13 +25,7 @@ from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
from qutebrowser.utils import qtutils
def _encode_url(url):
"""Encode a QUrl suitable to pass to QWebHistory."""
data = bytes(QUrl.toPercentEncoding(url.toString(), b':/#?&+=@%*'))
return data.decode('ascii')
def _serialize_ng(items, current_idx, stream):
def _serialize_items(items, current_idx, stream):
# {'currentItemIndex': 0,
# 'history': [{'children': [],
# 'documentSequenceNumber': 1485030525573123,
@ -47,13 +41,13 @@ def _serialize_ng(items, current_idx, stream):
# 'urlString': 'about:blank'}]}
data = {'currentItemIndex': current_idx, 'history': []}
for item in items:
data['history'].append(_serialize_item_ng(item))
data['history'].append(_serialize_item(item))
stream.writeInt(3) # history stream version
stream.writeQVariantMap(data)
def _serialize_item_ng(item):
def _serialize_item(item):
data = {
'originalURLString': item.original_url.toString(QUrl.FullyEncoded),
'scrollPosition': {'x': 0, 'y': 0},
@ -68,82 +62,6 @@ def _serialize_item_ng(item):
return data
def _serialize_old(items, current_idx, stream):
### Source/WebKit/qt/Api/qwebhistory.cpp operator<<
stream.writeInt(2) # history stream version
stream.writeInt(len(items))
stream.writeInt(current_idx)
for i, item in enumerate(items):
_serialize_item_old(i, item, stream)
def _serialize_item_old(i, item, stream):
"""Serialize a single WebHistoryItem into a QDataStream.
Args:
i: The index of the current item.
item: The WebHistoryItem to write.
stream: The QDataStream to write to.
"""
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
## urlString
stream.writeQString(_encode_url(item.url))
## title
stream.writeQString(item.title)
## originalURLString
stream.writeQString(_encode_url(item.original_url))
### Source/WebCore/history/HistoryItem.cpp decodeBackForwardTree
## backForwardTreeEncodingVersion
stream.writeUInt32(2)
## size (recursion stack)
stream.writeUInt64(0)
## node->m_documentSequenceNumber
# If two HistoryItems have the same document sequence number, then they
# refer to the same instance of a document. Traversing history from one
# such HistoryItem to another preserves the document.
stream.writeInt64(i + 1)
## size (node->m_documentState)
stream.writeUInt64(0)
## node->m_formContentType
# info used to repost form data
stream.writeQString(None)
## hasFormData
stream.writeBool(False)
## node->m_itemSequenceNumber
# If two HistoryItems have the same item sequence number, then they are
# clones of one another. Traversing history from one such HistoryItem to
# another is a no-op. HistoryItem clones are created for parent and
# sibling frames when only a subframe navigates.
stream.writeInt64(i + 1)
## node->m_referrer
stream.writeQString(None)
## node->m_scrollPoint (x)
try:
stream.writeInt32(item.user_data['scroll-pos'].x())
except (KeyError, TypeError):
stream.writeInt32(0)
## node->m_scrollPoint (y)
try:
stream.writeInt32(item.user_data['scroll-pos'].y())
except (KeyError, TypeError):
stream.writeInt32(0)
## node->m_pageScaleFactor
stream.writeFloat(1)
## hasStateObject
# Support for HTML5 History
stream.writeBool(False)
## node->m_target
stream.writeQString(None)
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
## validUserData
# We could restore the user data here, but we prefer to use the
# QWebHistoryItem API for that.
stream.writeBool(False)
def serialize(items):
"""Serialize a list of QWebHistoryItems to a data stream.
@ -180,10 +98,7 @@ def serialize(items):
else:
current_idx = 0
if qtutils.is_qtwebkit_ng():
_serialize_ng(items, current_idx, stream)
else:
_serialize_old(items, current_idx, stream)
_serialize_items(items, current_idx, stream)
user_data += [item.user_data for item in items]

View File

@ -168,7 +168,7 @@ class WebKitElement(webelem.AbstractWebElement):
if width > 1 and height > 1:
# fix coordinates according to zoom level
zoom = self._elem.webFrame().zoomFactor()
if not config.get('ui', 'zoom-text-only'):
if not config.val.zoom.text_only:
rect["left"] *= zoom
rect["top"] *= zoom
width *= zoom
@ -292,9 +292,6 @@ class WebKitElement(webelem.AbstractWebElement):
elem = elem._parent() # pylint: disable=protected-access
def _move_text_cursor(self):
if self is None:
# old PyQt versions call the slot after the element is deleted.
return
if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document()

View File

@ -36,9 +36,9 @@ class WebKitInspector(inspector.AbstractWebInspector):
self._set_widget(qwebinspector)
def inspect(self, page):
if not config.get('general', 'developer-extras'):
if not config.val.content.developer_extras:
raise inspector.WebInspectorError(
"Please enable developer-extras before using the "
"Please enable content.developer_extras before using the "
"webinspector!")
self._widget.setPage(page)
self.show()

View File

@ -33,7 +33,7 @@ from PyQt5.QtGui import QFont
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.config import config, websettings
from qutebrowser.utils import standarddir, objreg, urlutils, qtutils
from qutebrowser.utils import standarddir, urlutils
from qutebrowser.browser import shared
@ -111,12 +111,11 @@ def _set_user_stylesheet():
QWebSettings.globalSettings().setUserStyleSheetUrl(url)
def update_settings(section, option):
def _update_settings(option):
"""Update global settings when qwebsettings changed."""
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
if option in ['scrollbar.hide', 'content.user_stylesheets']:
_set_user_stylesheet()
websettings.update_mappings(MAPPINGS, section, option)
websettings.update_mappings(MAPPINGS, option)
def init(_args):
@ -132,16 +131,9 @@ def init(_args):
QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage'))
if (config.get('general', 'private-browsing') and
not qtutils.version_check('5.4.2')):
# WORKAROUND for https://codereview.qt-project.org/#/c/108936/
# Won't work when private browsing is not enabled globally, but that's
# the best we can do...
QWebSettings.setIconDatabasePath('')
websettings.init_mappings(MAPPINGS)
_set_user_stylesheet()
objreg.get('config').changed.connect(update_settings)
config.instance.changed.connect(_update_settings)
def shutdown():
@ -152,96 +144,79 @@ def shutdown():
MAPPINGS = {
'content': {
'allow-images':
Attribute(QWebSettings.AutoLoadImages),
'allow-javascript':
Attribute(QWebSettings.JavascriptEnabled),
'javascript-can-open-windows-automatically':
Attribute(QWebSettings.JavascriptCanOpenWindows),
'javascript-can-close-windows':
Attribute(QWebSettings.JavascriptCanCloseWindows),
'javascript-can-access-clipboard':
Attribute(QWebSettings.JavascriptCanAccessClipboard),
'allow-plugins':
Attribute(QWebSettings.PluginsEnabled),
'webgl':
Attribute(QWebSettings.WebGLEnabled),
'hyperlink-auditing':
Attribute(QWebSettings.HyperlinkAuditingEnabled),
'local-content-can-access-remote-urls':
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
'local-content-can-access-file-urls':
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
'cookies-accept':
CookiePolicy(),
},
'network': {
'dns-prefetch':
Attribute(QWebSettings.DnsPrefetchEnabled),
},
'input': {
'spatial-navigation':
Attribute(QWebSettings.SpatialNavigationEnabled),
'links-included-in-focus-chain':
Attribute(QWebSettings.LinksIncludedInFocusChain),
},
'fonts': {
'web-family-standard':
FontFamilySetter(QWebSettings.StandardFont),
'web-family-fixed':
FontFamilySetter(QWebSettings.FixedFont),
'web-family-serif':
FontFamilySetter(QWebSettings.SerifFont),
'web-family-sans-serif':
FontFamilySetter(QWebSettings.SansSerifFont),
'web-family-cursive':
FontFamilySetter(QWebSettings.CursiveFont),
'web-family-fantasy':
FontFamilySetter(QWebSettings.FantasyFont),
'web-size-minimum':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.MinimumFontSize]),
'web-size-minimum-logical':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.MinimumLogicalFontSize]),
'web-size-default':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.DefaultFontSize]),
'web-size-default-fixed':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.DefaultFixedFontSize]),
},
'ui': {
'zoom-text-only':
Attribute(QWebSettings.ZoomTextOnly),
'frame-flattening':
Attribute(QWebSettings.FrameFlatteningEnabled),
# user-stylesheet is handled separately
'smooth-scrolling':
Attribute(QWebSettings.ScrollAnimatorEnabled),
#'accelerated-compositing':
# Attribute(QWebSettings.AcceleratedCompositingEnabled),
#'tiled-backing-store':
# Attribute(QWebSettings.TiledBackingStoreEnabled),
},
'storage': {
'offline-web-application-cache':
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
'local-storage':
Attribute(QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled),
'maximum-pages-in-cache':
StaticSetter(QWebSettings.setMaximumPagesInCache),
},
'general': {
'developer-extras':
Attribute(QWebSettings.DeveloperExtrasEnabled),
'print-element-backgrounds':
Attribute(QWebSettings.PrintElementBackgrounds),
'xss-auditing':
Attribute(QWebSettings.XSSAuditingEnabled),
'default-encoding':
Setter(QWebSettings.setDefaultTextEncoding),
}
'content.images':
Attribute(QWebSettings.AutoLoadImages),
'content.javascript.enabled':
Attribute(QWebSettings.JavascriptEnabled),
'content.javascript.can_open_tabs_automatically':
Attribute(QWebSettings.JavascriptCanOpenWindows),
'content.javascript.can_close_tabs':
Attribute(QWebSettings.JavascriptCanCloseWindows),
'content.javascript.can_access_clipboard':
Attribute(QWebSettings.JavascriptCanAccessClipboard),
'content.plugins':
Attribute(QWebSettings.PluginsEnabled),
'content.webgl':
Attribute(QWebSettings.WebGLEnabled),
'content.hyperlink_auditing':
Attribute(QWebSettings.HyperlinkAuditingEnabled),
'content.local_content_can_access_remote_urls':
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
'content.local_content_can_access_file_urls':
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
'content.cookies.accept':
CookiePolicy(),
'content.dns_prefetch':
Attribute(QWebSettings.DnsPrefetchEnabled),
'content.frame_flattening':
Attribute(QWebSettings.FrameFlatteningEnabled),
'content.cache.appcache':
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
'content.local_storage':
Attribute(QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled),
'content.cache.maximum_pages':
StaticSetter(QWebSettings.setMaximumPagesInCache),
'content.developer_extras':
Attribute(QWebSettings.DeveloperExtrasEnabled),
'content.print_element_backgrounds':
Attribute(QWebSettings.PrintElementBackgrounds),
'content.xss_auditing':
Attribute(QWebSettings.XSSAuditingEnabled),
'content.default_encoding':
Setter(QWebSettings.setDefaultTextEncoding),
# content.user_stylesheets is handled separately
'input.spatial_navigation':
Attribute(QWebSettings.SpatialNavigationEnabled),
'input.links_included_in_focus_chain':
Attribute(QWebSettings.LinksIncludedInFocusChain),
'fonts.web.family.standard':
FontFamilySetter(QWebSettings.StandardFont),
'fonts.web.family.fixed':
FontFamilySetter(QWebSettings.FixedFont),
'fonts.web.family.serif':
FontFamilySetter(QWebSettings.SerifFont),
'fonts.web.family.sans_serif':
FontFamilySetter(QWebSettings.SansSerifFont),
'fonts.web.family.cursive':
FontFamilySetter(QWebSettings.CursiveFont),
'fonts.web.family.fantasy':
FontFamilySetter(QWebSettings.FantasyFont),
'fonts.web.size.minimum':
Setter(QWebSettings.setFontSize, args=[QWebSettings.MinimumFontSize]),
'fonts.web.size.minimum_logical':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.MinimumLogicalFontSize]),
'fonts.web.size.default':
Setter(QWebSettings.setFontSize, args=[QWebSettings.DefaultFontSize]),
'fonts.web.size.default_fixed':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.DefaultFixedFontSize]),
'zoom.text_only':
Attribute(QWebSettings.ZoomTextOnly),
'scrolling.smooth':
Attribute(QWebSettings.ScrollAnimatorEnabled),
}

View File

@ -27,25 +27,15 @@ import sip
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
QSize)
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtPrintSupport import QPrinter
from qutebrowser.browser import browsertab
from qutebrowser.browser.webkit import webview, tabhistory, webkitelem
from qutebrowser.browser.webkit.network import webkitqutescheme
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
def init():
"""Initialize QtWebKit-specific modules."""
qapp = QApplication.instance()
log.init.debug("Initializing js-bridge...")
js_bridge = webkitqutescheme.JSBridge(qapp)
objreg.register('js-bridge', js_bridge)
class WebKitAction(browsertab.AbstractAction):
"""QtWebKit implementations related to web actions."""
@ -65,20 +55,14 @@ class WebKitPrinting(browsertab.AbstractPrinting):
"""QtWebKit implementations related to printing."""
def _do_check(self):
if not qtutils.check_print_compat():
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
raise browsertab.WebTabError(
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
def check_pdf_support(self):
self._do_check()
pass
def check_printer_support(self):
self._do_check()
pass
def check_preview_support(self):
self._do_check()
pass
def to_pdf(self, filename):
printer = QPrinter()
@ -133,24 +117,21 @@ class WebKitSearch(browsertab.AbstractSearch):
self._widget.findText('')
self._widget.findText('', QWebPage.HighlightAllOccurrences)
def search(self, text, *, ignore_case=False, reverse=False,
def search(self, text, *, ignore_case='never', reverse=False,
result_cb=None):
self.search_displayed = True
flags = QWebPage.FindWrapsAroundDocument
if ignore_case == 'smart':
if not text.islower():
flags |= QWebPage.FindCaseSensitively
elif not ignore_case:
flags |= QWebPage.FindCaseSensitively
self.text = text
self._flags = QWebPage.FindWrapsAroundDocument
if self._is_case_sensitive(ignore_case):
self._flags |= QWebPage.FindCaseSensitively
if reverse:
flags |= QWebPage.FindBackward
self._flags |= QWebPage.FindBackward
# We actually search *twice* - once to highlight everything, then again
# to get a mark so we can navigate.
found = self._widget.findText(text, flags)
self._widget.findText(text, flags | QWebPage.HighlightAllOccurrences)
self.text = text
self._flags = flags
self._call_cb(result_cb, found, text, flags, 'search')
found = self._widget.findText(text, self._flags)
self._widget.findText(text,
self._flags | QWebPage.HighlightAllOccurrences)
self._call_cb(result_cb, found, text, self._flags, 'search')
def next_result(self, *, result_cb=None):
self.search_displayed = True

View File

@ -22,7 +22,7 @@
import html
import functools
from PyQt5.QtCore import pyqtSlot, pyqtSignal, PYQT_VERSION, Qt, QUrl, QPoint
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from PyQt5.QtWidgets import QFileDialog
@ -33,8 +33,8 @@ from qutebrowser.config import config
from qutebrowser.browser import pdfjs, shared
from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
objreg, debug, urlutils)
from qutebrowser.utils import (message, usertypes, log, jinja, objreg, debug,
urlutils)
class BrowserPage(QWebPage):
@ -87,22 +87,16 @@ class BrowserPage(QWebPage):
self.restoreFrameStateRequested.connect(
self.on_restore_frame_state_requested)
if PYQT_VERSION > 0x050300:
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
# We can't override javaScriptPrompt with older PyQt-versions because
# of a bug in PyQt.
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html
def javaScriptPrompt(self, frame, js_msg, default):
"""Override javaScriptPrompt to use qutebrowser prompts."""
if self._is_shutting_down:
return (False, "")
try:
return shared.javascript_prompt(frame.url(), js_msg, default,
abort_on=[self.loadStarted,
self.shutting_down])
except shared.CallSuper:
return super().javaScriptPrompt(frame, js_msg, default)
def javaScriptPrompt(self, frame, js_msg, default):
"""Override javaScriptPrompt to use qutebrowser prompts."""
if self._is_shutting_down:
return (False, "")
try:
return shared.javascript_prompt(frame.url(), js_msg, default,
abort_on=[self.loadStarted,
self.shutting_down])
except shared.CallSuper:
return super().javaScriptPrompt(frame, js_msg, default)
def _handle_errorpage(self, info, errpage):
"""Display an error page if needed.
@ -170,7 +164,7 @@ class BrowserPage(QWebPage):
title = "Error loading page: {}".format(urlstr)
error_html = jinja.render(
'error.html',
title=title, url=urlstr, error=error_str, icon='')
title=title, url=urlstr, error=error_str)
errpage.content = error_html.encode('utf-8')
errpage.encoding = 'utf-8'
return True
@ -225,10 +219,6 @@ class BrowserPage(QWebPage):
def on_print_requested(self, frame):
"""Handle printing when requested via javascript."""
if not qtutils.check_print_compat():
message.error("Printing on Qt < 5.3.0 on Windows is broken, "
"please upgrade!")
return
printdiag = QPrintDialog()
printdiag.setAttribute(Qt.WA_DeleteOnClose)
printdiag.open(lambda: frame.print(printdiag.printer()))
@ -277,7 +267,7 @@ class BrowserPage(QWebPage):
reply.finished.connect(functools.partial(
self.display_content, reply, 'image/jpeg'))
elif (mimetype in ['application/pdf', 'application/x-pdf'] and
config.get('content', 'enable-pdfjs')):
config.val.content.pdfjs):
# Use pdf.js to display the page
self._show_pdfjs(reply)
else:
@ -304,8 +294,8 @@ class BrowserPage(QWebPage):
return
options = {
QWebPage.Notifications: ('content', 'notifications'),
QWebPage.Geolocation: ('content', 'geolocation'),
QWebPage.Notifications: 'content.notifications',
QWebPage.Geolocation: 'content.geolocation',
}
messages = {
QWebPage.Notifications: 'show notifications',
@ -350,15 +340,7 @@ class BrowserPage(QWebPage):
frame: The QWebFrame which gets saved.
item: The QWebHistoryItem to be saved.
"""
try:
if frame != self.mainFrame():
return
except RuntimeError:
# With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab:
# RuntimeError: wrapped C/C++ object of type BrowserPage has
# been deleted
# Since the information here isn't that important for closing web
# views anyways, we ignore this error.
if frame != self.mainFrame():
return
data = {
'zoom': frame.zoomFactor(),
@ -384,7 +366,7 @@ class BrowserPage(QWebPage):
def userAgentForUrl(self, url):
"""Override QWebPage::userAgentForUrl to customize the user agent."""
ua = config.get('network', 'user-agent')
ua = config.val.content.headers.user_agent
if ua is None:
return super().userAgentForUrl(url)
else:
@ -401,9 +383,6 @@ class BrowserPage(QWebPage):
"""
return ext in self._extension_handlers
# WORKAROUND for:
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html
@utils.prevent_exceptions(False, PYQT_VERSION < 0x50302)
def extension(self, ext, opt, out):
"""Override QWebPage::extension to provide error pages.
@ -446,15 +425,8 @@ class BrowserPage(QWebPage):
def javaScriptConsoleMessage(self, msg, line, source):
"""Override javaScriptConsoleMessage to use debug log."""
log_javascript_console = config.get('general',
'log-javascript-console')
logstring = "[{}:{}] {}".format(source, line, msg)
logmap = {
'debug': log.js.debug,
'info': log.js.info,
'none': lambda arg: None
}
logmap[log_javascript_console](logstring)
shared.javascript_log_message(usertypes.JsLogLevel.unknown,
source, line, msg)
def acceptNavigationRequest(self,
_frame: QWebFrame,

View File

@ -25,11 +25,11 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QStyleFactory
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg, debug
from qutebrowser.utils import log, usertypes, utils, objreg, debug
from qutebrowser.browser.webkit import webpage
@ -57,7 +57,7 @@ class WebView(QWebView):
def __init__(self, *, win_id, tab_id, tab, private, parent=None):
super().__init__(parent)
if sys.platform == 'darwin' and qtutils.version_check('5.4'):
if sys.platform == 'darwin':
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
# See https://github.com/qutebrowser/qutebrowser/issues/462
self.setStyle(QStyleFactory.create('Fusion'))
@ -74,13 +74,9 @@ class WebView(QWebView):
page = webpage.BrowserPage(win_id=self.win_id, tab_id=self._tab_id,
tabdata=tab.data, private=private,
parent=self)
try:
page.setVisibilityState(
QWebPage.VisibilityStateVisible if self.isVisible()
else QWebPage.VisibilityStateHidden)
except AttributeError:
pass
page.setVisibilityState(
QWebPage.VisibilityStateVisible if self.isVisible()
else QWebPage.VisibilityStateHidden)
self.setPage(page)
@ -88,7 +84,7 @@ class WebView(QWebView):
window=win_id)
mode_manager.entered.connect(self.on_mode_entered)
mode_manager.left.connect(self.on_mode_left)
objreg.get('config').changed.connect(self._set_bg_color)
config.instance.changed.connect(self._set_bg_color)
def __repr__(self):
url = utils.elide(self.url().toDisplayString(QUrl.EncodeUnicode), 100)
@ -107,10 +103,10 @@ class WebView(QWebView):
# deleted
pass
@config.change_filter('colors', 'webpage.bg')
@config.change_filter('colors.webpage.bg')
def _set_bg_color(self):
"""Set the webpage background color as configured."""
col = config.get('colors', 'webpage.bg')
col = config.val.colors.webpage.bg
palette = self.palette()
if col is None:
col = self.style().standardPalette().color(QPalette.Base)
@ -135,22 +131,6 @@ class WebView(QWebView):
url: The URL to load as QUrl
"""
self.load(url)
if url.scheme() == 'qute':
frame = self.page().mainFrame()
frame.javaScriptWindowObjectCleared.connect(self.add_js_bridge)
@pyqtSlot()
def add_js_bridge(self):
"""Add the javascript bridge for qute://... pages."""
frame = self.sender()
if not isinstance(frame, QWebFrame):
log.webview.error("Got non-QWebFrame {!r} in "
"add_js_bridge!".format(frame))
return
if frame.url().scheme() == 'qute':
bridge = objreg.get('js-bridge')
frame.addToJavaScriptWindowObject('qute', bridge)
@pyqtSlot(usertypes.KeyMode)
def on_mode_entered(self, mode):
@ -256,12 +236,8 @@ class WebView(QWebView):
Return:
The superclass event return value.
"""
try:
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
except AttributeError:
pass
super().showEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
def hideEvent(self, e):
"""Extend hideEvent to set the page visibility state to hidden.
@ -272,12 +248,8 @@ class WebView(QWebView):
Return:
The superclass event return value.
"""
try:
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
except AttributeError:
pass
super().hideEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
def mousePressEvent(self, e):
"""Set the tabdata ClickTarget on a mousepress.
@ -285,10 +257,10 @@ class WebView(QWebView):
This is implemented here as we don't need it for QtWebEngine.
"""
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
background_tabs = config.get('tabs', 'background-tabs')
background = config.val.tabs.background
if e.modifiers() & Qt.ShiftModifier:
background_tabs = not background_tabs
if background_tabs:
background = not background
if background:
target = usertypes.ClickTarget.tab_bg
else:
target = usertypes.ClickTarget.tab

View File

@ -23,33 +23,33 @@ Defined here to avoid circular dependency hell.
"""
class CommandError(Exception):
class Error(Exception):
"""Base class for all cmdexc errors."""
class CommandError(Error):
"""Raised when a command encounters an error while running."""
pass
class CommandMetaError(Exception):
"""Common base class for exceptions occurring before a command is run."""
class NoSuchCommandError(CommandMetaError):
class NoSuchCommandError(Error):
"""Raised when a command wasn't found."""
pass
class ArgumentTypeError(CommandMetaError):
class ArgumentTypeError(Error):
"""Raised when an argument had an invalid type."""
pass
class PrerequisitesError(CommandMetaError):
class PrerequisitesError(Error):
"""Raised when a cmd can't be used because some prerequisites aren't met.

View File

@ -21,7 +21,6 @@
Module attributes:
cmd_dict: A mapping from command-strings to command objects.
aliases: A list of all aliases, needed for doc generation.
"""
import inspect
@ -30,7 +29,6 @@ from qutebrowser.utils import qtutils, log
from qutebrowser.commands import command, cmdexc
cmd_dict = {}
aliases = []
def check_overflow(arg, ctype):
@ -88,28 +86,6 @@ class register: # pylint: disable=invalid-name
self._name = name
self._kwargs = kwargs
def _get_names(self, func):
"""Get the name(s) which should be used for the current command.
If the name hasn't been overridden explicitly, the function name is
transformed.
If it has been set, it can either be a string which is
used directly, or an iterable.
Args:
func: The function to get the name of.
Return:
A list of names, with the main name being the first item.
"""
if self._name is None:
return [func.__name__.lower().replace('_', '-')]
elif isinstance(self._name, str):
return [self._name]
else:
return self._name
def __call__(self, func):
"""Register the command before running the function.
@ -124,17 +100,17 @@ class register: # pylint: disable=invalid-name
Return:
The original function (unmodified).
"""
global aliases
names = self._get_names(func)
log.commands.vdebug("Registering command {}".format(names[0]))
for name in names:
if name in cmd_dict:
raise ValueError("{} is already registered!".format(name))
cmd = command.Command(name=names[0], instance=self._instance,
if self._name is None:
name = func.__name__.lower().replace('_', '-')
else:
assert isinstance(self._name, str), self._name
name = self._name
log.commands.vdebug("Registering command {}".format(name))
if name in cmd_dict:
raise ValueError("{} is already registered!".format(name))
cmd = command.Command(name=name, instance=self._instance,
handler=func, **self._kwargs)
for name in names:
cmd_dict[name] = cmd
aliases += names[1:]
cmd_dict[name] = cmd
return func

View File

@ -22,44 +22,34 @@
import inspect
import collections
import traceback
import typing
import attr
from qutebrowser.commands import cmdexc, argparser
from qutebrowser.utils import (log, utils, message, docutils, objreg,
usertypes, typing)
from qutebrowser.utils import log, message, docutils, objreg, usertypes
from qutebrowser.utils import debug as debug_utils
from qutebrowser.misc import objects
@attr.s
class ArgInfo:
"""Information about an argument."""
def __init__(self, win_id=False, count=False, hide=False, metavar=None,
flag=None, completion=None, choices=None):
if win_id and count:
win_id = attr.ib(False)
count = attr.ib(False)
hide = attr.ib(False)
metavar = attr.ib(None)
flag = attr.ib(None)
completion = attr.ib(None)
choices = attr.ib(None)
@win_id.validator
@count.validator
def _validate_exclusive(self, _attr, _value):
if self.win_id and self.count:
raise TypeError("Argument marked as both count/win_id!")
self.win_id = win_id
self.count = count
self.flag = flag
self.hide = hide
self.metavar = metavar
self.completion = completion
self.choices = choices
def __eq__(self, other):
return (self.win_id == other.win_id and
self.count == other.count and
self.flag == other.flag and
self.hide == other.hide and
self.metavar == other.metavar and
self.completion == other.completion and
self.choices == other.choices)
def __repr__(self):
return utils.get_repr(self, win_id=self.win_id, count=self.count,
flag=self.flag, hide=self.hide,
metavar=self.metavar, completion=self.completion,
choices=self.choices, constructor=True)
class Command:
@ -90,7 +80,7 @@ class Command:
def __init__(self, *, handler, name, instance=None, maxsplit=None,
hide=False, modes=None, not_modes=None, debug=False,
ignore_args=False, deprecated=False, no_cmd_split=False,
deprecated=False, no_cmd_split=False,
star_args_optional=False, scope='global', backend=None,
no_replace_variables=False):
# I really don't know how to solve this in a better way, I tried.
@ -121,7 +111,6 @@ class Command:
self._scope = scope
self._star_args_optional = star_args_optional
self.debug = debug
self.ignore_args = ignore_args
self.handler = handler
self.no_cmd_split = no_cmd_split
self.backend = backend
@ -225,33 +214,31 @@ class Command:
else:
self.desc = ""
if not self.ignore_args:
for param in signature.parameters.values():
# https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
# "Python has no explicit syntax for defining positional-only
# parameters, but many built-in and extension module functions
# (especially those that accept only one or two parameters)
# accept them."
assert param.kind != inspect.Parameter.POSITIONAL_ONLY
if param.name == 'self':
continue
if self._inspect_special_param(param):
continue
if (param.kind == inspect.Parameter.KEYWORD_ONLY and
param.default is inspect.Parameter.empty):
raise TypeError("{}: handler has keyword only argument "
"{!r} without default!".format(self.name,
param.name))
typ = self._get_type(param)
is_bool = typ is bool
kwargs = self._param_to_argparse_kwargs(param, is_bool)
args = self._param_to_argparse_args(param, is_bool)
callsig = debug_utils.format_call(
self.parser.add_argument, args, kwargs,
full=False)
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
param.name, typ, callsig))
self.parser.add_argument(*args, **kwargs)
for param in signature.parameters.values():
# https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind
# "Python has no explicit syntax for defining positional-only
# parameters, but many built-in and extension module functions
# (especially those that accept only one or two parameters) accept
# them."
assert param.kind != inspect.Parameter.POSITIONAL_ONLY
if param.name == 'self':
continue
if self._inspect_special_param(param):
continue
if (param.kind == inspect.Parameter.KEYWORD_ONLY and
param.default is inspect.Parameter.empty):
raise TypeError("{}: handler has keyword only argument {!r} "
"without default!".format(
self.name, param.name))
typ = self._get_type(param)
is_bool = typ is bool
kwargs = self._param_to_argparse_kwargs(param, is_bool)
args = self._param_to_argparse_args(param, is_bool)
callsig = debug_utils.format_call(self.parser.add_argument, args,
kwargs, full=False)
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
param.name, typ, callsig))
self.parser.add_argument(*args, **kwargs)
return signature.parameters.values()
def _param_to_argparse_kwargs(self, param, is_bool):
@ -419,9 +406,10 @@ class Command:
# support that.
# pylint: disable=no-member,useless-suppression
try:
types = list(typ.__union_params__)
except AttributeError:
types = list(typ.__args__)
except AttributeError:
# Older Python 3.5 patch versions
types = list(typ.__union_params__)
# pylint: enable=no-member,useless-suppression
if param.default is not inspect.Parameter.empty:
types.append(type(param.default))
@ -453,12 +441,6 @@ class Command:
kwargs = {}
signature = inspect.signature(self.handler)
if self.ignore_args:
if self._instance is not None:
param = list(signature.parameters.values())[0]
self._get_self_arg(win_id, param, args)
return args, kwargs
for i, param in enumerate(signature.parameters.values()):
arg_info = self.get_arg_info(param)
if i == 0 and self._instance is not None:

View File

@ -19,22 +19,31 @@
"""Module containing command managers (SearchRunner and CommandRunner)."""
import collections
import traceback
import re
import attr
from PyQt5.QtCore import pyqtSlot, QUrl, QObject
from qutebrowser.config import config, configexc
from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
from qutebrowser.misc import split
ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline'])
last_command = {}
@attr.s
class ParseResult:
"""The result of parsing a commandline."""
cmd = attr.ib()
args = attr.ib()
cmdline = attr.ib()
def _current_url(tabbed_browser):
"""Convenience method to get the current url."""
try:
@ -81,19 +90,17 @@ def replace_variables(win_id, arglist):
return args
class CommandRunner(QObject):
class CommandParser:
"""Parse and run qutebrowser commandline commands.
"""Parse qutebrowser commandline commands.
Attributes:
_win_id: The window this CommandRunner is associated with.
_partial_match: Whether to allow partial command matches.
"""
def __init__(self, win_id, partial_match=False, parent=None):
super().__init__(parent)
def __init__(self, partial_match=False):
self._partial_match = partial_match
self._win_id = win_id
def _get_alias(self, text, default=None):
"""Get an alias from the config.
@ -108,9 +115,10 @@ class CommandRunner(QObject):
"""
parts = text.strip().split(maxsplit=1)
try:
alias = config.get('aliases', parts[0])
except (configexc.NoOptionError, configexc.NoSectionError):
alias = config.val.aliases[parts[0]]
except KeyError:
return default
try:
new_cmd = '{} {}'.format(alias, parts[1])
except IndexError:
@ -119,7 +127,7 @@ class CommandRunner(QObject):
new_cmd += ' '
return new_cmd
def parse_all(self, text, aliases=True, *args, **kwargs):
def _parse_all_gen(self, text, aliases=True, *args, **kwargs):
"""Split a command on ;; and parse all parts.
If the first command in the commandline is a non-split one, it only
@ -154,6 +162,10 @@ class CommandRunner(QObject):
for sub in sub_texts:
yield self.parse(sub, *args, **kwargs)
def parse_all(self, *args, **kwargs):
"""Wrapper over parse_all."""
return list(self._parse_all_gen(*args, **kwargs))
def parse(self, text, *, fallback=False, keep=False):
"""Split the commandline text into command and arguments.
@ -253,6 +265,20 @@ class CommandRunner(QObject):
# already.
return split_args
class CommandRunner(QObject):
"""Parse and run qutebrowser commandline commands.
Attributes:
_win_id: The window this CommandRunner is associated with.
"""
def __init__(self, win_id, partial_match=False, parent=None):
super().__init__(parent)
self._parser = CommandParser(partial_match=partial_match)
self._win_id = win_id
def run(self, text, count=None):
"""Parse a command from a line of text and run it.
@ -267,7 +293,7 @@ class CommandRunner(QObject):
window=self._win_id)
cur_mode = mode_manager.mode
for result in self.parse_all(text):
for result in self._parser.parse_all(text):
if result.cmd.no_replace_variables:
args = result.args
else:
@ -294,7 +320,7 @@ class CommandRunner(QObject):
"""Run a command and display exceptions in the statusbar."""
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
except cmdexc.Error as e:
message.error(str(e), stack=traceback.format_exc())
@pyqtSlot(str, int)
@ -306,5 +332,5 @@ class CommandRunner(QObject):
"""
try:
self.run(text, count)
except (cmdexc.CommandMetaError, cmdexc.CommandError) as e:
except cmdexc.Error as e:
message.error(str(e), stack=traceback.format_exc())

View File

@ -376,7 +376,7 @@ def _lookup_path(cmd):
"""
directories = [
os.path.join(standarddir.data(), "userscripts"),
os.path.join(standarddir.system_data(), "userscripts"),
os.path.join(standarddir.data(system=True), "userscripts"),
]
for directory in directories:
cmd_path = os.path.join(directory, cmd)
@ -417,7 +417,7 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
lambda cmd:
log.commands.debug("Got userscript command: {}".format(cmd)))
runner.got_cmd.connect(commandrunner.run_safely)
user_agent = config.get('network', 'user-agent')
user_agent = config.val.content.headers.user_agent
if user_agent is not None:
env['QUTE_USER_AGENT'] = user_agent

View File

@ -19,6 +19,7 @@
"""Completer attached to a CompletionView."""
import attr
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
from qutebrowser.config import config
@ -27,6 +28,15 @@ from qutebrowser.utils import log, utils, debug
from qutebrowser.completion.models import miscmodels
@attr.s
class CompletionInfo:
"""Context passed into all completion functions."""
config = attr.ib()
keyconf = attr.ib()
class Completer(QObject):
"""Completer which manages completions in a CompletionView.
@ -34,7 +44,6 @@ class Completer(QObject):
Attributes:
_cmd: The statusbar Command object this completer belongs to.
_ignore_change: Whether to ignore the next completion update.
_win_id: The window ID this completer is in.
_timer: The timer used to trigger the completion update.
_last_cursor_pos: The old cursor position so we avoid double completion
updates.
@ -42,9 +51,8 @@ class Completer(QObject):
_last_completion_func: The completion function used for the last text.
"""
def __init__(self, cmd, win_id, parent=None):
def __init__(self, cmd, parent=None):
super().__init__(parent)
self._win_id = win_id
self._cmd = cmd
self._ignore_change = False
self._timer = QTimer()
@ -106,7 +114,7 @@ class Completer(QObject):
"""
if not s:
return "''"
elif any(c in s for c in ' \'\t\n\\'):
elif any(c in s for c in ' "\'\t\n\\'):
# use single quotes, and put single quotes into double quotes
# the string $'b is then quoted as '$'"'"'b'
return "'" + s.replace("'", "'\"'\"'") + "'"
@ -123,9 +131,11 @@ class Completer(QObject):
if not text or not text.strip():
# Only ":", empty part under the cursor with nothing before/after
return [], '', []
runner = runners.CommandRunner(self._win_id)
result = runner.parse(text, fallback=True, keep=True)
parser = runners.CommandParser()
result = parser.parse(text, fallback=True, keep=True)
# pylint: disable=not-an-iterable
parts = [x for x in result.cmdline if x]
# pylint: enable=not-an-iterable
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
log.completion.debug('partitioning {} around position {}'.format(parts,
@ -164,7 +174,7 @@ class Completer(QObject):
if maxsplit is None:
text = self._quote(text)
model = self._model()
if model.count() == 1 and config.get('completion', 'quick-complete'):
if model.count() == 1 and config.val.completion.quick:
# If we only have one item, we want to apply it immediately
# and go on to the next part.
self._change_completed_part(text, before, after, immediate=True)
@ -233,7 +243,9 @@ class Completer(QObject):
args = (x for x in before_cursor[1:] if not x.startswith('-'))
with debug.log_time(log.completion,
'Starting {} completion'.format(func.__name__)):
model = func(*args)
info = CompletionInfo(config=config.instance,
keyconf=config.key_instance)
model = func(*args, info=info)
with debug.log_time(log.completion, 'Set completion model'):
completion.set_model(model)

View File

@ -30,8 +30,8 @@ from PyQt5.QtCore import QRectF, QSize, Qt
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
QAbstractTextDocumentLayout)
from qutebrowser.config import config, configexc, style
from qutebrowser.utils import qtutils
from qutebrowser.config import config
from qutebrowser.utils import qtutils, jinja
class CompletionItemDelegate(QStyledItemDelegate):
@ -147,16 +147,15 @@ class CompletionItemDelegate(QStyledItemDelegate):
# We can't use drawContents because then the color would be ignored.
clip = QRectF(0, 0, rect.width(), rect.height())
self._painter.save()
if self._opt.state & QStyle.State_Selected:
option = 'completion.item.selected.fg'
color = config.val.colors.completion.item.selected.fg
elif not self._opt.state & QStyle.State_Enabled:
option = 'completion.category.fg'
color = config.val.colors.completion.category.fg
else:
option = 'completion.fg'
try:
self._painter.setPen(config.get('colors', option))
except configexc.NoOptionError:
self._painter.setPen(config.get('colors', 'completion.fg'))
color = config.val.colors.completion.fg
self._painter.setPen(color)
ctx = QAbstractTextDocumentLayout.PaintContext()
ctx.palette.setColor(QPalette.Text, self._painter.pen().color())
if clip.isValid():
@ -188,13 +187,17 @@ class CompletionItemDelegate(QStyledItemDelegate):
self._doc = QTextDocument(self)
self._doc.setDefaultFont(self._opt.font)
self._doc.setDefaultTextOption(text_option)
self._doc.setDefaultStyleSheet(style.get_stylesheet("""
.highlight {
color: {{ color['completion.match.fg'] }};
}
"""))
self._doc.setDocumentMargin(2)
stylesheet = """
.highlight {
color: {{ conf.colors.completion.match.fg }};
}
"""
with jinja.environment.no_autoescape():
template = jinja.environment.from_string(stylesheet)
self._doc.setDefaultStyleSheet(template.render(conf=config.val))
if index.parent().isValid():
view = self.parent()
pattern = view.pattern
@ -209,7 +212,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
else:
self._doc.setHtml(
'<span style="font: {};">{}</span>'.format(
html.escape(config.get('fonts', 'completion.category')),
html.escape(config.val.fonts.completion.category),
html.escape(self._opt.text)))
def _draw_focus_rect(self):

View File

@ -26,9 +26,9 @@ subclasses to provide completions.
from PyQt5.QtWidgets import QStyle, QTreeView, QSizePolicy, QStyleFactory
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
from qutebrowser.config import config, style
from qutebrowser.config import config
from qutebrowser.completion import completiondelegate
from qutebrowser.utils import utils, usertypes, objreg, debug, log
from qutebrowser.utils import utils, usertypes, debug, log
from qutebrowser.commands import cmdexc, cmdutils
@ -57,27 +57,27 @@ class CompletionView(QTreeView):
# don't define that in this stylesheet.
STYLESHEET = """
QTreeView {
font: {{ font['completion'] }};
background-color: {{ color['completion.bg'] }};
alternate-background-color: {{ color['completion.alternate-bg'] }};
font: {{ conf.fonts.completion.entry }};
background-color: {{ conf.colors.completion.even.bg }};
alternate-background-color: {{ conf.colors.completion.odd.bg }};
outline: 0;
border: 0px;
}
QTreeView::item:disabled {
background-color: {{ color['completion.category.bg'] }};
background-color: {{ conf.colors.completion.category.bg }};
border-top: 1px solid
{{ color['completion.category.border.top'] }};
{{ conf.colors.completion.category.border.top }};
border-bottom: 1px solid
{{ color['completion.category.border.bottom'] }};
{{ conf.colors.completion.category.border.bottom }};
}
QTreeView::item:selected, QTreeView::item:selected:hover {
border-top: 1px solid
{{ color['completion.item.selected.border.top'] }};
{{ conf.colors.completion.item.selected.border.top }};
border-bottom: 1px solid
{{ color['completion.item.selected.border.bottom'] }};
background-color: {{ color['completion.item.selected.bg'] }};
{{ conf.colors.completion.item.selected.border.bottom }};
background-color: {{ conf.colors.completion.item.selected.bg }};
}
QTreeView:item::hover {
@ -85,14 +85,14 @@ class CompletionView(QTreeView):
}
QTreeView QScrollBar {
width: {{ config.get('completion', 'scrollbar-width') }}px;
background: {{ color['completion.scrollbar.bg'] }};
width: {{ conf.completion.scrollbar.width }}px;
background: {{ conf.colors.completion.scrollbar.bg }};
}
QTreeView QScrollBar::handle {
background: {{ color['completion.scrollbar.fg'] }};
border: {{ config.get('completion', 'scrollbar-padding') }}px solid
{{ color['completion.scrollbar.bg'] }};
background: {{ conf.colors.completion.scrollbar.fg }};
border: {{ conf.completion.scrollbar.padding }}px solid
{{ conf.colors.completion.scrollbar.bg }};
min-height: 10px;
}
@ -109,14 +109,14 @@ class CompletionView(QTreeView):
super().__init__(parent)
self.pattern = ''
self._win_id = win_id
objreg.get('config').changed.connect(self._on_config_changed)
config.instance.changed.connect(self._on_config_changed)
self._active = False
self._delegate = completiondelegate.CompletionItemDelegate(self)
self.setItemDelegate(self._delegate)
self.setStyle(QStyleFactory.create('Fusion'))
style.set_register_stylesheet(self)
config.set_register_stylesheet(self)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self.setHeaderHidden(True)
self.setAlternatingRowColors(True)
@ -139,11 +139,9 @@ class CompletionView(QTreeView):
def __repr__(self):
return utils.get_repr(self)
@pyqtSlot(str, str)
def _on_config_changed(self, section, option):
if section != 'completion':
return
if option in ['height', 'shrink']:
@pyqtSlot(str)
def _on_config_changed(self, option):
if option in ['completion.height', 'completion.shrink']:
self.update_geometry.emit()
def _resize_columns(self):
@ -262,9 +260,9 @@ class CompletionView(QTreeView):
count = self.model().count()
if count == 0:
self.hide()
elif count == 1 and config.get('completion', 'quick-complete'):
elif count == 1 and config.val.completion.quick:
self.hide()
elif config.get('completion', 'show') == 'auto':
elif config.val.completion.show == 'auto':
self.show()
def set_model(self, model):
@ -306,7 +304,7 @@ class CompletionView(QTreeView):
self._maybe_show()
def _maybe_show(self):
if (config.get('completion', 'show') == 'always' and
if (config.val.completion.show == 'always' and
self.model().count() > 0):
self.show()
else:
@ -314,7 +312,7 @@ class CompletionView(QTreeView):
def _maybe_update_geometry(self):
"""Emit the update_geometry signal if the config says so."""
if config.get('completion', 'shrink'):
if config.val.completion.shrink:
self.update_geometry.emit()
@pyqtSlot()
@ -329,14 +327,14 @@ class CompletionView(QTreeView):
def sizeHint(self):
"""Get the completion size according to the config."""
# Get the configured height/percentage.
confheight = str(config.get('completion', 'height'))
confheight = str(config.val.completion.height)
if confheight.endswith('%'):
perc = int(confheight.rstrip('%'))
height = self.window().height() * perc / 100
else:
height = int(confheight)
# Shrink to content size if needed and shrinking is enabled
if config.get('completion', 'shrink'):
if config.val.completion.shrink:
contents_height = (
self.viewportSizeHint().height() +
self.horizontalScrollBar().sizeHint().height())

View File

@ -20,77 +20,63 @@
"""Functions that return config-related completion models."""
from qutebrowser.config import configdata, configexc
from qutebrowser.completion.models import completionmodel, listcategory
from qutebrowser.utils import objreg
from qutebrowser.completion.models import completionmodel, listcategory, util
from qutebrowser.commands import cmdutils
def section():
"""A CompletionModel filled with settings sections."""
def option(*, info):
"""A CompletionModel filled with settings and their descriptions."""
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
sections = ((name, configdata.SECTION_DESC[name].splitlines()[0].strip())
for name in configdata.DATA)
model.add_category(listcategory.ListCategory("Sections", sections))
options = ((opt.name, opt.description, info.config.get_str(opt.name))
for opt in configdata.DATA.values())
model.add_category(listcategory.ListCategory("Options", sorted(options)))
return model
def option(sectname):
"""A CompletionModel filled with settings and their descriptions.
Args:
sectname: The name of the config section this model shows.
"""
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
try:
sectdata = configdata.DATA[sectname]
except KeyError:
return None
options = []
for name in sectdata:
try:
desc = sectdata.descriptions[name]
except (KeyError, AttributeError):
# Some stuff (especially ValueList items) don't have a
# description.
desc = ""
else:
desc = desc.splitlines()[0]
config = objreg.get('config')
val = config.get(sectname, name, raw=True)
options.append((name, desc, val))
model.add_category(listcategory.ListCategory(sectname, options))
return model
def value(sectname, optname):
def value(optname, *_values, info):
"""A CompletionModel filled with setting values.
Args:
sectname: The name of the config section this model shows.
optname: The name of the config option this model shows.
_values: The values already provided on the command line.
info: A CompletionInfo instance.
"""
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
config = objreg.get('config')
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
try:
current = config.get(sectname, optname, raw=True) or '""'
except (configexc.NoSectionError, configexc.NoOptionError):
current = info.config.get_str(optname) or '""'
except configexc.NoOptionError:
return None
default = configdata.DATA[sectname][optname].default() or '""'
if hasattr(configdata.DATA[sectname], 'valtype'):
# Same type for all values (ValueList)
vals = configdata.DATA[sectname].valtype.complete()
else:
if optname is None:
raise ValueError("optname may only be None for ValueList "
"sections, but {} is not!".format(sectname))
# Different type for each value (KeyValue)
vals = configdata.DATA[sectname][optname].typ.complete()
opt = info.config.get_opt(optname)
default = opt.typ.to_str(opt.default)
cur_cat = listcategory.ListCategory("Current/Default",
[(current, "Current value"), (default, "Default value")])
model.add_category(cur_cat)
vals = opt.typ.complete()
if vals is not None:
model.add_category(listcategory.ListCategory("Completions", vals))
model.add_category(listcategory.ListCategory("Completions",
sorted(vals)))
return model
def bind(key, *, info):
"""A CompletionModel filled with all bindable commands and descriptions.
Args:
key: the key being bound.
"""
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
cmd_text = info.keyconf.get_command(key, 'normal')
if cmd_text:
cmd_name = cmd_text.split(' ')[0]
cmd = cmdutils.cmd_dict.get(cmd_name)
data = [(cmd_text, cmd.desc, key)]
model.add_category(listcategory.ListCategory("Current", data))
cmdlist = util.get_cmd_completions(info, include_hidden=True,
include_aliases=True)
model.add_category(listcategory.ListCategory("Commands", cmdlist))
return model

View File

@ -38,9 +38,9 @@ class HistoryCategory(QSqlQueryModel):
self.name = "History"
# replace ' in timestamp-format to avoid breaking the query
timestamp_format = config.val.completion.timestamp_format
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
.format(config.get('completion', 'timestamp-format')
.replace("'", "`")))
.format(timestamp_format.replace("'", "`")))
self._query = sql.Query(' '.join([
"SELECT url, title, {}".format(timefmt),
@ -58,7 +58,7 @@ class HistoryCategory(QSqlQueryModel):
def _atime_expr(self):
"""If max_items is set, return an expression to limit the query."""
max_items = config.get('completion', 'web-history-max-items')
max_items = config.val.completion.web_history_max_items
# HistoryCategory should not be added to the completion in that case.
assert max_items != 0

View File

@ -60,33 +60,19 @@ class ListCategory(QSortFilterProxyModel):
sortcol = 0
self.sort(sortcol)
def lessThan(self, lindex, rindex):
def lessThan(self, _lindex, rindex):
"""Custom sorting implementation.
Prefers all items which start with self._pattern. Other than that, uses
normal Python string sorting.
Prefers all items which start with self._pattern. Other than that, keep
items in their original order.
Args:
lindex: The QModelIndex of the left item (*left* < right)
_lindex: The QModelIndex of the left item (*left* < right)
rindex: The QModelIndex of the right item (left < *right*)
Return:
True if left < right, else False
"""
qtutils.ensure_valid(lindex)
qtutils.ensure_valid(rindex)
left = self.srcmodel.data(lindex)
right = self.srcmodel.data(rindex)
leftstart = left.startswith(self._pattern)
rightstart = right.startswith(self._pattern)
if leftstart and rightstart:
return left < right
elif leftstart:
return True
elif rightstart:
return False
else:
return left < right
return not right.startswith(self._pattern)

View File

@ -19,46 +19,35 @@
"""Functions that return miscellaneous completion models."""
from qutebrowser.config import config, configdata
from qutebrowser.config import configdata
from qutebrowser.utils import objreg, log
from qutebrowser.commands import cmdutils
from qutebrowser.completion.models import completionmodel, listcategory
from qutebrowser.completion.models import completionmodel, listcategory, util
def command():
def command(*, info):
"""A CompletionModel filled with non-hidden commands and descriptions."""
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
cmdlist = _get_cmd_completions(include_aliases=True, include_hidden=False)
cmdlist = util.get_cmd_completions(info, include_aliases=True,
include_hidden=False)
model.add_category(listcategory.ListCategory("Commands", cmdlist))
return model
def helptopic():
def helptopic(*, info):
"""A CompletionModel filled with help topics."""
model = completionmodel.CompletionModel()
cmdlist = _get_cmd_completions(include_aliases=False, include_hidden=True,
prefix=':')
settings = []
for sectname, sectdata in configdata.DATA.items():
for optname in sectdata:
try:
desc = sectdata.descriptions[optname]
except (KeyError, AttributeError):
# Some stuff (especially ValueList items) don't have a
# description.
desc = ""
else:
desc = desc.splitlines()[0]
name = '{}->{}'.format(sectname, optname)
settings.append((name, desc))
cmdlist = util.get_cmd_completions(info, include_aliases=False,
include_hidden=True, prefix=':')
settings = ((opt.name, opt.description)
for opt in configdata.DATA.values())
model.add_category(listcategory.ListCategory("Commands", cmdlist))
model.add_category(listcategory.ListCategory("Settings", settings))
model.add_category(listcategory.ListCategory("Settings", sorted(settings)))
return model
def quickmark():
def quickmark(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with all quickmarks."""
def delete(data):
"""Delete a quickmark from the completion menu."""
@ -74,7 +63,7 @@ def quickmark():
return model
def bookmark():
def bookmark(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with all bookmarks."""
def delete(data):
"""Delete a bookmark from the completion menu."""
@ -90,7 +79,7 @@ def bookmark():
return model
def session():
def session(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with session names."""
model = completionmodel.CompletionModel()
try:
@ -103,7 +92,7 @@ def session():
return model
def buffer():
def buffer(*, info=None): # pylint: disable=unused-argument
"""A model to complete on open tabs across all windows.
Used for switching the buffer command.
@ -133,51 +122,3 @@ def buffer():
model.add_category(cat)
return model
def bind(key):
"""A CompletionModel filled with all bindable commands and descriptions.
Args:
key: the key being bound.
"""
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
cmd_text = objreg.get('key-config').get_bindings_for('normal').get(key)
if cmd_text:
cmd_name = cmd_text.split(' ')[0]
cmd = cmdutils.cmd_dict.get(cmd_name)
data = [(cmd_text, cmd.desc, key)]
model.add_category(listcategory.ListCategory("Current", data))
cmdlist = _get_cmd_completions(include_hidden=True, include_aliases=True)
model.add_category(listcategory.ListCategory("Commands", cmdlist))
return model
def _get_cmd_completions(include_hidden, include_aliases, prefix=''):
"""Get a list of completions info for commands, sorted by name.
Args:
include_hidden: True to include commands annotated with hide=True.
include_aliases: True to include command aliases.
prefix: String to append to the command name.
Return: A list of tuples of form (name, description, bindings).
"""
assert cmdutils.cmd_dict
cmdlist = []
cmd_to_keys = objreg.get('key-config').get_reverse_bindings_for('normal')
for obj in set(cmdutils.cmd_dict.values()):
hide_debug = obj.debug and not objreg.get('args').debug
hide_hidden = obj.hide and not include_hidden
if not (hide_debug or hide_hidden or obj.deprecated):
bindings = ', '.join(cmd_to_keys.get(obj.name, []))
cmdlist.append((prefix + obj.name, obj.desc, bindings))
if include_aliases:
for name, cmd in config.section('aliases').items():
bindings = ', '.join(cmd_to_keys.get(name, []))
cmdlist.append((name, "Alias for '{}'".format(cmd), bindings))
return cmdlist

View File

@ -22,7 +22,6 @@
from qutebrowser.completion.models import (completionmodel, listcategory,
histcategory)
from qutebrowser.utils import log, objreg
from qutebrowser.config import config
_URLCOL = 0
@ -50,7 +49,7 @@ def _delete_quickmark(data):
quickmark_manager.delete(name)
def url():
def url(*, info):
"""A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command.
@ -66,7 +65,7 @@ def url():
model.add_category(listcategory.ListCategory(
'Bookmarks', bookmarks, delete_func=_delete_bookmark))
if config.get('completion', 'web-history-max-items') != 0:
if info.config.get('completion.web_history_max_items') != 0:
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
model.add_category(hist_cat)
return model

View File

@ -0,0 +1,52 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
#
# 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/>.
"""Utility functions for completion models."""
from qutebrowser.utils import objreg
from qutebrowser.commands import cmdutils
def get_cmd_completions(info, include_hidden, include_aliases, prefix=''):
"""Get a list of completions info for commands, sorted by name.
Args:
info: The CompletionInfo.
include_hidden: True to include commands annotated with hide=True.
include_aliases: True to include command aliases.
prefix: String to append to the command name.
Return: A list of tuples of form (name, description, bindings).
"""
assert cmdutils.cmd_dict
cmdlist = []
cmd_to_keys = info.keyconf.get_reverse_bindings_for('normal')
for obj in set(cmdutils.cmd_dict.values()):
hide_debug = obj.debug and not objreg.get('args').debug
hide_hidden = obj.hide and not include_hidden
if not (hide_debug or hide_hidden or obj.deprecated):
bindings = ', '.join(cmd_to_keys.get(obj.name, []))
cmdlist.append((prefix + obj.name, obj.desc, bindings))
if include_aliases:
for name, cmd in info.config.get('aliases').items():
bindings = ', '.join(cmd_to_keys.get(name, []))
cmdlist.append((name, "Alias for '{}'".format(cmd), bindings))
return sorted(cmdlist)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,760 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Code to show a diff of the legacy config format."""
import difflib
import os.path
import pygments
import pygments.lexers
import pygments.formatters
from qutebrowser.utils import standarddir
OLD_CONF = """
[general]
ignore-case = smart
startpage = https://start.duckduckgo.com
yank-ignored-url-parameters = ref,utm_source,utm_medium,utm_campaign,utm_term,utm_content
default-open-dispatcher =
default-page = ${startpage}
auto-search = naive
auto-save-config = true
auto-save-interval = 15000
editor = gvim -f "{}"
editor-encoding = utf-8
private-browsing = false
developer-extras = false
print-element-backgrounds = true
xss-auditing = false
default-encoding = iso-8859-1
new-instance-open-target = tab
new-instance-open-target.window = last-focused
log-javascript-console = debug
save-session = false
session-default-name =
url-incdec-segments = path,query
[ui]
history-session-interval = 30
zoom-levels = 25%,33%,50%,67%,75%,90%,100%,110%,125%,150%,175%,200%,250%,300%,400%,500%
default-zoom = 100%
downloads-position = top
status-position = bottom
message-timeout = 2000
message-unfocused = false
confirm-quit = never
zoom-text-only = false
frame-flattening = false
user-stylesheet =
hide-scrollbar = true
smooth-scrolling = false
remove-finished-downloads = -1
hide-statusbar = false
statusbar-padding = 1,1,0,0
window-title-format = {perc}{title}{title_sep}qutebrowser
modal-js-dialog = false
hide-wayland-decoration = false
keyhint-blacklist =
keyhint-delay = 500
prompt-radius = 8
prompt-filebrowser = true
[network]
do-not-track = true
accept-language = en-US,en
referer-header = same-domain
user-agent =
proxy = system
proxy-dns-requests = true
ssl-strict = ask
dns-prefetch = true
custom-headers =
netrc-file =
[completion]
show = always
download-path-suggestion = path
timestamp-format = %Y-%m-%d
height = 50%
cmd-history-max-items = 100
web-history-max-items = -1
quick-complete = true
shrink = false
scrollbar-width = 12
scrollbar-padding = 2
[input]
timeout = 500
partial-timeout = 5000
insert-mode-on-plugins = false
auto-leave-insert-mode = true
auto-insert-mode = false
forward-unbound-keys = auto
spatial-navigation = false
links-included-in-focus-chain = true
rocker-gestures = false
mouse-zoom-divider = 512
[tabs]
background-tabs = false
select-on-remove = next
new-tab-position = next
new-tab-position-explicit = last
last-close = ignore
show = always
show-switching-delay = 800
wrap = true
movable = true
close-mouse-button = middle
position = top
show-favicons = true
favicon-scale = 1.0
width = 20%
pinned-width = 43
indicator-width = 3
tabs-are-windows = false
title-format = {index}: {title}
title-format-pinned = {index}
title-alignment = left
mousewheel-tab-switching = true
padding = 0,0,5,5
indicator-padding = 2,2,0,4
[storage]
download-directory =
prompt-download-directory = true
remember-download-directory = true
maximum-pages-in-cache = 0
offline-web-application-cache = true
local-storage = true
cache-size =
[content]
allow-images = true
allow-javascript = true
allow-plugins = false
webgl = true
hyperlink-auditing = false
geolocation = ask
notifications = ask
media-capture = ask
javascript-can-open-windows-automatically = false
javascript-can-close-windows = false
javascript-can-access-clipboard = false
ignore-javascript-prompt = false
ignore-javascript-alert = false
local-content-can-access-remote-urls = false
local-content-can-access-file-urls = true
cookies-accept = no-3rdparty
cookies-store = true
host-block-lists = https://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext
host-blocking-enabled = true
host-blocking-whitelist = piwik.org
enable-pdfjs = false
[hints]
border = 1px solid #E3BE23
mode = letter
chars = asdfghjkl
min-chars = 1
scatter = true
uppercase = false
dictionary = /usr/share/dict/words
auto-follow = unique-match
auto-follow-timeout = 0
next-regexes = \\bnext\\b,\\bmore\\b,\\bnewer\\b,\\b[>\u2192\u226b]\\b,\\b(>>|\xbb)\\b,\\bcontinue\\b
prev-regexes = \\bprev(ious)?\\b,\\bback\\b,\\bolder\\b,\\b[<\u2190\u226a]\\b,\\b(<<|\xab)\\b
find-implementation = python
hide-unmatched-rapid-hints = true
[searchengines]
DEFAULT = https://duckduckgo.com/?q={}
[aliases]
[colors]
completion.fg = white
completion.bg = #333333
completion.alternate-bg = #444444
completion.category.fg = white
completion.category.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #888888, stop:1 #505050)
completion.category.border.top = black
completion.category.border.bottom = ${completion.category.border.top}
completion.item.selected.fg = black
completion.item.selected.bg = #e8c000
completion.item.selected.border.top = #bbbb00
completion.item.selected.border.bottom = ${completion.item.selected.border.top}
completion.match.fg = #ff4444
completion.scrollbar.fg = ${completion.fg}
completion.scrollbar.bg = ${completion.bg}
statusbar.fg = white
statusbar.bg = black
statusbar.fg.private = ${statusbar.fg}
statusbar.bg.private = #666666
statusbar.fg.insert = ${statusbar.fg}
statusbar.bg.insert = darkgreen
statusbar.fg.command = ${statusbar.fg}
statusbar.bg.command = ${statusbar.bg}
statusbar.fg.command.private = ${statusbar.fg.private}
statusbar.bg.command.private = ${statusbar.bg.private}
statusbar.fg.caret = ${statusbar.fg}
statusbar.bg.caret = purple
statusbar.fg.caret-selection = ${statusbar.fg}
statusbar.bg.caret-selection = #a12dff
statusbar.progress.bg = white
statusbar.url.fg = ${statusbar.fg}
statusbar.url.fg.success = white
statusbar.url.fg.success.https = lime
statusbar.url.fg.error = orange
statusbar.url.fg.warn = yellow
statusbar.url.fg.hover = aqua
tabs.fg.odd = white
tabs.bg.odd = grey
tabs.fg.even = white
tabs.bg.even = darkgrey
tabs.fg.selected.odd = white
tabs.bg.selected.odd = black
tabs.fg.selected.even = ${tabs.fg.selected.odd}
tabs.bg.selected.even = ${tabs.bg.selected.odd}
tabs.bg.bar = #555555
tabs.indicator.start = #0000aa
tabs.indicator.stop = #00aa00
tabs.indicator.error = #ff0000
tabs.indicator.system = rgb
hints.fg = black
hints.bg = qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 247, 133, 0.8), stop:1 rgba(255, 197, 66, 0.8))
hints.fg.match = green
downloads.bg.bar = black
downloads.fg.start = white
downloads.bg.start = #0000aa
downloads.fg.stop = ${downloads.fg.start}
downloads.bg.stop = #00aa00
downloads.fg.system = rgb
downloads.bg.system = rgb
downloads.fg.error = white
downloads.bg.error = red
webpage.bg = white
keyhint.fg = #FFFFFF
keyhint.fg.suffix = #FFFF00
keyhint.bg = rgba(0, 0, 0, 80%)
messages.fg.error = white
messages.bg.error = red
messages.border.error = #bb0000
messages.fg.warning = white
messages.bg.warning = darkorange
messages.border.warning = #d47300
messages.fg.info = white
messages.bg.info = black
messages.border.info = #333333
prompts.fg = white
prompts.bg = darkblue
prompts.selected.bg = #308cc6
[fonts]
_monospace = xos4 Terminus, Terminus, Monospace, "DejaVu Sans Mono", Monaco, "Bitstream Vera Sans Mono", "Andale Mono", "Courier New", Courier, "Liberation Mono", monospace, Fixed, Consolas, Terminal
completion = 8pt ${_monospace}
completion.category = bold ${completion}
tabbar = 8pt ${_monospace}
statusbar = 8pt ${_monospace}
downloads = 8pt ${_monospace}
hints = bold 13px ${_monospace}
debug-console = 8pt ${_monospace}
web-family-standard =
web-family-fixed =
web-family-serif =
web-family-sans-serif =
web-family-cursive =
web-family-fantasy =
web-size-minimum = 0
web-size-minimum-logical = 6
web-size-default = 16
web-size-default-fixed = 13
keyhint = 8pt ${_monospace}
messages.error = 8pt ${_monospace}
messages.warning = 8pt ${_monospace}
messages.info = 8pt ${_monospace}
prompts = 8pt sans-serif
"""
OLD_KEYS_CONF = """
[!normal]
leave-mode
<escape>
<ctrl-[>
[normal]
clear-keychain ;; search ;; fullscreen --leave
<escape>
<ctrl-[>
set-cmd-text -s :open
o
set-cmd-text :open {url:pretty}
go
set-cmd-text -s :open -t
O
set-cmd-text :open -t -i {url:pretty}
gO
set-cmd-text -s :open -b
xo
set-cmd-text :open -b -i {url:pretty}
xO
set-cmd-text -s :open -w
wo
set-cmd-text :open -w {url:pretty}
wO
set-cmd-text /
/
set-cmd-text ?
?
set-cmd-text :
:
open -t
ga
<ctrl-t>
open -w
<ctrl-n>
tab-close
d
<ctrl-w>
tab-close -o
D
tab-only
co
tab-focus
T
tab-move
gm
tab-move -
gl
tab-move +
gr
tab-next
J
<ctrl-pgdown>
tab-prev
K
<ctrl-pgup>
tab-clone
gC
reload
r
<f5>
reload -f
R
<ctrl-f5>
back
H
<back>
back -t
th
back -w
wh
forward
L
<forward>
forward -t
tl
forward -w
wl
fullscreen
<f11>
hint
f
hint all tab
F
hint all window
wf
hint all tab-bg
;b
hint all tab-fg
;f
hint all hover
;h
hint images
;i
hint images tab
;I
hint links fill :open {hint-url}
;o
hint links fill :open -t -i {hint-url}
;O
hint links yank
;y
hint links yank-primary
;Y
hint --rapid links tab-bg
;r
hint --rapid links window
;R
hint links download
;d
hint inputs
;t
scroll left
h
scroll down
j
scroll up
k
scroll right
l
undo
u
<ctrl-shift-t>
scroll-perc 0
gg
scroll-perc
G
search-next
n
search-prev
N
enter-mode insert
i
enter-mode caret
v
enter-mode set_mark
`
enter-mode jump_mark
'
yank
yy
yank -s
yY
yank title
yt
yank title -s
yT
yank domain
yd
yank domain -s
yD
yank pretty-url
yp
yank pretty-url -s
yP
open -- {clipboard}
pp
open -- {primary}
pP
open -t -- {clipboard}
Pp
open -t -- {primary}
PP
open -w -- {clipboard}
wp
open -w -- {primary}
wP
quickmark-save
m
set-cmd-text -s :quickmark-load
b
set-cmd-text -s :quickmark-load -t
B
set-cmd-text -s :quickmark-load -w
wb
bookmark-add
M
set-cmd-text -s :bookmark-load
gb
set-cmd-text -s :bookmark-load -t
gB
set-cmd-text -s :bookmark-load -w
wB
save
sf
set-cmd-text -s :set
ss
set-cmd-text -s :set -t
sl
set-cmd-text -s :bind
sk
zoom-out
-
zoom-in
+
zoom
=
navigate prev
[[
navigate next
]]
navigate prev -t
{{
navigate next -t
}}
navigate up
gu
navigate up -t
gU
navigate increment
<ctrl-a>
navigate decrement
<ctrl-x>
inspector
wi
download
gd
download-cancel
ad
download-clear
cd
view-source
gf
set-cmd-text -s :buffer
gt
tab-focus last
<ctrl-tab>
<ctrl-6>
<ctrl-^>
enter-mode passthrough
<ctrl-v>
quit
<ctrl-q>
ZQ
wq
ZZ
scroll-page 0 1
<ctrl-f>
scroll-page 0 -1
<ctrl-b>
scroll-page 0 0.5
<ctrl-d>
scroll-page 0 -0.5
<ctrl-u>
tab-focus 1
<alt-1>
g0
g^
tab-focus 2
<alt-2>
tab-focus 3
<alt-3>
tab-focus 4
<alt-4>
tab-focus 5
<alt-5>
tab-focus 6
<alt-6>
tab-focus 7
<alt-7>
tab-focus 8
<alt-8>
tab-focus -1
<alt-9>
g$
home
<ctrl-h>
stop
<ctrl-s>
print
<ctrl-alt-p>
open qute://settings
Ss
follow-selected
<return>
<ctrl-m>
<ctrl-j>
<shift-return>
<enter>
<shift-enter>
follow-selected -t
<ctrl-return>
<ctrl-enter>
repeat-command
.
tab-pin
<ctrl-p>
record-macro
q
run-macro
@
[insert]
open-editor
<ctrl-e>
insert-text {primary}
<shift-ins>
[hint]
follow-hint
<return>
<ctrl-m>
<ctrl-j>
<shift-return>
<enter>
<shift-enter>
hint --rapid links tab-bg
<ctrl-r>
hint links
<ctrl-f>
hint all tab-bg
<ctrl-b>
[passthrough]
[command]
command-history-prev
<ctrl-p>
command-history-next
<ctrl-n>
completion-item-focus prev
<shift-tab>
<up>
completion-item-focus next
<tab>
<down>
completion-item-focus next-category
<ctrl-tab>
completion-item-focus prev-category
<ctrl-shift-tab>
completion-item-del
<ctrl-d>
command-accept
<return>
<ctrl-m>
<ctrl-j>
<shift-return>
<enter>
<shift-enter>
[prompt]
prompt-accept
<return>
<ctrl-m>
<ctrl-j>
<shift-return>
<enter>
<shift-enter>
prompt-accept yes
y
prompt-accept no
n
prompt-open-download
<ctrl-x>
prompt-item-focus prev
<shift-tab>
<up>
prompt-item-focus next
<tab>
<down>
[command,prompt]
rl-backward-char
<ctrl-b>
rl-forward-char
<ctrl-f>
rl-backward-word
<alt-b>
rl-forward-word
<alt-f>
rl-beginning-of-line
<ctrl-a>
rl-end-of-line
<ctrl-e>
rl-unix-line-discard
<ctrl-u>
rl-kill-line
<ctrl-k>
rl-kill-word
<alt-d>
rl-unix-word-rubout
<ctrl-w>
rl-backward-kill-word
<alt-backspace>
rl-yank
<ctrl-y>
rl-delete-char
<ctrl-?>
rl-backward-delete-char
<ctrl-h>
[caret]
toggle-selection
v
<space>
drop-selection
<ctrl-space>
enter-mode normal
c
move-to-next-line
j
move-to-prev-line
k
move-to-next-char
l
move-to-prev-char
h
move-to-end-of-word
e
move-to-next-word
w
move-to-prev-word
b
move-to-start-of-next-block
]
move-to-start-of-prev-block
[
move-to-end-of-next-block
}
move-to-end-of-prev-block
{
move-to-start-of-line
0
move-to-end-of-line
$
move-to-start-of-document
gg
move-to-end-of-document
G
yank selection -s
Y
yank selection
y
<return>
<ctrl-m>
<ctrl-j>
<shift-return>
<enter>
<shift-enter>
scroll left
H
scroll down
J
scroll up
K
scroll right
L
"""
def get_diff():
"""Get a HTML diff for the old config files."""
old_conf_lines = []
old_key_lines = []
for filename, dest in [('qutebrowser.conf', old_conf_lines),
('keys.conf', old_key_lines)]:
path = os.path.join(standarddir.config(), filename)
with open(path, 'r', encoding='utf-8') as f:
for line in f:
if not line.strip() or line.startswith('#'):
continue
dest.append(line.rstrip())
conf_delta = difflib.unified_diff(OLD_CONF.lstrip().splitlines(),
old_conf_lines)
key_delta = difflib.unified_diff(OLD_KEYS_CONF.lstrip().splitlines(),
old_key_lines)
conf_diff = '\n'.join(conf_delta)
key_diff = '\n'.join(key_delta)
# pylint: disable=no-member
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/491/
lexer = pygments.lexers.DiffLexer()
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table',
title='Config diff')
# pylint: enable=no-member
return pygments.highlight(conf_diff + key_diff, lexer, formatter)

View File

@ -19,6 +19,10 @@
"""Exceptions related to config parsing."""
import attr
from qutebrowser.utils import jinja
class Error(Exception):
@ -41,46 +45,82 @@ class ValidationError(Error):
"""Raised when a value for a config type was invalid.
Attributes:
section: Section in which the error occurred (added when catching and
re-raising the exception).
option: Option in which the error occurred.
value: Config value that triggered the error.
msg: Additional error message.
"""
def __init__(self, value, msg):
super().__init__("Invalid value '{}' - {}".format(value, msg))
self.section = None
self.option = None
class NoSectionError(Error):
class KeybindingError(Error):
"""Raised when no section matches a requested option."""
"""Raised for issues with keybindings."""
def __init__(self, section):
super().__init__("Section {!r} does not exist!".format(section))
self.section = section
class DuplicateKeyError(KeybindingError):
"""Raised when there was a duplicate key."""
def __init__(self, key):
super().__init__("Duplicate key {}".format(key))
class NoOptionError(Error):
"""Raised when an option was not found."""
def __init__(self, option, section):
super().__init__("No option {!r} in section {!r}".format(
option, section))
def __init__(self, option):
super().__init__("No option {!r}".format(option))
self.option = option
self.section = section
class InterpolationSyntaxError(Error):
@attr.s
class ConfigErrorDesc:
"""Raised when the source text contains invalid syntax.
"""A description of an error happening while reading the config.
Current implementation raises this exception when the source text into
which substitutions are made does not conform to the required syntax.
Attributes:
text: The text to show.
exception: The exception which happened.
traceback: The formatted traceback of the exception.
"""
def __init__(self, option, section, msg):
super().__init__(msg)
self.option = option
self.section = section
text = attr.ib()
exception = attr.ib()
traceback = attr.ib(None)
def __str__(self):
return '{}: {}'.format(self.text, self.exception)
class ConfigFileErrors(Error):
"""Raised when multiple errors occurred inside the config."""
def __init__(self, basename, errors):
super().__init__("Errors occurred while reading {}:\n{}".format(
basename, '\n'.join(' {}'.format(e) for e in errors)))
self.basename = basename
self.errors = errors
def to_html(self):
"""Get the error texts as a HTML snippet."""
template = jinja.environment.from_string("""
Errors occurred while reading {{ basename }}:
<ul>
{% for error in errors %}
<li>
<b>{{ error.text }}</b>: {{ error.exception }}
{% if error.traceback != none %}
<pre>
""".rstrip() + "\n{{ error.traceback }}" + """
</pre>
{% endif %}
</li>
{% endfor %}
</ul>
""")
return template.render(basename=self.basename, errors=self.errors)

View File

@ -0,0 +1,254 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Configuration files residing on disk."""
import types
import os.path
import textwrap
import traceback
import configparser
import contextlib
import yaml
from PyQt5.QtCore import QSettings
import qutebrowser
from qutebrowser.config import configexc, config
from qutebrowser.utils import standarddir, utils, qtutils
# The StateConfig instance
state = None
class StateConfig(configparser.ConfigParser):
"""The "state" file saving various application state."""
def __init__(self):
super().__init__()
self._filename = os.path.join(standarddir.data(), 'state')
self.read(self._filename, encoding='utf-8')
for sect in ['general', 'geometry']:
try:
self.add_section(sect)
except configparser.DuplicateSectionError:
pass
deleted_keys = ['fooled', 'backend-warning-shown']
for key in deleted_keys:
self['general'].pop(key, None)
def init_save_manager(self, save_manager):
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
the save_manager exists.
"""
save_manager.add_saveable('state-config', self._save)
def _save(self):
"""Save the state file to the configured location."""
with open(self._filename, 'w', encoding='utf-8') as f:
self.write(f)
class YamlConfig:
"""A config stored on disk as YAML file.
Class attributes:
VERSION: The current version number of the config file.
"""
VERSION = 1
def __init__(self):
self._filename = os.path.join(standarddir.config(auto=True),
'autoconfig.yml')
self.values = {}
def init_save_manager(self, save_manager):
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
the save_manager exists.
"""
save_manager.add_saveable('yaml-config', self._save)
def _save(self):
"""Save the changed settings to the YAML file."""
data = {'config_version': self.VERSION, 'global': self.values}
with qtutils.savefile_open(self._filename) as f:
f.write(textwrap.dedent("""
# DO NOT edit this file by hand, qutebrowser will overwrite it.
# Instead, create a config.py - see :help for details.
""".lstrip('\n')))
utils.yaml_dump(data, f)
def load(self):
"""Load self.values from the configured YAML file."""
try:
with open(self._filename, 'r', encoding='utf-8') as f:
yaml_data = utils.yaml_load(f)
except FileNotFoundError:
return
except OSError as e:
desc = configexc.ConfigErrorDesc("While reading", e)
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
except yaml.YAMLError as e:
desc = configexc.ConfigErrorDesc("While parsing", e)
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
try:
global_obj = yaml_data['global']
except KeyError:
desc = configexc.ConfigErrorDesc(
"While loading data",
"Toplevel object does not contain 'global' key")
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
except TypeError:
desc = configexc.ConfigErrorDesc("While loading data",
"Toplevel object is not a dict")
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
if not isinstance(global_obj, dict):
desc = configexc.ConfigErrorDesc(
"While loading data",
"'global' object is not a dict")
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
self.values = global_obj
class ConfigAPI:
"""Object which gets passed to config.py as "config" object.
This is a small wrapper over the Config object, but with more
straightforward method names (get/set call get_obj/set_obj) and a more
shallow API.
Attributes:
_config: The main Config object to use.
_keyconfig: The KeyConfig object.
load_autoconfig: Whether autoconfig.yml should be loaded.
errors: Errors which occurred while setting options.
"""
def __init__(self, conf, keyconfig):
self._config = conf
self._keyconfig = keyconfig
self.load_autoconfig = True
self.errors = []
@contextlib.contextmanager
def _handle_error(self, action, name):
try:
yield
except configexc.Error as e:
text = "While {} '{}'".format(action, name)
self.errors.append(configexc.ConfigErrorDesc(text, e))
def finalize(self):
"""Do work which needs to be done after reading config.py."""
self._config.update_mutables()
def get(self, name):
with self._handle_error('getting', name):
return self._config.get_obj(name)
def set(self, name, value):
with self._handle_error('setting', name):
self._config.set_obj(name, value)
def bind(self, key, command, mode='normal', *, force=False):
with self._handle_error('binding', key):
self._keyconfig.bind(key, command, mode=mode, force=force)
def unbind(self, key, mode='normal'):
with self._handle_error('unbinding', key):
self._keyconfig.unbind(key, mode=mode)
def read_config_py(filename=None):
"""Read a config.py file."""
api = ConfigAPI(config.instance, config.key_instance)
if filename is None:
filename = os.path.join(standarddir.config(), 'config.py')
if not os.path.exists(filename):
return api
container = config.ConfigContainer(config.instance, configapi=api)
basename = os.path.basename(filename)
module = types.ModuleType('config')
module.config = api
module.c = container
module.__file__ = filename
try:
with open(filename, mode='rb') as f:
source = f.read()
except OSError as e:
text = "Error while reading {}".format(basename)
desc = configexc.ConfigErrorDesc(text, e)
raise configexc.ConfigFileErrors(basename, [desc])
try:
code = compile(source, filename, 'exec')
except (ValueError, TypeError) as e:
# source contains NUL bytes
desc = configexc.ConfigErrorDesc("Error while compiling", e)
raise configexc.ConfigFileErrors(basename, [desc])
except SyntaxError as e:
desc = configexc.ConfigErrorDesc("Syntax Error", e,
traceback=traceback.format_exc())
raise configexc.ConfigFileErrors(basename, [desc])
try:
exec(code, module.__dict__)
except Exception as e:
api.errors.append(configexc.ConfigErrorDesc(
"Unhandled exception",
exception=e, traceback=traceback.format_exc()))
api.finalize()
return api
def init():
"""Initialize config storage not related to the main config."""
global state
state = StateConfig()
state['general']['version'] = qutebrowser.__version__
# Set the QSettings path to something like
# ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it
# doesn't overwrite our config.
#
# This fixes one of the corruption issues here:
# https://github.com/qutebrowser/qutebrowser/issues/515
path = os.path.join(standarddir.config(auto=True), 'qsettings')
for fmt in [QSettings.NativeFormat, QSettings.IniFormat]:
QSettings.setPath(fmt, QSettings.UserScope, path)

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