Merge branch 'master' of https://github.com/qutebrowser/qutebrowser into qutebrowser-master
This commit is contained in:
commit
6a997851eb
@ -7,7 +7,7 @@ environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python36\python.exe
|
||||
matrix:
|
||||
- TESTENV: py36-pyqt58
|
||||
- TESTENV: py36-pyqt59
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
|
2
.flake8
2
.flake8
@ -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
9
.github/CONTRIBUTING.asciidoc
vendored
Normal 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
5
.gitignore
vendored
@ -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
|
||||
|
@ -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
|
||||
|
44
.travis.yml
44
.travis.yml
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
--------
|
||||
|
@ -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.
|
@ -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 `<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.
|
@ -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. +
|
@ -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
|
||||
|
223
doc/help/configuring.asciidoc
Normal file
223
doc/help/configuring.asciidoc
Normal 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]!
|
@ -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
@ -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`):
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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#
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -4,3 +4,4 @@ pyPEG2
|
||||
PyYAML
|
||||
colorama
|
||||
cssutils
|
||||
attrs
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -3,7 +3,6 @@ cheroot
|
||||
coverage
|
||||
Flask
|
||||
hunter
|
||||
httpbin
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
|
@ -2,5 +2,5 @@
|
||||
|
||||
pluggy==0.4.0
|
||||
py==1.4.34
|
||||
tox==2.7.0
|
||||
tox==2.8.2
|
||||
virtualenv==15.1.0
|
||||
|
@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==0.21
|
||||
vulture==0.26
|
||||
|
@ -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
47
misc/userscripts/format_json
Executable 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"
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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'))
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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')
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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."""
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
52
qutebrowser/completion/models/util.py
Normal file
52
qutebrowser/completion/models/util.py
Normal 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
2259
qutebrowser/config/configdata.yml
Normal file
2259
qutebrowser/config/configdata.yml
Normal file
File diff suppressed because it is too large
Load Diff
760
qutebrowser/config/configdiff.py
Normal file
760
qutebrowser/config/configdiff.py
Normal 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)
|
@ -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)
|
||||
|
254
qutebrowser/config/configfiles.py
Normal file
254
qutebrowser/config/configfiles.py
Normal 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
Loading…
Reference in New Issue
Block a user