Merge branch 'master' into mutable-dict
This commit is contained in:
commit
cc540bb166
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
|
/qutebrowser/3rdparty
|
||||||
/doc/*.html
|
/doc/*.html
|
||||||
/README.html
|
/README.html
|
||||||
/CHANGELOG.html
|
|
||||||
/CONTRIBUTING.html
|
|
||||||
/FAQ.html
|
|
||||||
/INSTALL.html
|
|
||||||
/qutebrowser/html/doc/
|
/qutebrowser/html/doc/
|
||||||
|
/qutebrowser/html/*.html
|
||||||
/.venv*
|
/.venv*
|
||||||
/.coverage
|
/.coverage
|
||||||
/htmlcov
|
/htmlcov
|
||||||
|
@ -6,18 +6,12 @@ python: 3.6
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
|
||||||
env: DOCKER=debian-jessie
|
|
||||||
services: docker
|
|
||||||
- os: linux
|
- os: linux
|
||||||
env: DOCKER=archlinux
|
env: DOCKER=archlinux
|
||||||
services: docker
|
services: docker
|
||||||
- os: linux
|
- os: linux
|
||||||
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
|
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
|
||||||
services: docker
|
services: docker
|
||||||
- os: linux
|
|
||||||
env: DOCKER=ubuntu-xenial
|
|
||||||
services: docker
|
|
||||||
- os: linux
|
- os: linux
|
||||||
env: TESTENV=py36-pyqt571
|
env: TESTENV=py36-pyqt571
|
||||||
- os: linux
|
- os: linux
|
||||||
|
@ -11,8 +11,8 @@ graft misc/userscripts
|
|||||||
recursive-include scripts *.py *.sh
|
recursive-include scripts *.py *.sh
|
||||||
include qutebrowser/utils/testfile
|
include qutebrowser/utils/testfile
|
||||||
include qutebrowser/git-commit-id
|
include qutebrowser/git-commit-id
|
||||||
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
|
include LICENSE doc/* README.asciidoc
|
||||||
include qutebrowser.desktop
|
include misc/qutebrowser.desktop
|
||||||
include requirements.txt
|
include requirements.txt
|
||||||
include tox.ini
|
include tox.ini
|
||||||
include qutebrowser.py
|
include qutebrowser.py
|
||||||
@ -37,7 +37,6 @@ exclude qutebrowser/javascript/.eslintrc.yaml
|
|||||||
exclude qutebrowser/javascript/.eslintignore
|
exclude qutebrowser/javascript/.eslintignore
|
||||||
exclude doc/help
|
exclude doc/help
|
||||||
exclude .*
|
exclude .*
|
||||||
exclude codecov.yml
|
|
||||||
exclude misc/appveyor_install.py
|
exclude misc/appveyor_install.py
|
||||||
exclude misc/qutebrowser.spec
|
exclude misc/qutebrowser.spec
|
||||||
exclude misc/qutebrowser.nsi
|
exclude misc/qutebrowser.nsi
|
||||||
|
@ -9,7 +9,7 @@ qutebrowser
|
|||||||
// QUTE_WEB_HIDE
|
// QUTE_WEB_HIDE
|
||||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
|
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and 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://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://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"]
|
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
|
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.
|
detailed instructions on how to get qutebrowser running on various platforms.
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
@ -49,11 +49,11 @@ available:
|
|||||||
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"]
|
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]
|
* link:doc/quickstart.asciidoc[Quick start guide]
|
||||||
* A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings.
|
* A https://www.shortcutfoo.com/app/dojos/qutebrowser[free training course] to remember those key bindings.
|
||||||
* link:FAQ.asciidoc[Frequently asked questions]
|
* link:doc/faq.asciidoc[Frequently asked questions]
|
||||||
* link:doc/help/configuring.html[Configuring qutebrowser]
|
* link:doc/help/configuring.html[Configuring qutebrowser]
|
||||||
* link:CONTRIBUTING.asciidoc[Contributing to qutebrowser]
|
* link:doc/contributing.asciidoc[Contributing to qutebrowser]
|
||||||
* link:INSTALL.asciidoc[INSTALL]
|
* link:doc/install.asciidoc[Installing qutebrowser]
|
||||||
* link:CHANGELOG.asciidoc[Change Log]
|
* link:doc/changelog.asciidoc[Change Log]
|
||||||
* link:doc/stacktrace.asciidoc[Reporting segfaults]
|
* link:doc/stacktrace.asciidoc[Reporting segfaults]
|
||||||
* link:doc/userscripts.asciidoc[How to write userscripts]
|
* link:doc/userscripts.asciidoc[How to write userscripts]
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ Contributions / Bugs
|
|||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
You want to contribute to qutebrowser? Awesome! Please read
|
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.
|
useful hints.
|
||||||
|
|
||||||
If you found a bug or have a feature request, you can report it in several
|
If you found a bug or have a feature request, you can report it in several
|
||||||
@ -99,21 +99,18 @@ Requirements
|
|||||||
|
|
||||||
The following software and libraries are required to run qutebrowser:
|
The following software and libraries are required to run qutebrowser:
|
||||||
|
|
||||||
* http://www.python.org/[Python] 3.4 or newer (3.6 recommended) - note that
|
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
||||||
support for Python 3.4
|
* http://qt.io/[Qt] 5.7.1 or newer with the following modules:
|
||||||
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:
|
|
||||||
- QtCore / qtbase
|
- QtCore / qtbase
|
||||||
- QtQuick (part of qtbase in some distributions)
|
- QtQuick (part of qtbase in some distributions)
|
||||||
- QtSQL (part of qtbase in some distributions)
|
- QtSQL (part of qtbase in some distributions)
|
||||||
|
- QtOpenGL
|
||||||
- QtWebEngine, or
|
- QtWebEngine, or
|
||||||
- QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG).
|
- QtWebKit - only the
|
||||||
Note that support for legacy QtWebKit (before 5.212) will be
|
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
|
||||||
dropped soon.
|
supported.
|
||||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
|
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||||
(5.9 recommended) for Python 3. Note that support for PyQt < 5.7 will be
|
(5.9 recommended) for Python 3.
|
||||||
dropped soon.
|
|
||||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||||
* http://fdik.org/pyPEG/[pyPEG2]
|
* http://fdik.org/pyPEG/[pyPEG2]
|
||||||
* http://jinja.pocoo.org/[jinja2]
|
* http://jinja.pocoo.org/[jinja2]
|
||||||
@ -129,8 +126,8 @@ The following libraries are optional:
|
|||||||
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
|
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
|
||||||
command, when using the git repository (rather than a release).
|
command, when using the git repository (rather than a release).
|
||||||
|
|
||||||
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
|
See link:doc/install.asciidoc[the documentation] for directions on how to
|
||||||
and its dependencies.
|
install qutebrowser and its dependencies.
|
||||||
|
|
||||||
Donating
|
Donating
|
||||||
--------
|
--------
|
||||||
|
@ -20,18 +20,24 @@ v1.0.0 (unreleased)
|
|||||||
Breaking changes
|
Breaking changes
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
- Support for legacy QtWebKit (before 5.212 which is distributed
|
- (TODO) Support for legacy QtWebKit (before 5.212 which is distributed
|
||||||
independently from Qt) is dropped.
|
independently from Qt) is dropped.
|
||||||
- Support for Python 3.4 is dropped.
|
- Support for Python 3.4 is dropped.
|
||||||
- Support for Qt before 5.7 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.
|
||||||
|
- (TODO) The QtWebEngine backend is now used by default if available.
|
||||||
- New dependency on the QtSql module and Qt sqlite support.
|
- 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.
|
|
||||||
- New config system which ignores the old config file.
|
- New config system which ignores the old config file.
|
||||||
- The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
|
- The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
|
||||||
that PyQt5.QtOpenGL is still a dependency.
|
that PyQt5.QtOpenGL is still a dependency.
|
||||||
|
- PyQt5.QtOpenGL is now always required, even with QtWebKit.
|
||||||
- Migrating QtWebEngine data written by versions before 2016-11-15 (before
|
- Migrating QtWebEngine data written by versions before 2016-11-15 (before
|
||||||
v0.9.0) is now not supported anymore.
|
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.
|
||||||
|
- Various documentation files got moved to the doc/ subfolder,
|
||||||
|
`qutebrowser.desktop` got moved to misc/.
|
||||||
|
- The `--harfbuzz` commandline argument got dropped
|
||||||
|
|
||||||
Major changes
|
Major changes
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
@ -44,12 +50,15 @@ Added
|
|||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
- New back/forward indicator in the statusbar
|
- 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
|
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 `:download` now uses the page's title as filename.
|
||||||
- Using `:back` or `:forward` with a count now skips intermediate pages.
|
- Using `:back` or `:forward` with a count now skips intermediate pages.
|
||||||
- When there are multiple messages shown, the timeout is increased.
|
- When there are multiple messages shown, the timeout is increased.
|
@ -5,12 +5,6 @@ The Compiler <mail@qutebrowser.org>
|
|||||||
:data-uri:
|
:data-uri:
|
||||||
:toc:
|
: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!
|
I `<3` footnote:[Of course, that says `<3` in HTML.] contributors!
|
||||||
|
|
||||||
This document contains guidelines for contributing to qutebrowser, as well as
|
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:
|
Currently, the following tox environments are available:
|
||||||
|
|
||||||
* Tests using https://www.pytest.org[pytest]:
|
* Tests using https://www.pytest.org[pytest]:
|
||||||
- `py34`: Run pytest for python-3.4.
|
- `py35`, `py36`: Run pytest for python 3.5/3.6 with the system-wide PyQt.
|
||||||
- `py35`: Run pytest for python-3.5.
|
- `py36-pyqt57`, ..., `py36-pyqt59`: Run pytest with the given PyQt version (`py35-*` also works)
|
||||||
- `py34-cov`: Run pytest for python-3.4 with code coverage report.
|
- `py36-pyqt59-cov`: Run with coverage support (other Python/PyQt versions work too)
|
||||||
- `py35-cov`: Run pytest for python-3.5 with code coverage report.
|
|
||||||
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
|
* `flake8`: Run https://pypi.python.org/pypi/flake8[flake8] checks:
|
||||||
https://pypi.python.org/pypi/pyflakes[pyflakes],
|
https://pypi.python.org/pypi/pyflakes[pyflakes],
|
||||||
https://pypi.python.org/pypi/pep8[pep8],
|
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.
|
and shows a graphical representation of what takes how much time.
|
||||||
|
|
||||||
It uses the built-in Python
|
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:
|
the output in four different ways:
|
||||||
|
|
||||||
* Raw profile file (`--profile-tool=none`)
|
* Raw profile file (`--profile-tool=none`)
|
||||||
@ -542,11 +535,11 @@ ____
|
|||||||
Setting up a Windows Development Environment
|
Setting up a Windows Development Environment
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
* Install https://www.python.org/downloads/release/python-344/[Python 3.4]
|
* Install https://www.python.org/downloads/release/python-362/[Python 3.6].
|
||||||
* Install https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/[PyQt 5.5]
|
* Install PyQt via `pip install PyQt5`
|
||||||
* Create a file at `C:\Windows\system32\python3.bat` with the following content:
|
* Create a file at `C:\Windows\system32\python3.bat` with the following content (adjust the path as necessary):
|
||||||
`@C:\Python34\python %*`
|
`@C:\Python36\python %*`
|
||||||
This will make the Python 3.4 interpreter available as `python3`, which is used by various development scripts.
|
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]
|
* 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.
|
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.
|
* Clone your favourite qutebrowser repository.
|
@ -205,26 +205,6 @@ Experiencing freezing on sites like duckduckgo and youtube.::
|
|||||||
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
|
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
|
||||||
for more details.
|
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::
|
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 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. +
|
As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +
|
@ -66,6 +66,9 @@ link:commands.html#unbind[`:unbind`] commands:
|
|||||||
Key chains starting with a comma are ideal for custom bindings, as the comma key
|
Key chains starting with a comma are ideal for custom bindings, as the comma key
|
||||||
will never be used in a default keybinding.
|
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
|
Configuring qutebrowser via config.py
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
@ -105,8 +108,8 @@ accepted values depend on the type of the option. Commonly used are:
|
|||||||
- Booleans: `c.completion.shrink = True`
|
- Booleans: `c.completion.shrink = True`
|
||||||
- Integers: `c.messages.timeout = 5000`
|
- Integers: `c.messages.timeout = 5000`
|
||||||
- Dictionaries:
|
- Dictionaries:
|
||||||
* `c.headers.custom = {'X-Hello': 'World'}` to override any other values in the
|
* `c.headers.custom = {'X-Hello': 'World', 'X-Awesome': 'yes'}` to override
|
||||||
dictionary.
|
any other values in the dictionary.
|
||||||
* `c.aliases['foo'] = ':message-info foo'` to add a single value.
|
* `c.aliases['foo'] = ':message-info foo'` to add a single value.
|
||||||
- Lists:
|
- Lists:
|
||||||
* `c.url.start_pages = ["https://www.qutebrowser.org/"]` to override any
|
* `c.url.start_pages = ["https://www.qutebrowser.org/"]` to override any
|
||||||
@ -159,26 +162,39 @@ To bind a key:
|
|||||||
.config.py:
|
.config.py:
|
||||||
[source,python]
|
[source,python]
|
||||||
----
|
----
|
||||||
config.bind(',v', 'spawn mpv {url}', mode='normal')
|
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:
|
If the key is already bound, `force=True` needs to be given to rebind it:
|
||||||
|
|
||||||
[source,python]
|
[source,python]
|
||||||
----
|
----
|
||||||
config.bind(',v', 'message-info foo', mode='normal', force=True)
|
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):
|
To unbind a key (either a key which has been bound before, or a default binding):
|
||||||
|
|
||||||
[source,python]
|
[source,python]
|
||||||
----
|
----
|
||||||
config.unbind(',v', mode='normal')
|
config.unbind('<Ctrl-v>', mode='normal')
|
||||||
----
|
----
|
||||||
|
|
||||||
Key chains starting with a comma are ideal for custom bindings, as the comma key
|
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.
|
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
|
To suppress loading of any default keybindings, you can set
|
||||||
`c.bindings.default = {}`.
|
`c.bindings.default = {}`.
|
||||||
|
|
||||||
|
@ -7,13 +7,13 @@ Documentation
|
|||||||
The following help pages are currently available:
|
The following help pages are currently available:
|
||||||
|
|
||||||
* link:../quickstart.html[Quick start guide]
|
* link:../quickstart.html[Quick start guide]
|
||||||
* link:../../FAQ.html[Frequently asked questions]
|
* link:../doc.html[Frequently asked questions]
|
||||||
* link:../../CHANGELOG.html[Change Log]
|
* link:../changelog.html[Change Log]
|
||||||
* link:commands.html[Documentation of commands]
|
* link:commands.html[Documentation of commands]
|
||||||
* link:configuring.html[Configuring qutebrowser]
|
* link:configuring.html[Configuring qutebrowser]
|
||||||
* link:settings.html[Documentation of settings]
|
* link:settings.html[Documentation of settings]
|
||||||
* link:../userscripts.html[How to write userscripts]
|
* link:../userscripts.html[How to write userscripts]
|
||||||
* link:../../CONTRIBUTING.html[Contributing to qutebrowser]
|
* link:../contributing.html[Contributing to qutebrowser]
|
||||||
|
|
||||||
Getting help
|
Getting help
|
||||||
------------
|
------------
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|<<aliases,aliases>>|Aliases for commands.
|
|<<aliases,aliases>>|Aliases for commands.
|
||||||
|<<auto_save.interval,auto_save.interval>>|How often (in milliseconds) to auto-save config/cookies/etc.
|
|<<auto_save.interval,auto_save.interval>>|How often (in milliseconds) to auto-save config/cookies/etc.
|
||||||
|<<auto_save.session,auto_save.session>>|Always restore open sites when qutebrowser is reopened.
|
|<<auto_save.session,auto_save.session>>|Always restore open sites when qutebrowser is reopened.
|
||||||
|
|<<backend,backend>>|The backend to use to display websites.
|
||||||
|<<bindings.commands,bindings.commands>>|Keybindings mapping keys to commands in different modes.
|
|<<bindings.commands,bindings.commands>>|Keybindings mapping keys to commands in different modes.
|
||||||
|<<bindings.default,bindings.default>>|Default keybindings. If you want to add bindings, modify `bindings.commands` instead.
|
|<<bindings.default,bindings.default>>|Default keybindings. If you want to add bindings, modify `bindings.commands` instead.
|
||||||
|<<bindings.key_mappings,bindings.key_mappings>>|This setting can be used to map keys to other keys.
|
|<<bindings.key_mappings,bindings.key_mappings>>|This setting can be used to map keys to other keys.
|
||||||
@ -208,6 +209,7 @@
|
|||||||
|<<new_instance_open_target_window,new_instance_open_target_window>>|Which window to choose when opening links as new tabs.
|
|<<new_instance_open_target_window,new_instance_open_target_window>>|Which window to choose when opening links as new tabs.
|
||||||
|<<prompt.filebrowser,prompt.filebrowser>>|Show a filebrowser in upload/download prompts.
|
|<<prompt.filebrowser,prompt.filebrowser>>|Show a filebrowser in upload/download prompts.
|
||||||
|<<prompt.radius,prompt.radius>>|The rounding radius for the edges of prompts.
|
|<<prompt.radius,prompt.radius>>|The rounding radius for the edges of prompts.
|
||||||
|
|<<qt_args,qt_args>>|Additional arguments to pass to Qt, without leading `--`.
|
||||||
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|
||||||
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
||||||
|<<session_default_name,session_default_name>>|The name of the session to save by default.
|
|<<session_default_name,session_default_name>>|The name of the session to save by default.
|
||||||
@ -253,6 +255,7 @@
|
|||||||
[[aliases]]
|
[[aliases]]
|
||||||
=== aliases
|
=== aliases
|
||||||
Aliases for commands.
|
Aliases for commands.
|
||||||
|
The keys of the given dictionary are the aliases, while the values are the commands they map to.
|
||||||
|
|
||||||
Type: <<types,Dict>>
|
Type: <<types,Dict>>
|
||||||
|
|
||||||
@ -283,11 +286,29 @@ Valid values:
|
|||||||
|
|
||||||
Default: empty
|
Default: empty
|
||||||
|
|
||||||
|
[[backend]]
|
||||||
|
=== backend
|
||||||
|
The backend to use to display websites.
|
||||||
|
qutebrowser supports two different web rendering engines / backends, QtWebKit and QtWebEngine.
|
||||||
|
QtWebKit is based on WebKit (similar to Safari). It was discontinued by the Qt project with Qt 5.6, but picked up as a well maintained fork: https://github.com/annulen/webkit/wiki - qutebrowser only supports the fork.
|
||||||
|
QtWebEngine is Qt's official successor to QtWebKit and based on the Chromium project. It's slightly more resource hungry that QtWebKit and has a couple of missing features in qutebrowser, but is generally the preferred choice.
|
||||||
|
|
||||||
|
Type: <<types,String>>
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +auto+: Automatically select either QtWebEngine or QtWebKit
|
||||||
|
* +webkit+: Force QtWebKit
|
||||||
|
* +webengine+: Force QtWebEngine
|
||||||
|
|
||||||
|
Default: +pass:[auto]+
|
||||||
|
|
||||||
[[bindings.commands]]
|
[[bindings.commands]]
|
||||||
=== bindings.commands
|
=== bindings.commands
|
||||||
Keybindings mapping keys to commands in different modes.
|
Keybindings mapping keys to commands in different modes.
|
||||||
This setting is a dictionary containing mode names and dictionaries mapping keys to commands:
|
This setting is a dictionary containing mode names and dictionaries mapping keys to commands:
|
||||||
`{mode: {key: command}}`
|
`{mode: {key: command}}`
|
||||||
|
While it's possible to bind keys by modifying this setting, it's recommended to use the `config.bind()` function or `:bind` command, which makes modifying it easier and checks for more possible mistakes.
|
||||||
If you want to map a key to another key, check the `bindings.key_mappings` setting instead.
|
If you want to map a key to another key, check the `bindings.key_mappings` setting instead.
|
||||||
For special keys (can't be part of a keychain), enclose them in `<`...`>`. For modifiers, you can use either `-` or `+` as delimiters, and these names:
|
For special keys (can't be part of a keychain), enclose them in `<`...`>`. For modifiers, you can use either `-` or `+` as delimiters, and these names:
|
||||||
|
|
||||||
@ -344,7 +365,7 @@ Default: empty
|
|||||||
=== bindings.default
|
=== bindings.default
|
||||||
Default keybindings. If you want to add bindings, modify `bindings.commands` instead.
|
Default keybindings. If you want to add bindings, modify `bindings.commands` instead.
|
||||||
The main purpose of this setting is that you can set it to an empty dictionary if you want to load no default keybindings at all.
|
The main purpose of this setting is that you can set it to an empty dictionary if you want to load no default keybindings at all.
|
||||||
If you want to preserve default bindings (and get new bindings when there is an update), add new bindings to `bindings.commands` (or use `:bind`) and leave this setting alone.
|
If you want to preserve default bindings (and get new bindings when there is an update), use `config.bind()` in `config.py` or the `:bind` command, and leave this setting alone.
|
||||||
|
|
||||||
Type: <<types,Dict>>
|
Type: <<types,Dict>>
|
||||||
|
|
||||||
@ -1693,6 +1714,7 @@ Default: +pass:[true]+
|
|||||||
[[content.javascript.log]]
|
[[content.javascript.log]]
|
||||||
=== content.javascript.log
|
=== content.javascript.log
|
||||||
Log levels to use for JavaScript console logging messages.
|
Log levels to use for JavaScript console logging messages.
|
||||||
|
When a JavaScript message with the level given in the dictionary key is logged, the corresponding dictionary value selects the qutebrowser logger to use.
|
||||||
On QtWebKit, the "unknown" setting is always used.
|
On QtWebKit, the "unknown" setting is always used.
|
||||||
|
|
||||||
Type: <<types,Dict>>
|
Type: <<types,Dict>>
|
||||||
@ -2084,7 +2106,7 @@ Font used for the hints.
|
|||||||
|
|
||||||
Type: <<types,Font>>
|
Type: <<types,Font>>
|
||||||
|
|
||||||
Default: +pass:[bold 13px monospace]+
|
Default: +pass:[bold 10pt monospace]+
|
||||||
|
|
||||||
[[fonts.keyhint]]
|
[[fonts.keyhint]]
|
||||||
=== fonts.keyhint
|
=== fonts.keyhint
|
||||||
@ -2604,6 +2626,15 @@ Type: <<types,Int>>
|
|||||||
|
|
||||||
Default: +pass:[8]+
|
Default: +pass:[8]+
|
||||||
|
|
||||||
|
[[qt_args]]
|
||||||
|
=== qt_args
|
||||||
|
Additional arguments to pass to Qt, without leading `--`.
|
||||||
|
With QtWebEngine, some Chromium arguments (see https://peter.sh/experiments/chromium-command-line-switches/ for a list) will work.
|
||||||
|
|
||||||
|
Type: <<types,List>>
|
||||||
|
|
||||||
|
Default: empty
|
||||||
|
|
||||||
[[scrolling.bar]]
|
[[scrolling.bar]]
|
||||||
=== scrolling.bar
|
=== scrolling.bar
|
||||||
Show a scrollbar.
|
Show a scrollbar.
|
||||||
@ -3002,7 +3033,8 @@ Default:
|
|||||||
[[url.searchengines]]
|
[[url.searchengines]]
|
||||||
=== url.searchengines
|
=== url.searchengines
|
||||||
Definitions of search engines which can be used via the address bar.
|
Definitions of search engines which can be used via the address bar.
|
||||||
The searchengine named `DEFAULT` is used when `url.auto_search` is turned on and something else than a URL was entered to be opened. Other search engines can be used by prepending the search engine name to the search term, e.g. `:open google qutebrowser`. The string `{}` will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs.
|
Maps a searchengine name (such as `DEFAULT`, or `ddg`) to a URL with a `{}` placeholder. The placeholder will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs.
|
||||||
|
The searchengine named `DEFAULT` is used when `url.auto_search` is turned on and something else than a URL was entered to be opened. Other search engines can be used by prepending the search engine name to the search term, e.g. `:open google qutebrowser`.
|
||||||
|
|
||||||
Type: <<types,Dict>>
|
Type: <<types,Dict>>
|
||||||
|
|
||||||
|
@ -3,39 +3,50 @@ Installing qutebrowser
|
|||||||
|
|
||||||
toc::[]
|
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
|
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
|
Debian Jessie / Ubuntu 14.04 LTS / Linux Mint < 18
|
||||||
* Ubuntu Trusty (14.04 LTS) or newer
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
* Any other distribution based on these (e.g. Linux Mint 17+)
|
|
||||||
|
|
||||||
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is
|
Those distributions only have Python 3.4 and a too old Qt version available,
|
||||||
still relatively easy!
|
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>>
|
If you get qutebrowser running on those distributions, please
|
||||||
instead in order to be able to use the new QtWebEngine backend. Newer versions
|
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[contribute]
|
||||||
have a QtWebEngine package in the repositories.
|
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:
|
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
|
Get the qutebrowser package from the
|
||||||
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
|
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
|
||||||
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
|
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
|
# dpkg -i qutebrowser_*_all.deb
|
||||||
----
|
----
|
||||||
|
|
||||||
Build it from git
|
Some additional hints:
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Install the dependencies via apt-get:
|
|
||||||
|
|
||||||
|
- 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
|
# apt-get install --no-install-recommends asciidoc source-highlight
|
||||||
----
|
|
||||||
|
|
||||||
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
|
|
||||||
$ python3 scripts/asciidoc2html.py
|
$ 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}
|
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
|
||||||
----
|
----
|
||||||
|
|
||||||
Then <<tox,install qutebrowser via tox>>.
|
|
||||||
|
|
||||||
On Fedora
|
On Fedora
|
||||||
---------
|
---------
|
||||||
|
|
||||||
@ -116,7 +119,7 @@ $ rm -r qutebrowser-git
|
|||||||
|
|
||||||
or you could use an AUR helper, e.g. `yaourt -S 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
|
# 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
|
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:
|
A version of qutebrowser is available in the main repository and can be installed with:
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -161,15 +166,11 @@ To update to the last Live version, remember to do
|
|||||||
|
|
||||||
To include qutebrowser among the updates.
|
To include qutebrowser among the updates.
|
||||||
|
|
||||||
Make sure you have `python3_4` in your `PYTHON_TARGETS`
|
You'll also need to install `dev-qt/qtwebengine` or a newer QtWebKit using
|
||||||
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
|
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
|
||||||
necessary.
|
|
||||||
|
|
||||||
It's also recommended to install QtWebKit-NG via
|
If video or sound don't work with QtWebKit, try installing the gstreamer
|
||||||
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild],
|
plugins:
|
||||||
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:
|
|
||||||
|
|
||||||
----
|
----
|
||||||
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
|
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
|
||||||
@ -214,6 +215,8 @@ On openSUSE
|
|||||||
|
|
||||||
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
|
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
|
||||||
|
|
||||||
|
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
||||||
|
|
||||||
On OpenBSD
|
On OpenBSD
|
||||||
----------
|
----------
|
||||||
|
|
||||||
@ -328,6 +331,9 @@ it as part of the packaging process.
|
|||||||
Installing qutebrowser with tox
|
Installing qutebrowser with tox
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
|
Getting the repository
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
First of all, clone the repository using http://git-scm.org/[git] and switch
|
First of all, clone the repository using http://git-scm.org/[git] and switch
|
||||||
into the repository folder:
|
into the repository folder:
|
||||||
|
|
||||||
@ -336,6 +342,8 @@ $ git clone https://github.com/qutebrowser/qutebrowser.git
|
|||||||
$ cd qutebrowser
|
$ cd qutebrowser
|
||||||
----
|
----
|
||||||
|
|
||||||
|
Installing depdendencies (including Qt)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Then run tox inside the qutebrowser repository to set up a
|
Then run tox inside the qutebrowser repository to set up a
|
||||||
https://docs.python.org/3/library/venv.html[virtual environment]:
|
https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||||
@ -344,20 +352,39 @@ https://docs.python.org/3/library/venv.html[virtual environment]:
|
|||||||
$ tox -e mkvenv-pypi
|
$ tox -e mkvenv-pypi
|
||||||
----
|
----
|
||||||
|
|
||||||
If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux), you'll
|
This installs all needed Python dependencies in a `.venv` subfolder.
|
||||||
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
|
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few
|
||||||
qutebrowser.
|
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
|
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
|
||||||
local Qt install instead of installing PyQt in the virtualenv. However, unless
|
local Qt install instead of installing PyQt in the virtualenv. However, unless
|
||||||
you have QtWebKit-NG or QtWebEngine available, qutebrowser will use the legacy
|
you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It
|
||||||
QtWebKit backend.
|
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
|
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
||||||
Python3 is in your PATH before running tox.
|
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
|
You can then create a simple wrapper script to start qutebrowser somewhere in
|
||||||
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
|
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
|
@ -84,9 +84,6 @@ show it.
|
|||||||
*--force-color*::
|
*--force-color*::
|
||||||
Force colored logging
|
Force colored logging
|
||||||
|
|
||||||
*--harfbuzz* '{old,new,system,auto}'::
|
|
||||||
HarfBuzz engine version to use. Default: auto.
|
|
||||||
|
|
||||||
*--relaxed-config*::
|
*--relaxed-config*::
|
||||||
Silently remove unknown config options.
|
Silently remove unknown config options.
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ SetCompressor /solid lzma
|
|||||||
!define MUI_ICON "../icons/qutebrowser.ico"
|
!define MUI_ICON "../icons/qutebrowser.ico"
|
||||||
!define MUI_UNICON "../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_DIRECTORY
|
||||||
!insertmacro MUI_PAGE_INSTFILES
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
!insertmacro MUI_UNPAGE_CONFIRM
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
@ -20,4 +20,4 @@ pycodestyle==2.3.1
|
|||||||
pydocstyle==1.1.1 # rq.filter: < 2.0.0
|
pydocstyle==1.1.1 # rq.filter: < 2.0.0
|
||||||
pyflakes==1.6.0
|
pyflakes==1.6.0
|
||||||
pyparsing==2.2.0
|
pyparsing==2.2.0
|
||||||
six==1.10.0
|
six==1.11.0
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
|
altgraph==0.14
|
||||||
|
future==0.16.0
|
||||||
|
macholib==1.8
|
||||||
|
pefile==2017.9.3
|
||||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||||
|
@ -11,7 +11,7 @@ mccabe==0.6.1
|
|||||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
six==1.10.0
|
six==1.11.0
|
||||||
uritemplate==3.0.0
|
uritemplate==3.0.0
|
||||||
uritemplate.py==3.0.2
|
uritemplate.py==3.0.2
|
||||||
urllib3==1.22
|
urllib3==1.22
|
||||||
|
@ -11,7 +11,7 @@ mccabe==0.6.1
|
|||||||
pylint==1.7.2
|
pylint==1.7.2
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
six==1.10.0
|
six==1.11.0
|
||||||
uritemplate==3.0.0
|
uritemplate==3.0.0
|
||||||
uritemplate.py==3.0.2
|
uritemplate.py==3.0.2
|
||||||
urllib3==1.22
|
urllib3==1.22
|
||||||
|
@ -4,7 +4,6 @@ hg+https://bitbucket.org/ned/coveragepy
|
|||||||
git+https://github.com/micheles/decorator.git
|
git+https://github.com/micheles/decorator.git
|
||||||
git+https://github.com/pallets/flask.git
|
git+https://github.com/pallets/flask.git
|
||||||
git+https://github.com/miracle2k/python-glob2.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/HypothesisWorks/hypothesis-python.git
|
||||||
git+https://github.com/pallets/itsdangerous.git
|
git+https://github.com/pallets/itsdangerous.git
|
||||||
git+https://bitbucket.org/zzzeek/mako.git
|
git+https://bitbucket.org/zzzeek/mako.git
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
|
attrs==17.2.0
|
||||||
beautifulsoup4==4.6.0
|
beautifulsoup4==4.6.0
|
||||||
cheroot==5.8.3
|
cheroot==5.8.3
|
||||||
click==6.7
|
click==6.7
|
||||||
# colorama==0.3.9
|
# colorama==0.3.9
|
||||||
coverage==4.4.1
|
coverage==4.4.1
|
||||||
decorator==4.1.2
|
|
||||||
EasyProcess==0.2.3
|
EasyProcess==0.2.3
|
||||||
fields==5.0.0
|
fields==5.0.0
|
||||||
Flask==0.12.2
|
Flask==0.12.2
|
||||||
glob2==0.6
|
glob2==0.6
|
||||||
httpbin==0.5.0
|
hunter==2.0.1
|
||||||
hunter==1.4.1
|
hypothesis==3.28.3
|
||||||
hypothesis==3.19.1
|
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
# Jinja2==2.9.6
|
# Jinja2==2.9.6
|
||||||
Mako==1.0.7
|
Mako==1.0.7
|
||||||
@ -20,20 +19,21 @@ Mako==1.0.7
|
|||||||
parse==1.8.2
|
parse==1.8.2
|
||||||
parse-type==0.3.4
|
parse-type==0.3.4
|
||||||
py==1.4.34
|
py==1.4.34
|
||||||
pytest==3.2.1
|
py-cpuinfo==3.3.0
|
||||||
|
pytest==3.2.2
|
||||||
pytest-bdd==2.18.2
|
pytest-bdd==2.18.2
|
||||||
pytest-benchmark==3.1.1
|
pytest-benchmark==3.1.1
|
||||||
pytest-catchlog==1.2.2
|
pytest-catchlog==1.2.2
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
pytest-faulthandler==1.3.1
|
pytest-faulthandler==1.3.1
|
||||||
pytest-instafail==0.3.0
|
pytest-instafail==0.3.0
|
||||||
pytest-mock==1.6.2
|
pytest-mock==1.6.3
|
||||||
pytest-qt==2.1.2
|
pytest-qt==2.2.0
|
||||||
pytest-repeat==0.4.1
|
pytest-repeat==0.4.1
|
||||||
pytest-rerunfailures==3.0
|
pytest-rerunfailures==3.1
|
||||||
pytest-travis-fold==1.2.0
|
pytest-travis-fold==1.2.0
|
||||||
pytest-xvfb==1.0.0
|
pytest-xvfb==1.0.0
|
||||||
PyVirtualDisplay==0.2.1
|
PyVirtualDisplay==0.2.1
|
||||||
six==1.10.0
|
six==1.11.0
|
||||||
vulture==0.25
|
vulture==0.26
|
||||||
Werkzeug==0.12.2
|
Werkzeug==0.12.2
|
||||||
|
@ -3,7 +3,6 @@ cheroot
|
|||||||
coverage
|
coverage
|
||||||
Flask
|
Flask
|
||||||
hunter
|
hunter
|
||||||
httpbin
|
|
||||||
hypothesis
|
hypothesis
|
||||||
pytest
|
pytest
|
||||||
pytest-bdd
|
pytest-bdd
|
||||||
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
pluggy==0.4.0
|
pluggy==0.4.0
|
||||||
py==1.4.34
|
py==1.4.34
|
||||||
tox==2.7.0
|
tox==2.8.2
|
||||||
virtualenv==15.1.0
|
virtualenv==15.1.0
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
vulture==0.25
|
vulture==0.26
|
||||||
|
@ -19,15 +19,12 @@ markers =
|
|||||||
qtwebengine_todo: Features still missing with QtWebEngine
|
qtwebengine_todo: Features still missing with QtWebEngine
|
||||||
qtwebengine_skip: Tests not applicable with QtWebEngine
|
qtwebengine_skip: Tests not applicable with QtWebEngine
|
||||||
qtwebkit_skip: Tests not applicable with QtWebKit
|
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_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
|
||||||
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
|
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
|
||||||
js_prompt: Tests needing to display a javascript prompt
|
js_prompt: Tests needing to display a javascript prompt
|
||||||
this: Used to mark tests during development
|
this: Used to mark tests during development
|
||||||
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
no_invalid_lines: Don't fail on unparseable lines in end2end tests
|
||||||
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
|
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
|
||||||
qt55: Tests only running on Qt 5.5 or later
|
|
||||||
qt_log_level_fail = WARNING
|
qt_log_level_fail = WARNING
|
||||||
qt_log_ignore =
|
qt_log_ignore =
|
||||||
^SpellCheck: .*
|
^SpellCheck: .*
|
||||||
|
@ -43,7 +43,7 @@ import qutebrowser
|
|||||||
import qutebrowser.resources
|
import qutebrowser.resources
|
||||||
from qutebrowser.completion.models import miscmodels
|
from qutebrowser.completion.models import miscmodels
|
||||||
from qutebrowser.commands import cmdutils, runners, cmdexc
|
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||||
from qutebrowser.config import config, websettings, configexc
|
from qutebrowser.config import config, websettings, configexc, configfiles
|
||||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||||
downloads)
|
downloads)
|
||||||
from qutebrowser.browser.network import proxy
|
from qutebrowser.browser.network import proxy
|
||||||
@ -52,11 +52,14 @@ from qutebrowser.browser.webkit.network import networkmanager
|
|||||||
from qutebrowser.keyinput import macros
|
from qutebrowser.keyinput import macros
|
||||||
from qutebrowser.mainwindow import mainwindow, prompt
|
from qutebrowser.mainwindow import mainwindow, prompt
|
||||||
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
|
||||||
crashsignal, earlyinit, objects, sql, cmdhistory)
|
crashsignal, earlyinit, sql, cmdhistory)
|
||||||
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
|
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
|
||||||
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
|
usertypes, standarddir, error)
|
||||||
objreg, usertypes, standarddir, error)
|
# pylint: disable=unused-import
|
||||||
# We import utilcmds to run the cmdutils.register decorators.
|
# 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
|
qApp = None
|
||||||
@ -73,6 +76,9 @@ def run(args):
|
|||||||
log.init.debug("Initializing directories...")
|
log.init.debug("Initializing directories...")
|
||||||
standarddir.init(args)
|
standarddir.init(args)
|
||||||
|
|
||||||
|
log.init.debug("Initializing config...")
|
||||||
|
config.early_init(args)
|
||||||
|
|
||||||
global qApp
|
global qApp
|
||||||
qApp = Application(args)
|
qApp = Application(args)
|
||||||
qApp.setOrganizationName("qutebrowser")
|
qApp.setOrganizationName("qutebrowser")
|
||||||
@ -210,13 +216,12 @@ def _load_session(name):
|
|||||||
Args:
|
Args:
|
||||||
name: The name of the session to load, or None to read state file.
|
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')
|
session_manager = objreg.get('session-manager')
|
||||||
if name is None and session_manager.exists('_autosave'):
|
if name is None and session_manager.exists('_autosave'):
|
||||||
name = '_autosave'
|
name = '_autosave'
|
||||||
elif name is None:
|
elif name is None:
|
||||||
try:
|
try:
|
||||||
name = state_config['general']['session']
|
name = configfiles.state['general']['session']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# No session given as argument and none in the session file ->
|
# No session given as argument and none in the session file ->
|
||||||
# start without loading a session
|
# start without loading a session
|
||||||
@ -229,7 +234,7 @@ def _load_session(name):
|
|||||||
except sessions.SessionError as e:
|
except sessions.SessionError as e:
|
||||||
message.error("Failed to load session {}: {}".format(name, e))
|
message.error("Failed to load session {}: {}".format(name, e))
|
||||||
try:
|
try:
|
||||||
del state_config['general']['session']
|
del configfiles.state['general']['session']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
# If this was a _restart session, delete it.
|
# If this was a _restart session, delete it.
|
||||||
@ -323,22 +328,10 @@ def _open_special_pages(args):
|
|||||||
# With --basedir given, don't open anything.
|
# With --basedir given, don't open anything.
|
||||||
return
|
return
|
||||||
|
|
||||||
state_config = objreg.get('state-config')
|
general_sect = configfiles.state['general']
|
||||||
general_sect = state_config['general']
|
|
||||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
window='last-focused')
|
window='last-focused')
|
||||||
|
|
||||||
# Legacy QtWebKit warning
|
|
||||||
|
|
||||||
needs_warning = (objects.backend == usertypes.Backend.QtWebKit and
|
|
||||||
not qtutils.is_qtwebkit_ng())
|
|
||||||
warning_shown = general_sect.get('backend-warning-shown') == '1'
|
|
||||||
|
|
||||||
if not warning_shown and needs_warning:
|
|
||||||
tabbed_browser.tabopen(QUrl('qute://backend-warning'),
|
|
||||||
background=False)
|
|
||||||
general_sect['backend-warning-shown'] = '1'
|
|
||||||
|
|
||||||
# Quickstart page
|
# Quickstart page
|
||||||
|
|
||||||
quickstart_done = general_sect.get('quickstart-done') == '1'
|
quickstart_done = general_sect.get('quickstart-done') == '1'
|
||||||
@ -360,13 +353,6 @@ def _open_special_pages(args):
|
|||||||
general_sect['config-migration-shown'] = '1'
|
general_sect['config-migration-shown'] = '1'
|
||||||
|
|
||||||
|
|
||||||
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__
|
|
||||||
|
|
||||||
|
|
||||||
def on_focus_changed(_old, new):
|
def on_focus_changed(_old, new):
|
||||||
"""Register currently focused main window in the object registry."""
|
"""Register currently focused main window in the object registry."""
|
||||||
if new is None:
|
if new is None:
|
||||||
@ -407,7 +393,7 @@ def _init_modules(args, crash_handler):
|
|||||||
log.init.debug("Initializing save manager...")
|
log.init.debug("Initializing save manager...")
|
||||||
save_manager = savemanager.SaveManager(qApp)
|
save_manager = savemanager.SaveManager(qApp)
|
||||||
objreg.register('save-manager', save_manager)
|
objreg.register('save-manager', save_manager)
|
||||||
save_manager.add_saveable('version', _save_version)
|
config.late_init(save_manager)
|
||||||
|
|
||||||
log.init.debug("Initializing network...")
|
log.init.debug("Initializing network...")
|
||||||
networkmanager.init()
|
networkmanager.init()
|
||||||
@ -419,10 +405,6 @@ def _init_modules(args, crash_handler):
|
|||||||
readline_bridge = readline.ReadlineBridge()
|
readline_bridge = readline.ReadlineBridge()
|
||||||
objreg.register('readline-bridge', readline_bridge)
|
objreg.register('readline-bridge', readline_bridge)
|
||||||
|
|
||||||
log.init.debug("Initializing config...")
|
|
||||||
config.init(qApp)
|
|
||||||
save_manager.init_autosave()
|
|
||||||
|
|
||||||
log.init.debug("Initializing sql...")
|
log.init.debug("Initializing sql...")
|
||||||
try:
|
try:
|
||||||
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
|
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
|
||||||
@ -780,7 +762,7 @@ class Application(QApplication):
|
|||||||
"""
|
"""
|
||||||
self._last_focus_object = None
|
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))
|
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
|
||||||
super().__init__(qt_args)
|
super().__init__(qt_args)
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import sys
|
|||||||
import os.path
|
import os.path
|
||||||
import shlex
|
import shlex
|
||||||
import functools
|
import functools
|
||||||
|
import typing
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
|
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
|
||||||
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
|
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
|
||||||
@ -39,10 +40,11 @@ from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
|
|||||||
webelem, downloads)
|
webelem, downloads)
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
|
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
|
||||||
objreg, utils, typing, debug)
|
objreg, utils, debug)
|
||||||
from qutebrowser.utils.usertypes import KeyMode
|
from qutebrowser.utils.usertypes import KeyMode
|
||||||
from qutebrowser.misc import editor, guiprocess
|
from qutebrowser.misc import editor, guiprocess
|
||||||
from qutebrowser.completion.models import urlmodel, miscmodels
|
from qutebrowser.completion.models import urlmodel, miscmodels
|
||||||
|
from qutebrowser.mainwindow import mainwindow
|
||||||
|
|
||||||
|
|
||||||
class CommandDispatcher:
|
class CommandDispatcher:
|
||||||
@ -70,7 +72,6 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
def _new_tabbed_browser(self, private):
|
def _new_tabbed_browser(self, private):
|
||||||
"""Get a tabbed-browser from a new window."""
|
"""Get a tabbed-browser from a new window."""
|
||||||
from qutebrowser.mainwindow import mainwindow
|
|
||||||
new_window = mainwindow.MainWindow(private=private)
|
new_window = mainwindow.MainWindow(private=private)
|
||||||
new_window.show()
|
new_window.show()
|
||||||
return new_window.tabbed_browser
|
return new_window.tabbed_browser
|
||||||
|
@ -24,7 +24,8 @@ import binascii
|
|||||||
|
|
||||||
from PyQt5.QtWidgets import QWidget
|
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
|
from qutebrowser.misc import miscwidgets, objects
|
||||||
|
|
||||||
|
|
||||||
@ -67,9 +68,8 @@ class AbstractWebInspector(QWidget):
|
|||||||
|
|
||||||
def _load_state_geometry(self):
|
def _load_state_geometry(self):
|
||||||
"""Load the geometry from the state file."""
|
"""Load the geometry from the state file."""
|
||||||
state_config = objreg.get('state-config')
|
|
||||||
try:
|
try:
|
||||||
data = state_config['geometry']['inspector']
|
data = configfiles.state['geometry']['inspector']
|
||||||
geom = base64.b64decode(data, validate=True)
|
geom = base64.b64decode(data, validate=True)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# First start
|
# First start
|
||||||
@ -84,10 +84,9 @@ class AbstractWebInspector(QWidget):
|
|||||||
|
|
||||||
def closeEvent(self, e):
|
def closeEvent(self, e):
|
||||||
"""Save the geometry when closed."""
|
"""Save the geometry when closed."""
|
||||||
state_config = objreg.get('state-config')
|
|
||||||
data = bytes(self.saveGeometry())
|
data = bytes(self.saveGeometry())
|
||||||
geom = base64.b64encode(data).decode('ASCII')
|
geom = base64.b64encode(data).decode('ASCII')
|
||||||
state_config['geometry']['inspector'] = geom
|
configfiles.state['geometry']['inspector'] = geom
|
||||||
super().closeEvent(e)
|
super().closeEvent(e)
|
||||||
|
|
||||||
def inspect(self, page):
|
def inspect(self, page):
|
||||||
|
@ -24,6 +24,7 @@ import posixpath
|
|||||||
from qutebrowser.browser import webelem
|
from qutebrowser.browser import webelem
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
|
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
|
||||||
|
from qutebrowser.mainwindow import mainwindow
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
@ -134,7 +135,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
|
|||||||
window=win_id)
|
window=win_id)
|
||||||
|
|
||||||
if window:
|
if window:
|
||||||
from qutebrowser.mainwindow import mainwindow
|
|
||||||
new_window = mainwindow.MainWindow(
|
new_window = mainwindow.MainWindow(
|
||||||
private=cur_tabbed_browser.private)
|
private=cur_tabbed_browser.private)
|
||||||
new_window.show()
|
new_window.show()
|
||||||
|
@ -28,7 +28,6 @@ import json
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import datetime
|
|
||||||
import textwrap
|
import textwrap
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ from PyQt5.QtCore import QUrlQuery, QUrl
|
|||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.config import config, configdata, configexc, configdiff
|
from qutebrowser.config import config, configdata, configexc, configdiff
|
||||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||||
objreg, usertypes, qtutils)
|
objreg)
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
|
|
||||||
|
|
||||||
@ -224,50 +223,13 @@ def qute_history(url):
|
|||||||
|
|
||||||
return 'text/html', json.dumps(history_data(start_time, offset))
|
return 'text/html', json.dumps(history_data(start_time, offset))
|
||||||
else:
|
else:
|
||||||
if (
|
if not config.val.content.javascript.enabled:
|
||||||
config.val.content.javascript.enabled and
|
return 'text/plain', b'JavaScript is required for qute://history'
|
||||||
(objects.backend == usertypes.Backend.QtWebEngine or
|
return 'text/html', jinja.render(
|
||||||
qtutils.is_qtwebkit_ng())
|
'history.html',
|
||||||
):
|
title='History',
|
||||||
return 'text/html', jinja.render(
|
gap_interval=config.val.history_gap_interval
|
||||||
'history.html',
|
)
|
||||||
title='History',
|
|
||||||
gap_interval=config.val.history_gap_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(),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@add_handler('javascript')
|
@add_handler('javascript')
|
||||||
@ -345,7 +307,7 @@ def qute_log(url):
|
|||||||
@add_handler('gpl')
|
@add_handler('gpl')
|
||||||
def qute_gpl(_url):
|
def qute_gpl(_url):
|
||||||
"""Handler for qute://gpl. Return HTML content as string."""
|
"""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')
|
@add_handler('help')
|
||||||
|
@ -23,6 +23,7 @@ import html
|
|||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
||||||
|
from qutebrowser.mainwindow import mainwindow
|
||||||
|
|
||||||
|
|
||||||
class CallSuper(Exception):
|
class CallSuper(Exception):
|
||||||
@ -234,7 +235,6 @@ def get_tab(win_id, target):
|
|||||||
elif target == usertypes.ClickTarget.window:
|
elif target == usertypes.ClickTarget.window:
|
||||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
window=win_id)
|
window=win_id)
|
||||||
from qutebrowser.mainwindow import mainwindow
|
|
||||||
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
||||||
window.show()
|
window.show()
|
||||||
win_id = window.win_id
|
win_id = window.win_id
|
||||||
|
@ -31,6 +31,7 @@ from PyQt5.QtGui import QMouseEvent
|
|||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
|
from qutebrowser.mainwindow import mainwindow
|
||||||
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
|
||||||
|
|
||||||
|
|
||||||
@ -372,7 +373,6 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
|||||||
background = click_target == usertypes.ClickTarget.tab_bg
|
background = click_target == usertypes.ClickTarget.tab_bg
|
||||||
tabbed_browser.tabopen(url, background=background)
|
tabbed_browser.tabopen(url, background=background)
|
||||||
elif click_target == usertypes.ClickTarget.window:
|
elif click_target == usertypes.ClickTarget.window:
|
||||||
from qutebrowser.mainwindow import mainwindow
|
|
||||||
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
window = mainwindow.MainWindow(private=tabbed_browser.private)
|
||||||
window.show()
|
window.show()
|
||||||
window.tabbed_browser.tabopen(url)
|
window.tabbed_browser.tabopen(url)
|
||||||
|
@ -49,7 +49,6 @@ class DiskCache(QNetworkDiskCache):
|
|||||||
if size is None:
|
if size is None:
|
||||||
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
|
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
|
||||||
if (qtutils.version_check('5.7.1') and
|
if not qtutils.version_check('5.9'): # pragma: no cover
|
||||||
not qtutils.version_check('5.9')): # pragma: no cover
|
|
||||||
size = 0
|
size = 0
|
||||||
self.setMaximumCacheSize(size)
|
self.setMaximumCacheSize(size)
|
||||||
|
@ -38,12 +38,7 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
|||||||
string=str(self))
|
string=str(self))
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
try:
|
return hash(self._error)
|
||||||
# Qt >= 5.4
|
|
||||||
return hash(self._error)
|
|
||||||
except TypeError:
|
|
||||||
return hash((self._error.certificate().toDer(),
|
|
||||||
self._error.error()))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self._error == other._error # pylint: disable=protected-access
|
return self._error == other._error # pylint: disable=protected-access
|
||||||
|
@ -24,13 +24,12 @@ import collections
|
|||||||
import netrc
|
import netrc
|
||||||
import html
|
import html
|
||||||
|
|
||||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
|
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
|
||||||
QUrl, QByteArray)
|
QByteArray)
|
||||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
|
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
|
from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
|
||||||
urlutils)
|
|
||||||
from qutebrowser.browser import shared
|
from qutebrowser.browser import shared
|
||||||
from qutebrowser.browser.webkit import certificateerror
|
from qutebrowser.browser.webkit import certificateerror
|
||||||
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
|
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
|
||||||
@ -88,15 +87,9 @@ def _is_secure_cipher(cipher):
|
|||||||
|
|
||||||
def init():
|
def init():
|
||||||
"""Disable insecure SSL ciphers on old Qt versions."""
|
"""Disable insecure SSL ciphers on old Qt versions."""
|
||||||
if qtutils.version_check('5.3.0'):
|
default_ciphers = QSslSocket.defaultCiphers()
|
||||||
default_ciphers = QSslSocket.defaultCiphers()
|
log.init.debug("Default Qt ciphers: {}".format(
|
||||||
log.init.debug("Default Qt ciphers: {}".format(
|
', '.join(c.name() for c in default_ciphers)))
|
||||||
', '.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)))
|
|
||||||
|
|
||||||
good_ciphers = []
|
good_ciphers = []
|
||||||
bad_ciphers = []
|
bad_ciphers = []
|
||||||
@ -409,24 +402,11 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
tab = objreg.get('tab', scope='tab', window=self._win_id,
|
tab = objreg.get('tab', scope='tab', window=self._win_id,
|
||||||
tab=self._tab_id)
|
tab=self._tab_id)
|
||||||
current_url = tab.url()
|
current_url = tab.url()
|
||||||
except (KeyError, RuntimeError, TypeError):
|
except (KeyError, RuntimeError):
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/889
|
# https://github.com/qutebrowser/qutebrowser/issues/889
|
||||||
# Catching RuntimeError and TypeError because we could be in
|
# Catching RuntimeError because we could be in the middle of
|
||||||
# the middle of the webpage shutdown here.
|
# the webpage shutdown here.
|
||||||
current_url = QUrl()
|
current_url = QUrl()
|
||||||
|
|
||||||
self.set_referer(req, current_url)
|
self.set_referer(req, current_url)
|
||||||
|
return super().createRequest(op, req, outgoing_data)
|
||||||
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
|
|
||||||
|
@ -25,13 +25,7 @@ from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
|
|||||||
from qutebrowser.utils import qtutils
|
from qutebrowser.utils import qtutils
|
||||||
|
|
||||||
|
|
||||||
def _encode_url(url):
|
def _serialize_items(items, current_idx, stream):
|
||||||
"""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):
|
|
||||||
# {'currentItemIndex': 0,
|
# {'currentItemIndex': 0,
|
||||||
# 'history': [{'children': [],
|
# 'history': [{'children': [],
|
||||||
# 'documentSequenceNumber': 1485030525573123,
|
# 'documentSequenceNumber': 1485030525573123,
|
||||||
@ -47,13 +41,13 @@ def _serialize_ng(items, current_idx, stream):
|
|||||||
# 'urlString': 'about:blank'}]}
|
# 'urlString': 'about:blank'}]}
|
||||||
data = {'currentItemIndex': current_idx, 'history': []}
|
data = {'currentItemIndex': current_idx, 'history': []}
|
||||||
for item in items:
|
for item in items:
|
||||||
data['history'].append(_serialize_item_ng(item))
|
data['history'].append(_serialize_item(item))
|
||||||
|
|
||||||
stream.writeInt(3) # history stream version
|
stream.writeInt(3) # history stream version
|
||||||
stream.writeQVariantMap(data)
|
stream.writeQVariantMap(data)
|
||||||
|
|
||||||
|
|
||||||
def _serialize_item_ng(item):
|
def _serialize_item(item):
|
||||||
data = {
|
data = {
|
||||||
'originalURLString': item.original_url.toString(QUrl.FullyEncoded),
|
'originalURLString': item.original_url.toString(QUrl.FullyEncoded),
|
||||||
'scrollPosition': {'x': 0, 'y': 0},
|
'scrollPosition': {'x': 0, 'y': 0},
|
||||||
@ -68,82 +62,6 @@ def _serialize_item_ng(item):
|
|||||||
return data
|
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):
|
def serialize(items):
|
||||||
"""Serialize a list of QWebHistoryItems to a data stream.
|
"""Serialize a list of QWebHistoryItems to a data stream.
|
||||||
|
|
||||||
@ -180,10 +98,7 @@ def serialize(items):
|
|||||||
else:
|
else:
|
||||||
current_idx = 0
|
current_idx = 0
|
||||||
|
|
||||||
if qtutils.is_qtwebkit_ng():
|
_serialize_items(items, current_idx, stream)
|
||||||
_serialize_ng(items, current_idx, stream)
|
|
||||||
else:
|
|
||||||
_serialize_old(items, current_idx, stream)
|
|
||||||
|
|
||||||
user_data += [item.user_data for item in items]
|
user_data += [item.user_data for item in items]
|
||||||
|
|
||||||
|
@ -292,9 +292,6 @@ class WebKitElement(webelem.AbstractWebElement):
|
|||||||
elem = elem._parent() # pylint: disable=protected-access
|
elem = elem._parent() # pylint: disable=protected-access
|
||||||
|
|
||||||
def _move_text_cursor(self):
|
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():
|
if self.is_text_input() and self.is_editable():
|
||||||
self._tab.caret.move_to_end_of_document()
|
self._tab.caret.move_to_end_of_document()
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ from PyQt5.QtGui import QFont
|
|||||||
from PyQt5.QtWebKit import QWebSettings
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
|
|
||||||
from qutebrowser.config import config, websettings
|
from qutebrowser.config import config, websettings
|
||||||
from qutebrowser.utils import standarddir, urlutils, qtutils
|
from qutebrowser.utils import standarddir, urlutils
|
||||||
from qutebrowser.browser import shared
|
from qutebrowser.browser import shared
|
||||||
|
|
||||||
|
|
||||||
@ -131,13 +131,6 @@ def init(_args):
|
|||||||
QWebSettings.setOfflineStoragePath(
|
QWebSettings.setOfflineStoragePath(
|
||||||
os.path.join(data_path, 'offline-storage'))
|
os.path.join(data_path, 'offline-storage'))
|
||||||
|
|
||||||
if (config.val.content.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)
|
websettings.init_mappings(MAPPINGS)
|
||||||
_set_user_stylesheet()
|
_set_user_stylesheet()
|
||||||
config.instance.changed.connect(_update_settings)
|
config.instance.changed.connect(_update_settings)
|
||||||
|
@ -55,20 +55,14 @@ class WebKitPrinting(browsertab.AbstractPrinting):
|
|||||||
|
|
||||||
"""QtWebKit implementations related to printing."""
|
"""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):
|
def check_pdf_support(self):
|
||||||
self._do_check()
|
pass
|
||||||
|
|
||||||
def check_printer_support(self):
|
def check_printer_support(self):
|
||||||
self._do_check()
|
pass
|
||||||
|
|
||||||
def check_preview_support(self):
|
def check_preview_support(self):
|
||||||
self._do_check()
|
pass
|
||||||
|
|
||||||
def to_pdf(self, filename):
|
def to_pdf(self, filename):
|
||||||
printer = QPrinter()
|
printer = QPrinter()
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import html
|
import html
|
||||||
import functools
|
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.QtGui import QDesktopServices
|
||||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||||
from PyQt5.QtWidgets import QFileDialog
|
from PyQt5.QtWidgets import QFileDialog
|
||||||
@ -33,8 +33,8 @@ from qutebrowser.config import config
|
|||||||
from qutebrowser.browser import pdfjs, shared
|
from qutebrowser.browser import pdfjs, shared
|
||||||
from qutebrowser.browser.webkit import http
|
from qutebrowser.browser.webkit import http
|
||||||
from qutebrowser.browser.webkit.network import networkmanager
|
from qutebrowser.browser.webkit.network import networkmanager
|
||||||
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
|
from qutebrowser.utils import (message, usertypes, log, jinja, objreg, debug,
|
||||||
objreg, debug, urlutils)
|
urlutils)
|
||||||
|
|
||||||
|
|
||||||
class BrowserPage(QWebPage):
|
class BrowserPage(QWebPage):
|
||||||
@ -87,22 +87,16 @@ class BrowserPage(QWebPage):
|
|||||||
self.restoreFrameStateRequested.connect(
|
self.restoreFrameStateRequested.connect(
|
||||||
self.on_restore_frame_state_requested)
|
self.on_restore_frame_state_requested)
|
||||||
|
|
||||||
if PYQT_VERSION > 0x050300:
|
def javaScriptPrompt(self, frame, js_msg, default):
|
||||||
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
|
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||||
# We can't override javaScriptPrompt with older PyQt-versions because
|
if self._is_shutting_down:
|
||||||
# of a bug in PyQt.
|
return (False, "")
|
||||||
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html
|
try:
|
||||||
|
return shared.javascript_prompt(frame.url(), js_msg, default,
|
||||||
def javaScriptPrompt(self, frame, js_msg, default):
|
abort_on=[self.loadStarted,
|
||||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
self.shutting_down])
|
||||||
if self._is_shutting_down:
|
except shared.CallSuper:
|
||||||
return (False, "")
|
return super().javaScriptPrompt(frame, js_msg, default)
|
||||||
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):
|
def _handle_errorpage(self, info, errpage):
|
||||||
"""Display an error page if needed.
|
"""Display an error page if needed.
|
||||||
@ -225,10 +219,6 @@ class BrowserPage(QWebPage):
|
|||||||
|
|
||||||
def on_print_requested(self, frame):
|
def on_print_requested(self, frame):
|
||||||
"""Handle printing when requested via javascript."""
|
"""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 = QPrintDialog()
|
||||||
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
printdiag.setAttribute(Qt.WA_DeleteOnClose)
|
||||||
printdiag.open(lambda: frame.print(printdiag.printer()))
|
printdiag.open(lambda: frame.print(printdiag.printer()))
|
||||||
@ -350,15 +340,7 @@ class BrowserPage(QWebPage):
|
|||||||
frame: The QWebFrame which gets saved.
|
frame: The QWebFrame which gets saved.
|
||||||
item: The QWebHistoryItem to be saved.
|
item: The QWebHistoryItem to be saved.
|
||||||
"""
|
"""
|
||||||
try:
|
if frame != self.mainFrame():
|
||||||
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.
|
|
||||||
return
|
return
|
||||||
data = {
|
data = {
|
||||||
'zoom': frame.zoomFactor(),
|
'zoom': frame.zoomFactor(),
|
||||||
@ -401,9 +383,6 @@ class BrowserPage(QWebPage):
|
|||||||
"""
|
"""
|
||||||
return ext in self._extension_handlers
|
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):
|
def extension(self, ext, opt, out):
|
||||||
"""Override QWebPage::extension to provide error pages.
|
"""Override QWebPage::extension to provide error pages.
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
|||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.keyinput import modeman
|
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
|
from qutebrowser.browser.webkit import webpage
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class WebView(QWebView):
|
|||||||
|
|
||||||
def __init__(self, *, win_id, tab_id, tab, private, parent=None):
|
def __init__(self, *, win_id, tab_id, tab, private, parent=None):
|
||||||
super().__init__(parent)
|
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
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
|
||||||
# See https://github.com/qutebrowser/qutebrowser/issues/462
|
# See https://github.com/qutebrowser/qutebrowser/issues/462
|
||||||
self.setStyle(QStyleFactory.create('Fusion'))
|
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,
|
page = webpage.BrowserPage(win_id=self.win_id, tab_id=self._tab_id,
|
||||||
tabdata=tab.data, private=private,
|
tabdata=tab.data, private=private,
|
||||||
parent=self)
|
parent=self)
|
||||||
|
page.setVisibilityState(
|
||||||
try:
|
QWebPage.VisibilityStateVisible if self.isVisible()
|
||||||
page.setVisibilityState(
|
else QWebPage.VisibilityStateHidden)
|
||||||
QWebPage.VisibilityStateVisible if self.isVisible()
|
|
||||||
else QWebPage.VisibilityStateHidden)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.setPage(page)
|
self.setPage(page)
|
||||||
|
|
||||||
@ -240,12 +236,8 @@ class WebView(QWebView):
|
|||||||
Return:
|
Return:
|
||||||
The superclass event return value.
|
The superclass event return value.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
super().showEvent(e)
|
super().showEvent(e)
|
||||||
|
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
|
||||||
|
|
||||||
def hideEvent(self, e):
|
def hideEvent(self, e):
|
||||||
"""Extend hideEvent to set the page visibility state to hidden.
|
"""Extend hideEvent to set the page visibility state to hidden.
|
||||||
@ -256,12 +248,8 @@ class WebView(QWebView):
|
|||||||
Return:
|
Return:
|
||||||
The superclass event return value.
|
The superclass event return value.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
super().hideEvent(e)
|
super().hideEvent(e)
|
||||||
|
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
|
||||||
|
|
||||||
def mousePressEvent(self, e):
|
def mousePressEvent(self, e):
|
||||||
"""Set the tabdata ClickTarget on a mousepress.
|
"""Set the tabdata ClickTarget on a mousepress.
|
||||||
|
@ -22,10 +22,11 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import collections
|
import collections
|
||||||
import traceback
|
import traceback
|
||||||
|
import typing
|
||||||
|
|
||||||
from qutebrowser.commands import cmdexc, argparser
|
from qutebrowser.commands import cmdexc, argparser
|
||||||
from qutebrowser.utils import (log, utils, message, docutils, objreg,
|
from qutebrowser.utils import (log, utils, message, docutils, objreg,
|
||||||
usertypes, typing)
|
usertypes)
|
||||||
from qutebrowser.utils import debug as debug_utils
|
from qutebrowser.utils import debug as debug_utils
|
||||||
from qutebrowser.misc import objects
|
from qutebrowser.misc import objects
|
||||||
|
|
||||||
@ -415,10 +416,7 @@ class Command:
|
|||||||
# We also can't use isinstance here because typing.Union doesn't
|
# We also can't use isinstance here because typing.Union doesn't
|
||||||
# support that.
|
# support that.
|
||||||
# pylint: disable=no-member,useless-suppression
|
# pylint: disable=no-member,useless-suppression
|
||||||
try:
|
types = list(typ.__args__)
|
||||||
types = list(typ.__union_params__)
|
|
||||||
except AttributeError:
|
|
||||||
types = list(typ.__args__)
|
|
||||||
# pylint: enable=no-member,useless-suppression
|
# pylint: enable=no-member,useless-suppression
|
||||||
if param.default is not inspect.Parameter.empty:
|
if param.default is not inspect.Parameter.empty:
|
||||||
types.append(type(param.default))
|
types.append(type(param.default))
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
"""Configuration storage and config-related utilities."""
|
"""Configuration storage and config-related utilities."""
|
||||||
|
|
||||||
|
import sys
|
||||||
import copy
|
import copy
|
||||||
import contextlib
|
import contextlib
|
||||||
import functools
|
import functools
|
||||||
@ -27,9 +28,10 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
|
|||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
from qutebrowser.config import configdata, configexc, configtypes, configfiles
|
from qutebrowser.config import configdata, configexc, configtypes, configfiles
|
||||||
from qutebrowser.utils import utils, objreg, message, log, usertypes
|
from qutebrowser.utils import (utils, objreg, message, log, usertypes, jinja,
|
||||||
from qutebrowser.misc import objects, msgbox
|
qtutils)
|
||||||
from qutebrowser.commands import cmdexc, cmdutils
|
from qutebrowser.misc import objects, msgbox, earlyinit
|
||||||
|
from qutebrowser.commands import cmdexc, cmdutils, runners
|
||||||
from qutebrowser.completion.models import configmodel
|
from qutebrowser.completion.models import configmodel
|
||||||
|
|
||||||
# An easy way to access the config from other code via config.val.foo
|
# An easy way to access the config from other code via config.val.foo
|
||||||
@ -39,6 +41,8 @@ key_instance = None
|
|||||||
|
|
||||||
# Keeping track of all change filters to validate them later.
|
# Keeping track of all change filters to validate them later.
|
||||||
_change_filters = []
|
_change_filters = []
|
||||||
|
# Errors which happened during init, so we can show a message box.
|
||||||
|
_init_errors = []
|
||||||
|
|
||||||
|
|
||||||
class change_filter: # pylint: disable=invalid-name
|
class change_filter: # pylint: disable=invalid-name
|
||||||
@ -173,8 +177,6 @@ class KeyConfig:
|
|||||||
|
|
||||||
def bind(self, key, command, *, mode, force=False, save_yaml=False):
|
def bind(self, key, command, *, mode, force=False, save_yaml=False):
|
||||||
"""Add a new binding from key to command."""
|
"""Add a new binding from key to command."""
|
||||||
# Doing this here to work around a Python 3.4 circular import
|
|
||||||
from qutebrowser.commands import runners
|
|
||||||
key = self._prepare(key, mode)
|
key = self._prepare(key, mode)
|
||||||
|
|
||||||
parser = runners.CommandParser()
|
parser = runners.CommandParser()
|
||||||
@ -382,9 +384,17 @@ class Config(QObject):
|
|||||||
self._mutables = {}
|
self._mutables = {}
|
||||||
self._yaml = yaml_config
|
self._yaml = yaml_config
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
self._yaml.init_save_manager(save_manager)
|
||||||
|
|
||||||
def _set_value(self, opt, value):
|
def _set_value(self, opt, value):
|
||||||
"""Set the given option to the given value."""
|
"""Set the given option to the given value."""
|
||||||
if objects.backend is not None:
|
if not isinstance(objects.backend, objects.NoBackend):
|
||||||
if objects.backend not in opt.backends:
|
if objects.backend not in opt.backends:
|
||||||
raise configexc.BackendError(objects.backend)
|
raise configexc.BackendError(objects.backend)
|
||||||
|
|
||||||
@ -588,8 +598,6 @@ def set_register_stylesheet(obj, *, stylesheet=None, update=True):
|
|||||||
@functools.lru_cache()
|
@functools.lru_cache()
|
||||||
def _render_stylesheet(stylesheet):
|
def _render_stylesheet(stylesheet):
|
||||||
"""Render the given stylesheet jinja template."""
|
"""Render the given stylesheet jinja template."""
|
||||||
# Imported here to avoid a Python 3.4 circular import
|
|
||||||
from qutebrowser.utils import jinja
|
|
||||||
with jinja.environment.no_autoescape():
|
with jinja.environment.no_autoescape():
|
||||||
template = jinja.environment.from_string(stylesheet)
|
template = jinja.environment.from_string(stylesheet)
|
||||||
return template.render(conf=val)
|
return template.render(conf=val)
|
||||||
@ -639,18 +647,14 @@ class StyleSheetObserver(QObject):
|
|||||||
instance.changed.connect(self._update_stylesheet)
|
instance.changed.connect(self._update_stylesheet)
|
||||||
|
|
||||||
|
|
||||||
def init(parent=None):
|
def early_init(args):
|
||||||
"""Initialize the config.
|
"""Initialize the part of the config which works without a QApplication."""
|
||||||
|
|
||||||
Args:
|
|
||||||
parent: The parent to pass to QObjects which get initialized.
|
|
||||||
"""
|
|
||||||
configdata.init()
|
configdata.init()
|
||||||
|
|
||||||
yaml_config = configfiles.YamlConfig()
|
yaml_config = configfiles.YamlConfig()
|
||||||
|
|
||||||
global val, instance, key_instance
|
global val, instance, key_instance
|
||||||
instance = Config(yaml_config=yaml_config, parent=parent)
|
instance = Config(yaml_config=yaml_config)
|
||||||
val = ConfigContainer(instance)
|
val = ConfigContainer(instance)
|
||||||
key_instance = KeyConfig(instance)
|
key_instance = KeyConfig(instance)
|
||||||
|
|
||||||
@ -671,12 +675,7 @@ def init(parent=None):
|
|||||||
raise configexc.ConfigFileErrors('config.py', config_api.errors)
|
raise configexc.ConfigFileErrors('config.py', config_api.errors)
|
||||||
except configexc.ConfigFileErrors as e:
|
except configexc.ConfigFileErrors as e:
|
||||||
log.config.exception("Error while loading config.py")
|
log.config.exception("Error while loading config.py")
|
||||||
errbox = msgbox.msgbox(parent=None,
|
_init_errors.append(e)
|
||||||
title="Error while reading config",
|
|
||||||
text=e.to_html(),
|
|
||||||
icon=QMessageBox.Warning,
|
|
||||||
plain_text=False)
|
|
||||||
errbox.exec_()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if getattr(config_api, 'load_autoconfig', True):
|
if getattr(config_api, 'load_autoconfig', True):
|
||||||
@ -688,12 +687,72 @@ def init(parent=None):
|
|||||||
desc = configexc.ConfigErrorDesc("Error", e)
|
desc = configexc.ConfigErrorDesc("Error", e)
|
||||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||||
except configexc.ConfigFileErrors as e:
|
except configexc.ConfigFileErrors as e:
|
||||||
log.config.exception("Error while loading autoconfig.yml")
|
log.config.exception("Error while loading config.py")
|
||||||
|
_init_errors.append(e)
|
||||||
|
|
||||||
|
configfiles.init()
|
||||||
|
|
||||||
|
objects.backend = get_backend(args)
|
||||||
|
earlyinit.init_with_backend(objects.backend)
|
||||||
|
|
||||||
|
|
||||||
|
def get_backend(args):
|
||||||
|
"""Find out what backend to use based on available libraries."""
|
||||||
|
try:
|
||||||
|
import PyQt5.QtWebKit # pylint: disable=unused-variable
|
||||||
|
except ImportError:
|
||||||
|
webkit_available = False
|
||||||
|
else:
|
||||||
|
webkit_available = qtutils.is_new_qtwebkit()
|
||||||
|
|
||||||
|
str_to_backend = {
|
||||||
|
'webkit': usertypes.Backend.QtWebKit,
|
||||||
|
'webengine': usertypes.Backend.QtWebEngine,
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.backend is not None:
|
||||||
|
return str_to_backend[args.backend]
|
||||||
|
elif val.backend != 'auto':
|
||||||
|
return str_to_backend[val.backend]
|
||||||
|
elif webkit_available:
|
||||||
|
return usertypes.Backend.QtWebKit
|
||||||
|
else:
|
||||||
|
return usertypes.Backend.QtWebEngine
|
||||||
|
|
||||||
|
|
||||||
|
def late_init(save_manager):
|
||||||
|
"""Initialize the rest of the config after the QApplication is created."""
|
||||||
|
global _init_errors
|
||||||
|
for err in _init_errors:
|
||||||
errbox = msgbox.msgbox(parent=None,
|
errbox = msgbox.msgbox(parent=None,
|
||||||
title="Error while reading config",
|
title="Error while reading config",
|
||||||
text=e.to_html(),
|
text=err.to_html(),
|
||||||
icon=QMessageBox.Warning,
|
icon=QMessageBox.Warning,
|
||||||
plain_text=False)
|
plain_text=False)
|
||||||
errbox.exec_()
|
errbox.exec_()
|
||||||
|
_init_errors = []
|
||||||
|
|
||||||
configfiles.init()
|
instance.init_save_manager(save_manager)
|
||||||
|
configfiles.state.init_save_manager(save_manager)
|
||||||
|
|
||||||
|
|
||||||
|
def qt_args(namespace):
|
||||||
|
"""Get the Qt QApplication arguments based on an argparse namespace.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
namespace: The argparse namespace.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The argv list to be passed to Qt.
|
||||||
|
"""
|
||||||
|
argv = [sys.argv[0]]
|
||||||
|
|
||||||
|
if namespace.qt_flag is not None:
|
||||||
|
argv += ['--' + flag[0] for flag in namespace.qt_flag]
|
||||||
|
|
||||||
|
if namespace.qt_arg is not None:
|
||||||
|
for name, value in namespace.qt_arg:
|
||||||
|
argv += ['--' + name, value]
|
||||||
|
|
||||||
|
argv += ['--' + arg for arg in val.qt_args]
|
||||||
|
return argv
|
||||||
|
@ -15,6 +15,9 @@ aliases:
|
|||||||
desc: >-
|
desc: >-
|
||||||
Aliases for commands.
|
Aliases for commands.
|
||||||
|
|
||||||
|
The keys of the given dictionary are the aliases, while the values are the
|
||||||
|
commands they map to.
|
||||||
|
|
||||||
confirm_quit:
|
confirm_quit:
|
||||||
type: ConfirmQuit
|
type: ConfirmQuit
|
||||||
default: [never]
|
default: [never]
|
||||||
@ -85,6 +88,41 @@ session_default_name:
|
|||||||
|
|
||||||
If this is set to null, the session which was last loaded is saved.
|
If this is set to null, the session which was last loaded is saved.
|
||||||
|
|
||||||
|
qt_args:
|
||||||
|
type:
|
||||||
|
name: List
|
||||||
|
valtype: String
|
||||||
|
none_ok: true
|
||||||
|
default: []
|
||||||
|
desc: >-
|
||||||
|
Additional arguments to pass to Qt, without leading `--`.
|
||||||
|
|
||||||
|
With QtWebEngine, some Chromium arguments (see
|
||||||
|
https://peter.sh/experiments/chromium-command-line-switches/ for a list)
|
||||||
|
will work.
|
||||||
|
|
||||||
|
backend:
|
||||||
|
type:
|
||||||
|
name: String
|
||||||
|
valid_values:
|
||||||
|
- auto: Automatically select either QtWebEngine or QtWebKit
|
||||||
|
- webkit: Force QtWebKit
|
||||||
|
- webengine: Force QtWebEngine
|
||||||
|
default: auto
|
||||||
|
desc: >-
|
||||||
|
The backend to use to display websites.
|
||||||
|
|
||||||
|
qutebrowser supports two different web rendering engines / backends,
|
||||||
|
QtWebKit and QtWebEngine.
|
||||||
|
|
||||||
|
QtWebKit is based on WebKit (similar to Safari). It was discontinued by the
|
||||||
|
Qt project with Qt 5.6, but picked up as a well maintained fork:
|
||||||
|
https://github.com/annulen/webkit/wiki - qutebrowser only supports the fork.
|
||||||
|
|
||||||
|
QtWebEngine is Qt's official successor to QtWebKit and based on the Chromium
|
||||||
|
project. It's slightly more resource hungry that QtWebKit and has a couple
|
||||||
|
of missing features in qutebrowser, but is generally the preferred choice.
|
||||||
|
|
||||||
## auto_save
|
## auto_save
|
||||||
|
|
||||||
auto_save.interval:
|
auto_save.interval:
|
||||||
@ -402,6 +440,10 @@ content.javascript.log:
|
|||||||
desc: >-
|
desc: >-
|
||||||
Log levels to use for JavaScript console logging messages.
|
Log levels to use for JavaScript console logging messages.
|
||||||
|
|
||||||
|
When a JavaScript message with the level given in the dictionary key is
|
||||||
|
logged, the corresponding dictionary value selects the qutebrowser logger to
|
||||||
|
use.
|
||||||
|
|
||||||
On QtWebKit, the "unknown" setting is always used.
|
On QtWebKit, the "unknown" setting is always used.
|
||||||
|
|
||||||
content.javascript.modal_dialog:
|
content.javascript.modal_dialog:
|
||||||
@ -1183,11 +1225,14 @@ url.searchengines:
|
|||||||
desc: >-
|
desc: >-
|
||||||
Definitions of search engines which can be used via the address bar.
|
Definitions of search engines which can be used via the address bar.
|
||||||
|
|
||||||
|
Maps a searchengine name (such as `DEFAULT`, or `ddg`) to a URL with a `{}`
|
||||||
|
placeholder. The placeholder will be replaced by the search term, use `{{`
|
||||||
|
and `}}` for literal `{`/`}` signs.
|
||||||
|
|
||||||
The searchengine named `DEFAULT` is used when `url.auto_search` is turned on
|
The searchengine named `DEFAULT` is used when `url.auto_search` is turned on
|
||||||
and something else than a URL was entered to be opened. Other search engines
|
and something else than a URL was entered to be opened. Other search engines
|
||||||
can be used by prepending the search engine name to the search term, e.g.
|
can be used by prepending the search engine name to the search term, e.g.
|
||||||
`:open google qutebrowser`. The string `{}` will be replaced by the search
|
`:open google qutebrowser`.
|
||||||
term, use `{{` and `}}` for literal `{`/`}` signs.
|
|
||||||
|
|
||||||
url.start_pages:
|
url.start_pages:
|
||||||
type:
|
type:
|
||||||
@ -1713,7 +1758,7 @@ fonts.downloads:
|
|||||||
desc: Font used for the downloadbar.
|
desc: Font used for the downloadbar.
|
||||||
|
|
||||||
fonts.hints:
|
fonts.hints:
|
||||||
default: bold 13px monospace
|
default: bold 10pt monospace
|
||||||
type: Font
|
type: Font
|
||||||
desc: Font used for the hints.
|
desc: Font used for the hints.
|
||||||
|
|
||||||
@ -2122,7 +2167,7 @@ bindings.default:
|
|||||||
want to load no default keybindings at all.
|
want to load no default keybindings at all.
|
||||||
|
|
||||||
If you want to preserve default bindings (and get new bindings when there is
|
If you want to preserve default bindings (and get new bindings when there is
|
||||||
an update), add new bindings to `bindings.commands` (or use `:bind`) and
|
an update), use `config.bind()` in `config.py` or the `:bind` command, and
|
||||||
leave this setting alone.
|
leave this setting alone.
|
||||||
|
|
||||||
bindings.commands:
|
bindings.commands:
|
||||||
@ -2148,64 +2193,4 @@ bindings.commands:
|
|||||||
|
|
||||||
`{mode: {key: command}}`
|
`{mode: {key: command}}`
|
||||||
|
|
||||||
If you want to map a key to another key, check the `bindings.key_mappings`
|
While it's possible to bind keys by modifying this setting, it's recommended
|
||||||
setting instead.
|
|
||||||
|
|
||||||
For special keys (can't be part of a keychain), enclose them in `<`...`>`.
|
|
||||||
For modifiers, you can use either `-` or `+` as delimiters, and these names:
|
|
||||||
|
|
||||||
* Control: `Control`, `Ctrl`
|
|
||||||
|
|
||||||
* Meta: `Meta`, `Windows`, `Mod4`
|
|
||||||
|
|
||||||
* Alt: `Alt`, `Mod1`
|
|
||||||
|
|
||||||
* Shift: `Shift`
|
|
||||||
|
|
||||||
For simple keys (no `<>`-signs), a capital letter means the key is pressed
|
|
||||||
with Shift. For special keys (with `<>`-signs), you need to explicitly add
|
|
||||||
`Shift-` to match a key pressed with shift.
|
|
||||||
|
|
||||||
If you want a binding to do nothing, bind it to the `nop` command.
|
|
||||||
If you want a default binding to be passed through to the website, bind it
|
|
||||||
to null.
|
|
||||||
|
|
||||||
Note that some commands which are only useful for bindings (but not used
|
|
||||||
interactively) are hidden from the command completion. See `:help` for a
|
|
||||||
full list of available commands.
|
|
||||||
|
|
||||||
The following modes are available:
|
|
||||||
|
|
||||||
|
|
||||||
* normal: The default mode, where most commands are invoked.
|
|
||||||
|
|
||||||
|
|
||||||
* insert: Entered when an input field is focused on a website, or by
|
|
||||||
pressing `i` in normal mode. Passes through almost all keypresses to the
|
|
||||||
website, but has some bindings like `<Ctrl-e>` to open an external editor.
|
|
||||||
Note that single keys can't be bound in this mode.
|
|
||||||
|
|
||||||
* hint: Entered when `f` is pressed to select links with the keyboard. Note
|
|
||||||
that single keys can't be bound in this mode.
|
|
||||||
|
|
||||||
* passthrough: Similar to insert mode, but passes through all keypresses
|
|
||||||
except `<Escape>` to leave the mode. It might be useful to bind `<Escape>`
|
|
||||||
to some other key in this mode if you want to be able to send an Escape
|
|
||||||
key to the website as well. Note that single keys can't be bound in this
|
|
||||||
mode.
|
|
||||||
|
|
||||||
* command: Entered when pressing the `:` key in order to enter a command.
|
|
||||||
Note that single keys can't be bound in this mode.
|
|
||||||
|
|
||||||
* prompt: Entered when there's a prompt to display, like for download
|
|
||||||
locations or when invoked from JavaScript.
|
|
||||||
+
|
|
||||||
You can bind normal keys in this mode, but they will be only active when a
|
|
||||||
yes/no-prompt is asked. For other prompt modes, you can only bind special
|
|
||||||
keys.
|
|
||||||
|
|
||||||
* caret: Entered when pressing the `v` mode, used to select text using the
|
|
||||||
keyboard.
|
|
||||||
|
|
||||||
* register: Entered when qutebrowser is waiting for a register name/key for
|
|
||||||
commands like `:set-mark`.
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
"""Exceptions related to config parsing."""
|
"""Exceptions related to config parsing."""
|
||||||
|
|
||||||
from qutebrowser.utils import utils
|
from qutebrowser.utils import utils, jinja
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
@ -108,7 +108,6 @@ class ConfigFileErrors(Error):
|
|||||||
|
|
||||||
def to_html(self):
|
def to_html(self):
|
||||||
"""Get the error texts as a HTML snippet."""
|
"""Get the error texts as a HTML snippet."""
|
||||||
from qutebrowser.utils import jinja
|
|
||||||
template = jinja.environment.from_string("""
|
template = jinja.environment.from_string("""
|
||||||
Errors occurred while reading {{ basename }}:
|
Errors occurred while reading {{ basename }}:
|
||||||
|
|
||||||
|
@ -29,8 +29,13 @@ import contextlib
|
|||||||
import yaml
|
import yaml
|
||||||
from PyQt5.QtCore import QSettings
|
from PyQt5.QtCore import QSettings
|
||||||
|
|
||||||
from qutebrowser.config import configexc
|
import qutebrowser
|
||||||
from qutebrowser.utils import objreg, standarddir, utils, qtutils
|
from qutebrowser.config import configexc, config
|
||||||
|
from qutebrowser.utils import standarddir, utils, qtutils
|
||||||
|
|
||||||
|
|
||||||
|
# The StateConfig instance
|
||||||
|
state = None
|
||||||
|
|
||||||
|
|
||||||
class StateConfig(configparser.ConfigParser):
|
class StateConfig(configparser.ConfigParser):
|
||||||
@ -39,7 +44,6 @@ class StateConfig(configparser.ConfigParser):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
save_manager = objreg.get('save-manager')
|
|
||||||
self._filename = os.path.join(standarddir.data(), 'state')
|
self._filename = os.path.join(standarddir.data(), 'state')
|
||||||
self.read(self._filename, encoding='utf-8')
|
self.read(self._filename, encoding='utf-8')
|
||||||
for sect in ['general', 'geometry']:
|
for sect in ['general', 'geometry']:
|
||||||
@ -47,8 +51,17 @@ class StateConfig(configparser.ConfigParser):
|
|||||||
self.add_section(sect)
|
self.add_section(sect)
|
||||||
except configparser.DuplicateSectionError:
|
except configparser.DuplicateSectionError:
|
||||||
pass
|
pass
|
||||||
# See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
|
|
||||||
self['general'].pop('fooled', None)
|
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)
|
save_manager.add_saveable('state-config', self._save)
|
||||||
|
|
||||||
def _save(self):
|
def _save(self):
|
||||||
@ -68,12 +81,18 @@ class YamlConfig:
|
|||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
save_manager = objreg.get('save-manager')
|
|
||||||
self._filename = os.path.join(standarddir.config(auto=True),
|
self._filename = os.path.join(standarddir.config(auto=True),
|
||||||
'autoconfig.yml')
|
'autoconfig.yml')
|
||||||
save_manager.add_saveable('yaml-config', self._save)
|
|
||||||
self.values = {}
|
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):
|
def _save(self):
|
||||||
"""Save the changed settings to the YAML file."""
|
"""Save the changed settings to the YAML file."""
|
||||||
data = {'config_version': self.VERSION, 'global': self.values}
|
data = {'config_version': self.VERSION, 'global': self.values}
|
||||||
@ -135,8 +154,8 @@ class ConfigAPI:
|
|||||||
errors: Errors which occurred while setting options.
|
errors: Errors which occurred while setting options.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config, keyconfig):
|
def __init__(self, conf, keyconfig):
|
||||||
self._config = config
|
self._config = conf
|
||||||
self._keyconfig = keyconfig
|
self._keyconfig = keyconfig
|
||||||
self.load_autoconfig = True
|
self.load_autoconfig = True
|
||||||
self.errors = []
|
self.errors = []
|
||||||
@ -161,18 +180,17 @@ class ConfigAPI:
|
|||||||
with self._handle_error('setting', name):
|
with self._handle_error('setting', name):
|
||||||
self._config.set_obj(name, value)
|
self._config.set_obj(name, value)
|
||||||
|
|
||||||
def bind(self, key, command, *, mode, force=False):
|
def bind(self, key, command, mode='normal', *, force=False):
|
||||||
with self._handle_error('binding', key):
|
with self._handle_error('binding', key):
|
||||||
self._keyconfig.bind(key, command, mode=mode, force=force)
|
self._keyconfig.bind(key, command, mode=mode, force=force)
|
||||||
|
|
||||||
def unbind(self, key, *, mode):
|
def unbind(self, key, mode='normal'):
|
||||||
with self._handle_error('unbinding', key):
|
with self._handle_error('unbinding', key):
|
||||||
self._keyconfig.unbind(key, mode=mode)
|
self._keyconfig.unbind(key, mode=mode)
|
||||||
|
|
||||||
|
|
||||||
def read_config_py(filename=None):
|
def read_config_py(filename=None):
|
||||||
"""Read a config.py file."""
|
"""Read a config.py file."""
|
||||||
from qutebrowser.config import config
|
|
||||||
api = ConfigAPI(config.instance, config.key_instance)
|
api = ConfigAPI(config.instance, config.key_instance)
|
||||||
|
|
||||||
if filename is None:
|
if filename is None:
|
||||||
@ -220,8 +238,9 @@ def read_config_py(filename=None):
|
|||||||
|
|
||||||
def init():
|
def init():
|
||||||
"""Initialize config storage not related to the main config."""
|
"""Initialize config storage not related to the main config."""
|
||||||
|
global state
|
||||||
state = StateConfig()
|
state = StateConfig()
|
||||||
objreg.register('state-config', state)
|
state['general']['version'] = qutebrowser.__version__
|
||||||
|
|
||||||
# Set the QSettings path to something like
|
# Set the QSettings path to something like
|
||||||
# ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it
|
# ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it
|
||||||
|
@ -59,9 +59,9 @@ from PyQt5.QtCore import QUrl, Qt
|
|||||||
from PyQt5.QtGui import QColor, QFont
|
from PyQt5.QtGui import QColor, QFont
|
||||||
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
||||||
|
|
||||||
from qutebrowser.commands import cmdutils
|
from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||||
from qutebrowser.config import configexc
|
from qutebrowser.config import configexc
|
||||||
from qutebrowser.utils import standarddir, utils, qtutils
|
from qutebrowser.utils import standarddir, utils, qtutils, urlutils
|
||||||
|
|
||||||
|
|
||||||
SYSTEM_PROXY = object() # Return value for Proxy type
|
SYSTEM_PROXY = object() # Return value for Proxy type
|
||||||
@ -791,7 +791,6 @@ class Command(BaseType):
|
|||||||
if not Command.unvalidated:
|
if not Command.unvalidated:
|
||||||
Command.unvalidated = True
|
Command.unvalidated = True
|
||||||
try:
|
try:
|
||||||
from qutebrowser.commands import runners, cmdexc
|
|
||||||
parser = runners.CommandParser()
|
parser = runners.CommandParser()
|
||||||
try:
|
try:
|
||||||
parser.parse_all(value)
|
parser.parse_all(value)
|
||||||
@ -1287,7 +1286,6 @@ class Proxy(BaseType):
|
|||||||
('none', "Don't use any proxy"))
|
('none', "Don't use any proxy"))
|
||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
from qutebrowser.utils import urlutils
|
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
@ -1352,7 +1350,6 @@ class FuzzyUrl(BaseType):
|
|||||||
"""A URL which gets interpreted as search if needed."""
|
"""A URL which gets interpreted as search if needed."""
|
||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
from qutebrowser.utils import urlutils
|
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
{% extends "styled.html" %}
|
|
||||||
|
|
||||||
{% block style %}
|
|
||||||
{{super()}}
|
|
||||||
.note {
|
|
||||||
font-size: smaller;
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mono {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h1>Legacy QtWebKit backend</h1>
|
|
||||||
|
|
||||||
<span class="note">Note this warning will only appear once. Use <span class="mono">:open
|
|
||||||
qute://backend-warning</span> to show it again at a later time.</span>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
You're using qutebrowser with the legacy QtWebKit backend. It's still the
|
|
||||||
default until a few remaining issues are sorted out. If you can, it's
|
|
||||||
strongly suggested to switch earlier, as legacy QtWebKit has known security
|
|
||||||
issues and also breaks things on various websites.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Using QtWebEngine instead</h2>
|
|
||||||
|
|
||||||
<span class="note">This is usually the better choice if you aren't using Nouveau graphics, and
|
|
||||||
don't need any features which are currently unavailable with QtWebEngine (like
|
|
||||||
the <span class="mono">qute://settings</span> page or caret browsing).</span>
|
|
||||||
|
|
||||||
{% macro install_webengine(package) -%}
|
|
||||||
You should be able to install <span class="mono">{{ package }}</span> and start qutebrowser with <span class="mono">--backend webengine</span> to use the new backend.
|
|
||||||
{%- endmacro %}
|
|
||||||
|
|
||||||
{% macro please_open_issue() -%}
|
|
||||||
If you know more, please <a href="https://github.com/qutebrowser/qutebrowser/issues/new">open an issue</a>!
|
|
||||||
{%- endmacro %}
|
|
||||||
|
|
||||||
{% macro unknown_system() -%}
|
|
||||||
There's no information available for your system. {{ please_open_issue() }}
|
|
||||||
{%- endmacro %}
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% if distribution.parsed == Distribution.ubuntu %}
|
|
||||||
{% if distribution.version == none %}
|
|
||||||
{{ unknown_system() }}
|
|
||||||
{% elif distribution.version >= version('17.04') %}
|
|
||||||
{{ install_webengine('python3-pyqt5.qtwebengine') }}
|
|
||||||
{% elif distribution.version >= version('16.04') %}
|
|
||||||
QtWebEngine is only available in Ubuntu's repositories since 17.04, but you can <a href="https://github.com/qutebrowser/qutebrowser/blob/master/INSTALL.asciidoc#installing-qutebrowser-with-tox">install qutebrowser via tox</a> with <span class="mono">tox -e mkvenv-pypi</span> to use the new backend.
|
|
||||||
{% else %}
|
|
||||||
Unfortunately, no easy way is known to install QtWebEngine on Ubuntu < 16.04. {{ please_open_issue() }}
|
|
||||||
{% endif %}
|
|
||||||
{% elif distribution.parsed == Distribution.debian %}
|
|
||||||
{% if distribution.version == none %}
|
|
||||||
{{ unknown_system() }}
|
|
||||||
{% elif distribution.version >= version('9') %}
|
|
||||||
{{ install_webengine('python3-pyqt5.qtwebengine') }}
|
|
||||||
{% else %}
|
|
||||||
Unfortunately, no easy way is known to install QtWebEngine on Debian < 9. {{ please_open_issue() }}
|
|
||||||
{% endif %}
|
|
||||||
{% elif distribution.parsed in [Distribution.arch, Distribution.manjaro] %}
|
|
||||||
{{ install_webengine('qt5-webengine') }}
|
|
||||||
{% elif distribution.parsed == Distribution.void %}
|
|
||||||
{{ install_webengine('python-PyQt5-webengine') }}
|
|
||||||
{% elif distribution.parsed == Distribution.fedora %}
|
|
||||||
{{ install_webengine('qt5-qtwebengine') }}
|
|
||||||
{% elif distribution.parsed == Distribution.opensuse %}
|
|
||||||
{{ install_webengine('libqt5-qtwebengine') }}
|
|
||||||
{% elif distribution.parsed == Distribution.gentoo %}
|
|
||||||
{{ install_webengine('dev-qt/qtwebengine') }}
|
|
||||||
{% else %}
|
|
||||||
{{ unknown_system() }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Using QtWebKit-NG instead</h2>
|
|
||||||
|
|
||||||
<span class="note">This is a drop-in replacement for legacy QtWebKit.</span>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
{% if distribution.parsed == Distribution.debian and distribution.version != none and distribution.version >= version('9') %}
|
|
||||||
There are unofficial QtWebKit-NG packages <a href="http://repo.paretje.be/unstable/">available</a>.
|
|
||||||
{% elif distribution.parsed in [Distribution.ubuntu, Distribution.debian] %}
|
|
||||||
No easy way is known to install QtWebKit-NG on your system.
|
|
||||||
There are unofficial QtWebKit-NG packages <a href="http://repo.paretje.be/unstable/">available</a>, but they are intended for Debian Unstable.
|
|
||||||
{{ please_open_issue() }}
|
|
||||||
{% elif distribution.parsed in [Distribution.arch, Distribution.manjaro] %}
|
|
||||||
With an updated <span class="mono">qt5-webkit</span> package, you should already get QtWebKit-NG.
|
|
||||||
{% elif distribution.parsed == Distribution.gentoo %}
|
|
||||||
There's an unofficial <a href="https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71">ebuild</a> available.
|
|
||||||
{% else %}
|
|
||||||
{{ unknown_system() }}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,61 +0,0 @@
|
|||||||
{% extends "styled.html" %}
|
|
||||||
|
|
||||||
{% block style %}
|
|
||||||
{{super()}}
|
|
||||||
body {
|
|
||||||
max-width: 1440px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.title {
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.time {
|
|
||||||
color: #555;
|
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date {
|
|
||||||
color: #555;
|
|
||||||
font-size: 12pt;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-link {
|
|
||||||
color: #555;
|
|
||||||
font-weight: bold;
|
|
||||||
margn-bottom: 15px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<h1>Browsing history</h1>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<caption class="date">{{curr_date.strftime("%a, %d %B %Y")}}</caption>
|
|
||||||
<tbody>
|
|
||||||
{% for url, title, time, host in history %}
|
|
||||||
<tr>
|
|
||||||
<td class="title">
|
|
||||||
<a href="{{url}}">{{title}}</a>
|
|
||||||
<span class="hostname">{{host}}</span>
|
|
||||||
</td>
|
|
||||||
<td class="time">{{time.strftime("%X")}}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<span class="pagination-link"><a href="qute://history/?date={{prev_date.strftime("%Y-%m-%d")}}" rel="prev">Previous</a></span>
|
|
||||||
{% if today >= next_date %}
|
|
||||||
<span class="pagination-link"><a href="qute://history/?date={{next_date.strftime("%Y-%m-%d")}}" rel="next">Next</a></span>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -28,11 +28,10 @@ from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
|||||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
|
||||||
|
|
||||||
from qutebrowser.commands import runners, cmdutils
|
from qutebrowser.commands import runners, cmdutils
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config, configfiles
|
||||||
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
|
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
|
||||||
jinja, debug)
|
jinja, debug)
|
||||||
from qutebrowser.mainwindow import tabbedbrowser, messageview, prompt
|
from qutebrowser.mainwindow import messageview, prompt
|
||||||
from qutebrowser.mainwindow.statusbar import bar
|
|
||||||
from qutebrowser.completion import completionwidget, completer
|
from qutebrowser.completion import completionwidget, completer
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
from qutebrowser.browser import (commands, downloadview, hints,
|
from qutebrowser.browser import (commands, downloadview, hints,
|
||||||
@ -140,6 +139,11 @@ class MainWindow(QWidget):
|
|||||||
parent: The parent the window should get.
|
parent: The parent the window should get.
|
||||||
"""
|
"""
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
# Late import to avoid a circular dependency
|
||||||
|
# - browsertab -> hints -> webelem -> mainwindow -> bar -> browsertab
|
||||||
|
from qutebrowser.mainwindow import tabbedbrowser
|
||||||
|
from qutebrowser.mainwindow.statusbar import bar
|
||||||
|
|
||||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||||
self._commandrunner = None
|
self._commandrunner = None
|
||||||
self._overlays = []
|
self._overlays = []
|
||||||
@ -365,9 +369,8 @@ class MainWindow(QWidget):
|
|||||||
|
|
||||||
def _load_state_geometry(self):
|
def _load_state_geometry(self):
|
||||||
"""Load the geometry from the state file."""
|
"""Load the geometry from the state file."""
|
||||||
state_config = objreg.get('state-config')
|
|
||||||
try:
|
try:
|
||||||
data = state_config['geometry']['mainwindow']
|
data = configfiles.state['geometry']['mainwindow']
|
||||||
geom = base64.b64decode(data, validate=True)
|
geom = base64.b64decode(data, validate=True)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# First start
|
# First start
|
||||||
@ -380,10 +383,9 @@ class MainWindow(QWidget):
|
|||||||
|
|
||||||
def _save_geometry(self):
|
def _save_geometry(self):
|
||||||
"""Save the window geometry to the state config."""
|
"""Save the window geometry to the state config."""
|
||||||
state_config = objreg.get('state-config')
|
|
||||||
data = bytes(self.saveGeometry())
|
data = bytes(self.saveGeometry())
|
||||||
geom = base64.b64encode(data).decode('ASCII')
|
geom = base64.b64encode(data).decode('ASCII')
|
||||||
state_config['geometry']['mainwindow'] = geom
|
configfiles.state['geometry']['mainwindow'] = geom
|
||||||
|
|
||||||
def _load_geometry(self, geom):
|
def _load_geometry(self, geom):
|
||||||
"""Load geometry from a bytes object.
|
"""Load geometry from a bytes object.
|
||||||
|
@ -61,10 +61,6 @@ class Progress(QProgressBar):
|
|||||||
|
|
||||||
def on_tab_changed(self, tab):
|
def on_tab_changed(self, tab):
|
||||||
"""Set the correct value when the current tab changed."""
|
"""Set the correct value when the current tab changed."""
|
||||||
if self is None: # pragma: no branch
|
|
||||||
# This should never happen, but for some weird reason it does
|
|
||||||
# sometimes.
|
|
||||||
return # pragma: no cover
|
|
||||||
self.setValue(tab.progress())
|
self.setValue(tab.progress())
|
||||||
if tab.load_status() == usertypes.LoadStatus.loading:
|
if tab.load_status() == usertypes.LoadStatus.loading:
|
||||||
self.show()
|
self.show()
|
||||||
|
@ -28,7 +28,7 @@ from PyQt5.QtGui import QIcon
|
|||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
from qutebrowser.mainwindow import tabwidget
|
from qutebrowser.mainwindow import tabwidget, mainwindow
|
||||||
from qutebrowser.browser import signalfilter, browsertab
|
from qutebrowser.browser import signalfilter, browsertab
|
||||||
from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg,
|
from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg,
|
||||||
urlutils, message, jinja)
|
urlutils, message, jinja)
|
||||||
@ -432,7 +432,6 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
|
|
||||||
if (config.val.tabs.tabs_are_windows and self.count() > 0 and
|
if (config.val.tabs.tabs_are_windows and self.count() > 0 and
|
||||||
not ignore_tabs_are_windows):
|
not ignore_tabs_are_windows):
|
||||||
from qutebrowser.mainwindow import mainwindow
|
|
||||||
window = mainwindow.MainWindow(private=self.private)
|
window = mainwindow.MainWindow(private=self.private)
|
||||||
window.show()
|
window.show()
|
||||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
|
@ -70,9 +70,6 @@ class TabWidget(QTabWidget):
|
|||||||
@config.change_filter('tabs')
|
@config.change_filter('tabs')
|
||||||
def _init_config(self):
|
def _init_config(self):
|
||||||
"""Initialize attributes based on the config."""
|
"""Initialize attributes based on the config."""
|
||||||
if self is None: # pragma: no cover
|
|
||||||
# WORKAROUND for PyQt 5.2
|
|
||||||
return
|
|
||||||
tabbar = self.tabBar()
|
tabbar = self.tabBar()
|
||||||
self.setMovable(True)
|
self.setMovable(True)
|
||||||
self.setTabsClosable(False)
|
self.setTabsClosable(False)
|
||||||
@ -558,7 +555,7 @@ class TabBar(QTabBar):
|
|||||||
# pylint: enable=bad-config-option
|
# pylint: enable=bad-config-option
|
||||||
if idx == selected:
|
if idx == selected:
|
||||||
setting = setting.selected
|
setting = setting.selected
|
||||||
setting = setting.odd if idx % 2 else setting.even
|
setting = setting.odd if (idx + 1) % 2 else setting.even
|
||||||
|
|
||||||
tab.palette.setColor(QPalette.Window, setting.bg)
|
tab.palette.setColor(QPalette.Window, setting.bg)
|
||||||
tab.palette.setColor(QPalette.WindowText, setting.fg)
|
tab.palette.setColor(QPalette.WindowText, setting.fg)
|
||||||
@ -744,24 +741,17 @@ class TabBarStyle(QCommonStyle):
|
|||||||
log.misc.warning("Could not get layouts for tab!")
|
log.misc.warning("Could not get layouts for tab!")
|
||||||
return QRect()
|
return QRect()
|
||||||
return layouts.text
|
return layouts.text
|
||||||
elif sr == QStyle.SE_TabWidgetTabBar:
|
elif sr in [QStyle.SE_TabWidgetTabBar,
|
||||||
|
QStyle.SE_TabBarScrollLeftButton]:
|
||||||
|
# Handling SE_TabBarScrollLeftButton so the left scroll button is
|
||||||
|
# aligned properly. Otherwise, empty space will be shown after the
|
||||||
|
# last tab even though the button width is set to 0
|
||||||
|
#
|
||||||
# Need to use super() because we also use super() to render
|
# Need to use super() because we also use super() to render
|
||||||
# element in drawControl(); otherwise, we may get bit by
|
# element in drawControl(); otherwise, we may get bit by
|
||||||
# style differences...
|
# style differences...
|
||||||
rct = super().subElementRect(sr, opt, widget)
|
return super().subElementRect(sr, opt, widget)
|
||||||
return rct
|
|
||||||
else:
|
else:
|
||||||
try:
|
|
||||||
# We need this so the left scroll button is aligned properly.
|
|
||||||
# Otherwise, empty space will be shown after the last tab even
|
|
||||||
# though the button width is set to 0
|
|
||||||
#
|
|
||||||
# QStyle.SE_TabBarScrollLeftButton was added in Qt 5.7
|
|
||||||
if sr == QStyle.SE_TabBarScrollLeftButton:
|
|
||||||
return super().subElementRect(sr, opt, widget)
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return self._style.subElementRect(sr, opt, widget)
|
return self._style.subElementRect(sr, opt, widget)
|
||||||
|
|
||||||
def _tab_layout(self, opt):
|
def _tab_layout(self, opt):
|
||||||
|
@ -43,12 +43,12 @@ except ImportError: # pragma: no cover
|
|||||||
# to stderr.
|
# to stderr.
|
||||||
def check_python_version():
|
def check_python_version():
|
||||||
"""Check if correct python version is run."""
|
"""Check if correct python version is run."""
|
||||||
if sys.hexversion < 0x03040000:
|
if sys.hexversion < 0x03050000:
|
||||||
# We don't use .format() and print_function here just in case someone
|
# We don't use .format() and print_function here just in case someone
|
||||||
# still has < 2.6 installed.
|
# still has < 2.6 installed.
|
||||||
# pylint: disable=bad-builtin
|
# pylint: disable=bad-builtin
|
||||||
version_str = '.'.join(map(str, sys.version_info[:3]))
|
version_str = '.'.join(map(str, sys.version_info[:3]))
|
||||||
text = ("At least Python 3.4 is required to run qutebrowser, but " +
|
text = ("At least Python 3.5 is required to run qutebrowser, but " +
|
||||||
version_str + " is installed!\n")
|
version_str + " is installed!\n")
|
||||||
if Tk and '--no-err-windows' not in sys.argv: # pragma: no cover
|
if Tk and '--no-err-windows' not in sys.argv: # pragma: no cover
|
||||||
root = Tk()
|
root = Tk()
|
||||||
|
@ -38,7 +38,7 @@ import qutebrowser
|
|||||||
from qutebrowser.utils import version, log, utils, objreg, usertypes
|
from qutebrowser.utils import version, log, utils, objreg, usertypes
|
||||||
from qutebrowser.misc import (miscwidgets, autoupdate, msgbox, httpclient,
|
from qutebrowser.misc import (miscwidgets, autoupdate, msgbox, httpclient,
|
||||||
pastebin, objects)
|
pastebin, objects)
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config, configfiles
|
||||||
|
|
||||||
|
|
||||||
def parse_fatal_stacktrace(text):
|
def parse_fatal_stacktrace(text):
|
||||||
@ -152,22 +152,7 @@ class _CrashDialog(QDialog):
|
|||||||
self._pypi_client = autoupdate.PyPIVersionClient(self)
|
self._pypi_client = autoupdate.PyPIVersionClient(self)
|
||||||
self._init_text()
|
self._init_text()
|
||||||
|
|
||||||
contact = QLabel("I'd like to be able to follow up with you, to keep "
|
self._init_contact_input()
|
||||||
"you posted on the status of this crash and get more "
|
|
||||||
"information if I need it - how can I contact you?",
|
|
||||||
wordWrap=True)
|
|
||||||
self._vbox.addWidget(contact)
|
|
||||||
self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False)
|
|
||||||
try:
|
|
||||||
state = objreg.get('state-config')
|
|
||||||
try:
|
|
||||||
self._contact.setPlainText(state['general']['contact-info'])
|
|
||||||
except KeyError:
|
|
||||||
self._contact.setPlaceholderText("Mail or IRC nickname")
|
|
||||||
except Exception:
|
|
||||||
log.misc.exception("Failed to get contact information!")
|
|
||||||
self._contact.setPlaceholderText("Mail or IRC nickname")
|
|
||||||
self._vbox.addWidget(self._contact, 2)
|
|
||||||
|
|
||||||
info = QLabel("What were you doing when this crash/bug happened?")
|
info = QLabel("What were you doing when this crash/bug happened?")
|
||||||
self._vbox.addWidget(info)
|
self._vbox.addWidget(info)
|
||||||
@ -201,6 +186,26 @@ class _CrashDialog(QDialog):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return utils.get_repr(self)
|
return utils.get_repr(self)
|
||||||
|
|
||||||
|
def _init_contact_input(self):
|
||||||
|
"""Initialize the widget asking for contact info."""
|
||||||
|
contact = QLabel("I'd like to be able to follow up with you, to keep "
|
||||||
|
"you posted on the status of this crash and get more "
|
||||||
|
"information if I need it - how can I contact you?",
|
||||||
|
wordWrap=True)
|
||||||
|
self._vbox.addWidget(contact)
|
||||||
|
self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False)
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
info = configfiles.state['general']['contact-info']
|
||||||
|
except KeyError:
|
||||||
|
self._contact.setPlaceholderText("Mail or IRC nickname")
|
||||||
|
else:
|
||||||
|
self._contact.setPlainText(info)
|
||||||
|
except Exception:
|
||||||
|
log.misc.exception("Failed to get contact information!")
|
||||||
|
self._contact.setPlaceholderText("Mail or IRC nickname")
|
||||||
|
self._vbox.addWidget(self._contact, 2)
|
||||||
|
|
||||||
def _init_text(self):
|
def _init_text(self):
|
||||||
"""Initialize the main text to be displayed on an exception.
|
"""Initialize the main text to be displayed on an exception.
|
||||||
|
|
||||||
@ -296,8 +301,8 @@ class _CrashDialog(QDialog):
|
|||||||
def _save_contact_info(self):
|
def _save_contact_info(self):
|
||||||
"""Save the contact info to disk."""
|
"""Save the contact info to disk."""
|
||||||
try:
|
try:
|
||||||
state = objreg.get('state-config')
|
info = self._contact.toPlainText()
|
||||||
state['general']['contact-info'] = self._contact.toPlainText()
|
configfiles.state['general']['contact-info'] = info
|
||||||
except Exception:
|
except Exception:
|
||||||
log.misc.exception("Failed to save contact information!")
|
log.misc.exception("Failed to save contact information!")
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
"""Things which need to be done really early (e.g. before importing Qt).
|
"""Things which need to be done really early (e.g. before importing Qt).
|
||||||
|
|
||||||
At this point we can be sure we have all python 3.4 features available.
|
At this point we can be sure we have all python 3.5 features available.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -29,7 +29,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
hunter = None
|
hunter = None
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import traceback
|
import traceback
|
||||||
@ -41,8 +40,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
tkinter = None
|
tkinter = None
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
# NOTE: No qutebrowser or PyQt import should be done here, as some early
|
# NOTE: No qutebrowser or PyQt import should be done here, as some early
|
||||||
# initialization needs to take place before that!
|
# initialization needs to take place before that!
|
||||||
|
|
||||||
@ -68,7 +65,7 @@ def _missing_str(name, *, windows=None, pip=None, webengine=False):
|
|||||||
if not webengine:
|
if not webengine:
|
||||||
lines = ['<b>If you installed a qutebrowser package for your '
|
lines = ['<b>If you installed a qutebrowser package for your '
|
||||||
'distribution, please report this as a bug.</b>']
|
'distribution, please report this as a bug.</b>']
|
||||||
blocks.append('<br />'.join(lines))
|
blocks.append('<br />'.join(lines))
|
||||||
if windows is not None:
|
if windows is not None:
|
||||||
lines = ["<b>On Windows:</b>"]
|
lines = ["<b>On Windows:</b>"]
|
||||||
lines += windows.splitlines()
|
lines += windows.splitlines()
|
||||||
@ -140,73 +137,6 @@ def init_faulthandler(fileobj=sys.__stderr__):
|
|||||||
faulthandler.register(signal.SIGUSR1)
|
faulthandler.register(signal.SIGUSR1)
|
||||||
|
|
||||||
|
|
||||||
def _qt_version():
|
|
||||||
"""Get the running Qt version.
|
|
||||||
|
|
||||||
Needs to be in a function so we can do a local import easily (to not import
|
|
||||||
from QtCore too early) but can patch this out easily for tests.
|
|
||||||
"""
|
|
||||||
from PyQt5.QtCore import qVersion
|
|
||||||
return pkg_resources.parse_version(qVersion())
|
|
||||||
|
|
||||||
|
|
||||||
def fix_harfbuzz(args):
|
|
||||||
"""Fix harfbuzz issues.
|
|
||||||
|
|
||||||
This switches to the most stable harfbuzz font rendering engine available
|
|
||||||
on the platform instead of using the system wide one.
|
|
||||||
|
|
||||||
This fixes crashes on various sites.
|
|
||||||
|
|
||||||
- On Qt 5.2 (and probably earlier) the new engine probably has more
|
|
||||||
crashes and is also experimental.
|
|
||||||
|
|
||||||
e.g. https://bugreports.qt.io/browse/QTBUG-36099
|
|
||||||
|
|
||||||
- On Qt 5.3.0 there's a bug that affects a lot of websites:
|
|
||||||
https://bugreports.qt.io/browse/QTBUG-39278
|
|
||||||
So the new engine will be more stable.
|
|
||||||
|
|
||||||
- On Qt 5.3.1 this bug is fixed and the old engine will be the more stable
|
|
||||||
one again.
|
|
||||||
|
|
||||||
- On Qt 5.4 the new engine is the default and most bugs are taken care of.
|
|
||||||
|
|
||||||
IMPORTANT: This needs to be done before QWidgets is imported in any way!
|
|
||||||
|
|
||||||
WORKAROUND (remove this when we bump the requirements to 5.3.1)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
args: The argparse namespace.
|
|
||||||
"""
|
|
||||||
from qutebrowser.utils import log
|
|
||||||
if 'PyQt5.QtWidgets' in sys.modules:
|
|
||||||
msg = "Harfbuzz fix attempted but QtWidgets is already imported!"
|
|
||||||
if getattr(sys, 'frozen', False):
|
|
||||||
log.init.debug(msg)
|
|
||||||
else:
|
|
||||||
log.init.warning(msg)
|
|
||||||
if sys.platform.startswith('linux') and args.harfbuzz == 'auto':
|
|
||||||
if _qt_version() == pkg_resources.parse_version('5.3.0'):
|
|
||||||
log.init.debug("Using new harfbuzz engine (auto)")
|
|
||||||
os.environ['QT_HARFBUZZ'] = 'new'
|
|
||||||
elif _qt_version() < pkg_resources.parse_version('5.4.0'):
|
|
||||||
log.init.debug("Using old harfbuzz engine (auto)")
|
|
||||||
os.environ['QT_HARFBUZZ'] = 'old'
|
|
||||||
else:
|
|
||||||
log.init.debug("Using system harfbuzz engine (auto)")
|
|
||||||
elif args.harfbuzz in ['old', 'new']:
|
|
||||||
# forced harfbuzz variant
|
|
||||||
# FIXME looking at the Qt code, 'new' isn't a valid value, but leaving
|
|
||||||
# it empty and using new yields different behavior...
|
|
||||||
# (probably irrelevant when workaround gets removed)
|
|
||||||
log.init.debug("Using {} harfbuzz engine (forced)".format(
|
|
||||||
args.harfbuzz))
|
|
||||||
os.environ['QT_HARFBUZZ'] = args.harfbuzz
|
|
||||||
else:
|
|
||||||
log.init.debug("Using system harfbuzz engine")
|
|
||||||
|
|
||||||
|
|
||||||
def check_pyqt_core():
|
def check_pyqt_core():
|
||||||
"""Check if PyQt core is installed."""
|
"""Check if PyQt core is installed."""
|
||||||
try:
|
try:
|
||||||
@ -233,26 +163,6 @@ def check_pyqt_core():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def get_backend(args):
|
|
||||||
"""Find out what backend to use based on available libraries.
|
|
||||||
|
|
||||||
Note this function returns the backend as a string so we don't have to
|
|
||||||
import qutebrowser.utils.usertypes yet.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import PyQt5.QtWebKit # pylint: disable=unused-variable
|
|
||||||
webkit_available = True
|
|
||||||
except ImportError:
|
|
||||||
webkit_available = False
|
|
||||||
|
|
||||||
if args.backend is not None:
|
|
||||||
return args.backend
|
|
||||||
elif webkit_available:
|
|
||||||
return 'webkit'
|
|
||||||
else:
|
|
||||||
return 'webengine'
|
|
||||||
|
|
||||||
|
|
||||||
def qt_version(qversion=None, qt_version_str=None):
|
def qt_version(qversion=None, qt_version_str=None):
|
||||||
"""Get a Qt version string based on the runtime/compiled versions."""
|
"""Get a Qt version string based on the runtime/compiled versions."""
|
||||||
if qversion is None:
|
if qversion is None:
|
||||||
@ -268,50 +178,65 @@ def qt_version(qversion=None, qt_version_str=None):
|
|||||||
return qversion
|
return qversion
|
||||||
|
|
||||||
|
|
||||||
def check_qt_version(backend):
|
def check_qt_version():
|
||||||
"""Check if the Qt version is recent enough."""
|
"""Check if the Qt version is recent enough."""
|
||||||
from PyQt5.QtCore import PYQT_VERSION, PYQT_VERSION_STR
|
from PyQt5.QtCore import PYQT_VERSION, PYQT_VERSION_STR
|
||||||
from qutebrowser.utils import qtutils
|
from qutebrowser.utils import qtutils
|
||||||
if (not qtutils.version_check('5.2.0', strict=True) or
|
if (not qtutils.version_check('5.7.1', strict=True) or
|
||||||
PYQT_VERSION < 0x050200):
|
PYQT_VERSION < 0x050200):
|
||||||
text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but Qt {} / "
|
text = ("Fatal error: Qt >= 5.7.1 and PyQt >= 5.7 are required, "
|
||||||
"PyQt {} is installed.".format(qt_version(),
|
"but Qt {} / PyQt {} is installed.".format(qt_version(),
|
||||||
PYQT_VERSION_STR))
|
PYQT_VERSION_STR))
|
||||||
_die(text)
|
|
||||||
elif (backend == 'webengine' and (
|
|
||||||
not qtutils.version_check('5.7.1', strict=True) or
|
|
||||||
PYQT_VERSION < 0x050700)):
|
|
||||||
text = ("Fatal error: Qt >= 5.7.1 and PyQt >= 5.7 are required for "
|
|
||||||
"QtWebEngine support, but Qt {} / PyQt {} is installed."
|
|
||||||
.format(qt_version(), PYQT_VERSION_STR))
|
|
||||||
_die(text)
|
_die(text)
|
||||||
|
|
||||||
|
|
||||||
def check_ssl_support(backend):
|
def check_ssl_support():
|
||||||
"""Check if SSL support is available."""
|
"""Check if SSL support is available."""
|
||||||
from qutebrowser.utils import log
|
# pylint: disable=unused-variable
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PyQt5.QtNetwork import QSslSocket
|
from PyQt5.QtNetwork import QSslSocket
|
||||||
except ImportError:
|
except ImportError:
|
||||||
_die("Fatal error: Your Qt is built without SSL support.")
|
_die("Fatal error: Your Qt is built without SSL support.")
|
||||||
|
|
||||||
|
|
||||||
|
def check_backend_ssl_support(backend):
|
||||||
|
"""Check for full SSL availability when we know the backend."""
|
||||||
|
from PyQt5.QtNetwork import QSslSocket
|
||||||
|
from qutebrowser.utils import log, usertypes
|
||||||
text = ("Could not initialize QtNetwork SSL support. If you use "
|
text = ("Could not initialize QtNetwork SSL support. If you use "
|
||||||
"OpenSSL 1.1 with a PyQt package from PyPI (e.g. on Archlinux "
|
"OpenSSL 1.1 with a PyQt package from PyPI (e.g. on Archlinux "
|
||||||
"or Debian Stretch), you need to set LD_LIBRARY_PATH to the path "
|
"or Debian Stretch), you need to set LD_LIBRARY_PATH to the path "
|
||||||
"of OpenSSL 1.0.")
|
"of OpenSSL 1.0. This only affects downloads.")
|
||||||
if backend == 'webengine':
|
|
||||||
text += " This only affects downloads."
|
|
||||||
|
|
||||||
if not QSslSocket.supportsSsl():
|
if not QSslSocket.supportsSsl():
|
||||||
if backend == 'webkit':
|
if backend == usertypes.Backend.QtWebKit:
|
||||||
_die("Could not initialize SSL support.")
|
_die("Could not initialize SSL support.")
|
||||||
else:
|
else:
|
||||||
assert backend == 'webengine'
|
assert backend == usertypes.Backend.QtWebEngine
|
||||||
log.init.warning(text)
|
log.init.warning(text)
|
||||||
|
|
||||||
|
|
||||||
def check_libraries(backend):
|
def _check_modules(modules):
|
||||||
|
"""Make sure the given modules are available."""
|
||||||
|
from qutebrowser.utils import log
|
||||||
|
|
||||||
|
for name, text in modules.items():
|
||||||
|
try:
|
||||||
|
# https://github.com/pallets/jinja/pull/628
|
||||||
|
# https://bitbucket.org/birkenfeld/pygments-main/issues/1314/
|
||||||
|
# https://github.com/pallets/jinja/issues/646
|
||||||
|
# https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e
|
||||||
|
messages = ['invalid escape sequence',
|
||||||
|
'Flags not at the start of the expression']
|
||||||
|
with log.ignore_py_warnings(
|
||||||
|
category=DeprecationWarning,
|
||||||
|
message=r'({})'.format('|'.join(messages))):
|
||||||
|
importlib.import_module(name)
|
||||||
|
except ImportError as e:
|
||||||
|
_die(text, e)
|
||||||
|
|
||||||
|
|
||||||
|
def check_libraries():
|
||||||
"""Check if all needed Python libraries are installed."""
|
"""Check if all needed Python libraries are installed."""
|
||||||
modules = {
|
modules = {
|
||||||
'pkg_resources':
|
'pkg_resources':
|
||||||
@ -338,33 +263,38 @@ def check_libraries(backend):
|
|||||||
pip="PyYAML"),
|
pip="PyYAML"),
|
||||||
'PyQt5.QtQml': _missing_str("PyQt5.QtQml"),
|
'PyQt5.QtQml': _missing_str("PyQt5.QtQml"),
|
||||||
'PyQt5.QtSql': _missing_str("PyQt5.QtSql"),
|
'PyQt5.QtSql': _missing_str("PyQt5.QtSql"),
|
||||||
|
'PyQt5.QtOpenGL': _missing_str("PyQt5.QtOpenGL"),
|
||||||
}
|
}
|
||||||
if backend == 'webengine':
|
_check_modules(modules)
|
||||||
modules['PyQt5.QtWebEngineWidgets'] = _missing_str("QtWebEngine",
|
|
||||||
webengine=True)
|
|
||||||
modules['PyQt5.QtOpenGL'] = _missing_str("PyQt5.QtOpenGL")
|
def check_backend_libraries(backend):
|
||||||
|
"""Make sure the libraries needed by the given backend are available.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
backend: The backend as usertypes.Backend member.
|
||||||
|
"""
|
||||||
|
from qutebrowser.utils import usertypes
|
||||||
|
if backend == usertypes.Backend.QtWebEngine:
|
||||||
|
modules = {
|
||||||
|
'PyQt5.QtWebEngineWidgets':
|
||||||
|
_missing_str("QtWebEngine", webengine=True),
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
assert backend == 'webkit'
|
assert backend == usertypes.Backend.QtWebKit, backend
|
||||||
modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit")
|
modules = {
|
||||||
modules['PyQt5.QtWebKitWidgets'] = _missing_str(
|
'PyQt5.QtWebKit': _missing_str("PyQt5.QtWebKit"),
|
||||||
"PyQt5.QtWebKitWidgets")
|
'PyQt5.QtWebKitWidgets': _missing_str("PyQt5.QtWebKitWidgets"),
|
||||||
|
}
|
||||||
|
_check_modules(modules)
|
||||||
|
|
||||||
from qutebrowser.utils import log
|
|
||||||
|
|
||||||
for name, text in modules.items():
|
def check_new_webkit(backend):
|
||||||
try:
|
"""Make sure we use QtWebEngine or a new QtWebKit."""
|
||||||
# https://github.com/pallets/jinja/pull/628
|
from qutebrowser.utils import usertypes, qtutils
|
||||||
# https://bitbucket.org/birkenfeld/pygments-main/issues/1314/
|
if backend == usertypes.Backend.QtWebKit and not qtutils.is_new_qtwebkit():
|
||||||
# https://github.com/pallets/jinja/issues/646
|
_die("qutebrowser does not support legacy QtWebKit versions anymore, "
|
||||||
# https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e
|
"see the installation docs for details.")
|
||||||
messages = ['invalid escape sequence',
|
|
||||||
'Flags not at the start of the expression']
|
|
||||||
with log.ignore_py_warnings(
|
|
||||||
category=DeprecationWarning,
|
|
||||||
message=r'({})'.format('|'.join(messages))):
|
|
||||||
importlib.import_module(name)
|
|
||||||
except ImportError as e:
|
|
||||||
_die(text, e)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_inputhook():
|
def remove_inputhook():
|
||||||
@ -395,18 +325,7 @@ def check_optimize_flag():
|
|||||||
"unexpected behavior may occur.")
|
"unexpected behavior may occur.")
|
||||||
|
|
||||||
|
|
||||||
def set_backend(backend):
|
def early_init(args):
|
||||||
"""Set the objects.backend global to the given backend (as string)."""
|
|
||||||
from qutebrowser.misc import objects
|
|
||||||
from qutebrowser.utils import usertypes
|
|
||||||
backends = {
|
|
||||||
'webkit': usertypes.Backend.QtWebKit,
|
|
||||||
'webengine': usertypes.Backend.QtWebEngine,
|
|
||||||
}
|
|
||||||
objects.backend = backends[backend]
|
|
||||||
|
|
||||||
|
|
||||||
def earlyinit(args):
|
|
||||||
"""Do all needed early initialization.
|
"""Do all needed early initialization.
|
||||||
|
|
||||||
Note that it's vital the other earlyinit functions get called in the right
|
Note that it's vital the other earlyinit functions get called in the right
|
||||||
@ -423,15 +342,23 @@ def earlyinit(args):
|
|||||||
check_pyqt_core()
|
check_pyqt_core()
|
||||||
# Init logging as early as possible
|
# Init logging as early as possible
|
||||||
init_log(args)
|
init_log(args)
|
||||||
# Now the faulthandler is enabled we fix the Qt harfbuzzing library, before
|
|
||||||
# importing QtWidgets.
|
|
||||||
fix_harfbuzz(args)
|
|
||||||
# Now we can be sure QtCore is available, so we can print dialogs on
|
# Now we can be sure QtCore is available, so we can print dialogs on
|
||||||
# errors, so people only using the GUI notice them as well.
|
# errors, so people only using the GUI notice them as well.
|
||||||
backend = get_backend(args)
|
check_libraries()
|
||||||
check_qt_version(backend)
|
check_qt_version()
|
||||||
remove_inputhook()
|
remove_inputhook()
|
||||||
check_libraries(backend)
|
check_ssl_support()
|
||||||
check_ssl_support(backend)
|
|
||||||
check_optimize_flag()
|
check_optimize_flag()
|
||||||
set_backend(backend)
|
|
||||||
|
|
||||||
|
def init_with_backend(backend):
|
||||||
|
"""Do later stages of init when we know the backend.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
backend: The backend as usertypes.Backend member.
|
||||||
|
"""
|
||||||
|
assert not isinstance(backend, str), backend
|
||||||
|
assert backend is not None
|
||||||
|
check_backend_libraries(backend)
|
||||||
|
check_backend_ssl_support(backend)
|
||||||
|
check_new_webkit(backend)
|
||||||
|
@ -22,5 +22,14 @@
|
|||||||
# NOTE: We need to be careful with imports here, as this is imported from
|
# NOTE: We need to be careful with imports here, as this is imported from
|
||||||
# earlyinit.
|
# earlyinit.
|
||||||
|
|
||||||
|
|
||||||
|
class NoBackend:
|
||||||
|
|
||||||
|
"""Special object when there's no backend set so we notice that."""
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
raise AssertionError("No backend set!")
|
||||||
|
|
||||||
|
|
||||||
# A usertypes.Backend member
|
# A usertypes.Backend member
|
||||||
backend = None
|
backend = NoBackend()
|
||||||
|
@ -113,19 +113,12 @@ class SaveManager(QObject):
|
|||||||
self.saveables = collections.OrderedDict()
|
self.saveables = collections.OrderedDict()
|
||||||
self._save_timer = usertypes.Timer(self, name='save-timer')
|
self._save_timer = usertypes.Timer(self, name='save-timer')
|
||||||
self._save_timer.timeout.connect(self.autosave)
|
self._save_timer.timeout.connect(self.autosave)
|
||||||
|
self._set_autosave_interval()
|
||||||
|
config.instance.changed.connect(self._set_autosave_interval)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return utils.get_repr(self, saveables=self.saveables)
|
return utils.get_repr(self, saveables=self.saveables)
|
||||||
|
|
||||||
def init_autosave(self):
|
|
||||||
"""Initialize auto-saving.
|
|
||||||
|
|
||||||
We don't do this in __init__ because the config needs to be initialized
|
|
||||||
first, but the config needs the save manager.
|
|
||||||
"""
|
|
||||||
self._set_autosave_interval()
|
|
||||||
config.instance.changed.connect(self._set_autosave_interval)
|
|
||||||
|
|
||||||
@config.change_filter('auto_save.interval')
|
@config.change_filter('auto_save.interval')
|
||||||
def _set_autosave_interval(self):
|
def _set_autosave_interval(self):
|
||||||
"""Set the auto-save interval."""
|
"""Set the auto-save interval."""
|
||||||
|
@ -30,8 +30,9 @@ import yaml
|
|||||||
from qutebrowser.utils import (standarddir, objreg, qtutils, log, message,
|
from qutebrowser.utils import (standarddir, objreg, qtutils, log, message,
|
||||||
utils)
|
utils)
|
||||||
from qutebrowser.commands import cmdexc, cmdutils
|
from qutebrowser.commands import cmdexc, cmdutils
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config, configfiles
|
||||||
from qutebrowser.completion.models import miscmodels
|
from qutebrowser.completion.models import miscmodels
|
||||||
|
from qutebrowser.mainwindow import mainwindow
|
||||||
|
|
||||||
|
|
||||||
default = object() # Sentinel value
|
default = object() # Sentinel value
|
||||||
@ -294,8 +295,7 @@ class SessionManager(QObject):
|
|||||||
raise SessionError(e)
|
raise SessionError(e)
|
||||||
|
|
||||||
if load_next_time:
|
if load_next_time:
|
||||||
state_config = objreg.get('state-config')
|
configfiles.state['general']['session'] = name
|
||||||
state_config['general']['session'] = name
|
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def save_autosave(self):
|
def save_autosave(self):
|
||||||
@ -372,7 +372,6 @@ class SessionManager(QObject):
|
|||||||
name: The name of the session to load.
|
name: The name of the session to load.
|
||||||
temp: If given, don't set the current session.
|
temp: If given, don't set the current session.
|
||||||
"""
|
"""
|
||||||
from qutebrowser.mainwindow import mainwindow
|
|
||||||
path = self._get_session_path(name, check_exists=True)
|
path = self._get_session_path(name, check_exists=True)
|
||||||
try:
|
try:
|
||||||
with open(path, encoding='utf-8') as f:
|
with open(path, encoding='utf-8') as f:
|
||||||
|
@ -95,9 +95,6 @@ def get_argparser():
|
|||||||
action='store_false', dest='color')
|
action='store_false', dest='color')
|
||||||
debug.add_argument('--force-color', help="Force colored logging",
|
debug.add_argument('--force-color', help="Force colored logging",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
debug.add_argument('--harfbuzz', choices=['old', 'new', 'system', 'auto'],
|
|
||||||
default='auto', help="HarfBuzz engine version to use. "
|
|
||||||
"Default: auto.")
|
|
||||||
debug.add_argument('--relaxed-config', action='store_true',
|
debug.add_argument('--relaxed-config', action='store_true',
|
||||||
help="Silently remove unknown config options.")
|
help="Silently remove unknown config options.")
|
||||||
debug.add_argument('--nowindow', action='store_true', help="Don't show "
|
debug.add_argument('--nowindow', action='store_true', help="Don't show "
|
||||||
@ -170,8 +167,8 @@ def main():
|
|||||||
# from json.
|
# from json.
|
||||||
data = json.loads(args.json_args)
|
data = json.loads(args.json_args)
|
||||||
args = argparse.Namespace(**data)
|
args = argparse.Namespace(**data)
|
||||||
earlyinit.earlyinit(args)
|
earlyinit.early_init(args)
|
||||||
# We do this imports late as earlyinit needs to be run first (because of
|
# We do this imports late as earlyinit needs to be run first (because of
|
||||||
# the harfbuzz fix and version checking).
|
# version checking and other early initialization)
|
||||||
from qutebrowser import app
|
from qutebrowser import app
|
||||||
return app.run(args)
|
return app.run(args)
|
||||||
|
@ -63,7 +63,7 @@ class DocstringParser:
|
|||||||
|
|
||||||
"""Generate documentation based on a docstring of a command handler.
|
"""Generate documentation based on a docstring of a command handler.
|
||||||
|
|
||||||
The docstring needs to follow the format described in CONTRIBUTING.
|
The docstring needs to follow the format described in doc/contributing.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
_state: The current state of the parser state machine.
|
_state: The current state of the parser state machine.
|
||||||
|
@ -345,7 +345,7 @@ def qt_message_handler(msg_type, context, msg):
|
|||||||
try:
|
try:
|
||||||
qt_to_logging[QtCore.QtInfoMsg] = logging.INFO
|
qt_to_logging[QtCore.QtInfoMsg] = logging.INFO
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Qt < 5.5
|
# While we don't support Qt < 5.5 anymore, logging still needs to work
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Change levels of some well-known messages to debug so they don't get
|
# Change levels of some well-known messages to debug so they don't get
|
||||||
|
@ -105,13 +105,9 @@ class ObjectRegistry(collections.UserDict):
|
|||||||
func = partial_objs[name]
|
func = partial_objs[name]
|
||||||
try:
|
try:
|
||||||
self[name].destroyed.disconnect(func)
|
self[name].destroyed.disconnect(func)
|
||||||
except (RuntimeError, TypeError):
|
except RuntimeError:
|
||||||
# If C++ has deleted the object, the slot is already
|
# If C++ has deleted the object, the slot is already
|
||||||
# disconnected.
|
# disconnected.
|
||||||
#
|
|
||||||
# With older PyQt-versions (5.2.1) we'll get a "TypeError:
|
|
||||||
# pyqtSignal must be bound to a QObject" instead:
|
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/257
|
|
||||||
pass
|
pass
|
||||||
del partial_objs[name]
|
del partial_objs[name]
|
||||||
|
|
||||||
@ -145,7 +141,7 @@ class ObjectRegistry(collections.UserDict):
|
|||||||
for name, obj in self.data.items():
|
for name, obj in self.data.items():
|
||||||
try:
|
try:
|
||||||
obj_repr = repr(obj)
|
obj_repr = repr(obj)
|
||||||
except (RuntimeError, TypeError):
|
except RuntimeError:
|
||||||
# Underlying object deleted probably
|
# Underlying object deleted probably
|
||||||
obj_repr = '<deleted>'
|
obj_repr = '<deleted>'
|
||||||
lines.append("{}: {}".format(name, obj_repr))
|
lines.append("{}: {}".format(name, obj_repr))
|
||||||
|
@ -28,30 +28,17 @@ Module attributes:
|
|||||||
|
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import operator
|
import operator
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
|
import pkg_resources
|
||||||
from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray,
|
from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray,
|
||||||
QIODevice, QSaveFile, QT_VERSION_STR)
|
QIODevice, QSaveFile, QT_VERSION_STR)
|
||||||
from PyQt5.QtWidgets import QApplication
|
|
||||||
try:
|
try:
|
||||||
from PyQt5.QtWebKit import qWebKitVersion
|
from PyQt5.QtWebKit import qWebKitVersion
|
||||||
except ImportError: # pragma: no cover
|
except ImportError: # pragma: no cover
|
||||||
qWebKitVersion = None
|
qWebKitVersion = None
|
||||||
|
|
||||||
from qutebrowser.utils import log
|
|
||||||
|
|
||||||
with log.ignore_py_warnings(category=PendingDeprecationWarning, module='imp'):
|
|
||||||
with log.ignore_py_warnings(category=ImportWarning):
|
|
||||||
# This imports 'imp' and gives us a PendingDeprecationWarning on
|
|
||||||
# Debian Jessie.
|
|
||||||
#
|
|
||||||
# On Archlinux, we get ImportWarning from
|
|
||||||
# importlib/_bootstrap_external.py for modules with missing __init__.
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
|
|
||||||
MAXVALS = {
|
MAXVALS = {
|
||||||
'int': 2 ** 31 - 1,
|
'int': 2 ** 31 - 1,
|
||||||
@ -104,8 +91,8 @@ def version_check(version, exact=False, strict=False):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def is_qtwebkit_ng():
|
def is_new_qtwebkit():
|
||||||
"""Check if the given version is QtWebKit-NG."""
|
"""Check if the given version is a new QtWebKit."""
|
||||||
assert qWebKitVersion is not None
|
assert qWebKitVersion is not None
|
||||||
return (pkg_resources.parse_version(qWebKitVersion()) >
|
return (pkg_resources.parse_version(qWebKitVersion()) >
|
||||||
pkg_resources.parse_version('538.1'))
|
pkg_resources.parse_version('538.1'))
|
||||||
@ -139,36 +126,6 @@ def check_overflow(arg, ctype, fatal=True):
|
|||||||
return arg
|
return arg
|
||||||
|
|
||||||
|
|
||||||
def get_args(namespace):
|
|
||||||
"""Get the Qt QApplication arguments based on an argparse namespace.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
namespace: The argparse namespace.
|
|
||||||
|
|
||||||
Return:
|
|
||||||
The argv list to be passed to Qt.
|
|
||||||
"""
|
|
||||||
argv = [sys.argv[0]]
|
|
||||||
|
|
||||||
if namespace.qt_flag is not None:
|
|
||||||
argv += ['--' + flag[0] for flag in namespace.qt_flag]
|
|
||||||
|
|
||||||
if namespace.qt_arg is not None:
|
|
||||||
for name, value in namespace.qt_arg:
|
|
||||||
argv += ['--' + name, value]
|
|
||||||
|
|
||||||
return argv
|
|
||||||
|
|
||||||
|
|
||||||
def check_print_compat():
|
|
||||||
"""Check if printing should work in the given Qt version."""
|
|
||||||
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
|
|
||||||
if os.name == 'nt':
|
|
||||||
return version_check('5.3')
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_valid(obj):
|
def ensure_valid(obj):
|
||||||
"""Ensure a Qt object with an .isValid() method is valid."""
|
"""Ensure a Qt object with an .isValid() method is valid."""
|
||||||
if not obj.isValid():
|
if not obj.isValid():
|
||||||
@ -242,23 +199,6 @@ def savefile_open(filename, binary=False, encoding='utf-8'):
|
|||||||
raise QtOSError(f, msg="Commit failed!")
|
raise QtOSError(f, msg="Commit failed!")
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def unset_organization():
|
|
||||||
"""Temporarily unset QApplication.organizationName().
|
|
||||||
|
|
||||||
This is primarily needed in config.py.
|
|
||||||
"""
|
|
||||||
qapp = QApplication.instance()
|
|
||||||
if qapp is not None:
|
|
||||||
orgname = qapp.organizationName()
|
|
||||||
qapp.setOrganizationName(None)
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
if qapp is not None:
|
|
||||||
qapp.setOrganizationName(orgname)
|
|
||||||
|
|
||||||
|
|
||||||
class PyQIODevice(io.BufferedIOBase):
|
class PyQIODevice(io.BufferedIOBase):
|
||||||
|
|
||||||
"""Wrapper for a QIODevice which provides a python interface.
|
"""Wrapper for a QIODevice which provides a python interface.
|
||||||
|
@ -23,10 +23,12 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
import os.path
|
import os.path
|
||||||
|
import contextlib
|
||||||
|
|
||||||
from PyQt5.QtCore import QStandardPaths
|
from PyQt5.QtCore import QStandardPaths
|
||||||
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from qutebrowser.utils import log, qtutils, debug, usertypes, message
|
from qutebrowser.utils import log, debug, usertypes, message
|
||||||
|
|
||||||
# The cached locations
|
# The cached locations
|
||||||
_locations = {}
|
_locations = {}
|
||||||
@ -45,6 +47,23 @@ class EmptyValueError(Exception):
|
|||||||
"""Error raised when QStandardPaths returns an empty value."""
|
"""Error raised when QStandardPaths returns an empty value."""
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _unset_organization():
|
||||||
|
"""Temporarily unset QApplication.organizationName().
|
||||||
|
|
||||||
|
This is primarily needed in config.py.
|
||||||
|
"""
|
||||||
|
qapp = QApplication.instance()
|
||||||
|
if qapp is not None:
|
||||||
|
orgname = qapp.organizationName()
|
||||||
|
qapp.setOrganizationName(None)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
if qapp is not None:
|
||||||
|
qapp.setOrganizationName(orgname)
|
||||||
|
|
||||||
|
|
||||||
def _init_config(args):
|
def _init_config(args):
|
||||||
"""Initialize the location for configs."""
|
"""Initialize the location for configs."""
|
||||||
typ = QStandardPaths.ConfigLocation
|
typ = QStandardPaths.ConfigLocation
|
||||||
@ -204,7 +223,7 @@ def _writable_location(typ):
|
|||||||
# FIXME old Qt
|
# FIXME old Qt
|
||||||
getattr(QStandardPaths, 'AppDataLocation', object())], typ_str
|
getattr(QStandardPaths, 'AppDataLocation', object())], typ_str
|
||||||
|
|
||||||
with qtutils.unset_organization():
|
with _unset_organization():
|
||||||
path = QStandardPaths.writableLocation(typ)
|
path = QStandardPaths.writableLocation(typ)
|
||||||
|
|
||||||
log.misc.debug("writable location for {}: {}".format(typ_str, path))
|
log.misc.debug("writable location for {}: {}".format(typ_str, path))
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
||||||
#
|
|
||||||
# This file is part of qutebrowser.
|
|
||||||
#
|
|
||||||
# qutebrowser is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# qutebrowser is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# pylint: disable=unused-import,bad-mcs-method-argument
|
|
||||||
|
|
||||||
"""Wrapper for Python 3.5's typing module.
|
|
||||||
|
|
||||||
This wrapper is needed as both Python 3.5 and typing for PyPI isn't commonly
|
|
||||||
packaged yet. As we don't actually need anything from the typing module at
|
|
||||||
runtime, we instead mock the typing classes (using objects to make things
|
|
||||||
easier) so the typing module isn't a hard dependency.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Those are defined here to make them testable easily
|
|
||||||
|
|
||||||
|
|
||||||
class FakeTypingMeta(type):
|
|
||||||
|
|
||||||
"""Fake typing metaclass like typing.TypingMeta."""
|
|
||||||
|
|
||||||
def __init__(self, *args, # pylint: disable=super-init-not-called
|
|
||||||
**_kwds):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class FakeUnionMeta(FakeTypingMeta):
|
|
||||||
|
|
||||||
"""Fake union metaclass metaclass like typing.UnionMeta."""
|
|
||||||
|
|
||||||
def __new__(cls, name, bases, namespace, parameters=None):
|
|
||||||
if parameters is None:
|
|
||||||
return super().__new__(cls, name, bases, namespace)
|
|
||||||
self = super().__new__(cls, name, bases, {})
|
|
||||||
self.__union_params__ = tuple(parameters)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __getitem__(self, parameters):
|
|
||||||
return self.__class__(self.__name__, self.__bases__,
|
|
||||||
dict(self.__dict__), parameters=parameters)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeUnion(metaclass=FakeUnionMeta):
|
|
||||||
|
|
||||||
"""Fake Union type like typing.Union."""
|
|
||||||
|
|
||||||
__union_params__ = None
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
from typing import Union
|
|
||||||
except ImportError: # pragma: no cover
|
|
||||||
Union = FakeUnion
|
|
@ -355,14 +355,8 @@ class Question(QObject):
|
|||||||
log.misc.debug("Question was already aborted")
|
log.misc.debug("Question was already aborted")
|
||||||
return
|
return
|
||||||
self.is_aborted = True
|
self.is_aborted = True
|
||||||
try:
|
self.aborted.emit()
|
||||||
self.aborted.emit()
|
self.completed.emit()
|
||||||
self.completed.emit()
|
|
||||||
except TypeError:
|
|
||||||
# WORKAROUND
|
|
||||||
# We seem to get "pyqtSignal must be bound to a QObject, not
|
|
||||||
# 'Question' here, which makes no sense at all..."
|
|
||||||
log.misc.exception("Error while aborting question")
|
|
||||||
|
|
||||||
|
|
||||||
class Timer(QTimer):
|
class Timer(QTimer):
|
||||||
|
@ -42,7 +42,7 @@ except ImportError: # pragma: no cover
|
|||||||
from yaml import SafeLoader as YamlLoader, SafeDumper as YamlDumper
|
from yaml import SafeLoader as YamlLoader, SafeDumper as YamlDumper
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import qtutils, log
|
from qutebrowser.utils import qtutils, log, debug
|
||||||
|
|
||||||
|
|
||||||
fake_clipboard = None
|
fake_clipboard = None
|
||||||
@ -425,14 +425,13 @@ class KeyInfo:
|
|||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
# Meh, dependency cycle...
|
|
||||||
from qutebrowser.utils.debug import qenum_key
|
|
||||||
if self.modifiers is None:
|
if self.modifiers is None:
|
||||||
modifiers = None
|
modifiers = None
|
||||||
else:
|
else:
|
||||||
#modifiers = qflags_key(Qt, self.modifiers)
|
#modifiers = qflags_key(Qt, self.modifiers)
|
||||||
modifiers = hex(int(self.modifiers))
|
modifiers = hex(int(self.modifiers))
|
||||||
return get_repr(self, constructor=True, key=qenum_key(Qt, self.key),
|
return get_repr(self, constructor=True,
|
||||||
|
key=debug.qenum_key(Qt, self.key),
|
||||||
modifiers=modifiers, text=self.text)
|
modifiers=modifiers, text=self.text)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
@ -814,10 +813,12 @@ def open_file(filename, cmdline=None):
|
|||||||
the filename is appended to the cmdline.
|
the filename is appended to the cmdline.
|
||||||
"""
|
"""
|
||||||
# Import late to avoid circular imports:
|
# Import late to avoid circular imports:
|
||||||
# utils -> config -> configdata -> configtypes -> cmdutils -> command ->
|
# - usertypes -> utils -> guiprocess -> message -> usertypes
|
||||||
# utils
|
# - usertypes -> utils -> config -> configdata -> configtypes ->
|
||||||
from qutebrowser.misc import guiprocess
|
# cmdutils -> command -> message -> usertypes
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
|
from qutebrowser.misc import guiprocess
|
||||||
|
|
||||||
# the default program to open downloads with - will be empty string
|
# the default program to open downloads with - will be empty string
|
||||||
# if we want to use the default
|
# if we want to use the default
|
||||||
override = config.val.downloads.open_dispatcher
|
override = config.val.downloads.open_dispatcher
|
||||||
|
@ -44,7 +44,7 @@ except ImportError: # pragma: no cover
|
|||||||
QWebEngineProfile = None
|
QWebEngineProfile = None
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import log, utils, standarddir, usertypes, qtutils
|
from qutebrowser.utils import log, utils, standarddir, usertypes
|
||||||
from qutebrowser.misc import objects, earlyinit, sql
|
from qutebrowser.misc import objects, earlyinit, sql
|
||||||
from qutebrowser.browser import pdfjs
|
from qutebrowser.browser import pdfjs
|
||||||
|
|
||||||
@ -190,7 +190,6 @@ def _module_versions():
|
|||||||
('pygments', ['__version__']),
|
('pygments', ['__version__']),
|
||||||
('yaml', ['__version__']),
|
('yaml', ['__version__']),
|
||||||
('cssutils', ['__version__']),
|
('cssutils', ['__version__']),
|
||||||
('typing', []),
|
|
||||||
('PyQt5.QtWebEngineWidgets', []),
|
('PyQt5.QtWebEngineWidgets', []),
|
||||||
('PyQt5.QtWebKitWidgets', []),
|
('PyQt5.QtWebKitWidgets', []),
|
||||||
])
|
])
|
||||||
@ -304,9 +303,7 @@ def _chromium_version():
|
|||||||
def _backend():
|
def _backend():
|
||||||
"""Get the backend line with relevant information."""
|
"""Get the backend line with relevant information."""
|
||||||
if objects.backend == usertypes.Backend.QtWebKit:
|
if objects.backend == usertypes.Backend.QtWebKit:
|
||||||
return '{} (WebKit {})'.format(
|
return 'new QtWebKit (WebKit {})'.format(qWebKitVersion())
|
||||||
'QtWebKit-NG' if qtutils.is_qtwebkit_ng() else 'legacy QtWebKit',
|
|
||||||
qWebKitVersion())
|
|
||||||
else:
|
else:
|
||||||
webengine = usertypes.Backend.QtWebEngine
|
webengine = usertypes.Backend.QtWebEngine
|
||||||
assert objects.backend == webengine, objects.backend
|
assert objects.backend == webengine, objects.backend
|
||||||
|
@ -40,13 +40,7 @@ class AsciiDoc:
|
|||||||
|
|
||||||
"""Abstraction of an asciidoc subprocess."""
|
"""Abstraction of an asciidoc subprocess."""
|
||||||
|
|
||||||
FILES = [
|
FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts']
|
||||||
('FAQ.asciidoc', 'qutebrowser/html/doc/FAQ.html'),
|
|
||||||
('CHANGELOG.asciidoc', 'qutebrowser/html/doc/CHANGELOG.html'),
|
|
||||||
('CONTRIBUTING.asciidoc', 'qutebrowser/html/doc/CONTRIBUTING.html'),
|
|
||||||
('doc/quickstart.asciidoc', 'qutebrowser/html/doc/quickstart.html'),
|
|
||||||
('doc/userscripts.asciidoc', 'qutebrowser/html/doc/userscripts.html'),
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
self._cmd = None
|
self._cmd = None
|
||||||
@ -80,7 +74,9 @@ class AsciiDoc:
|
|||||||
|
|
||||||
def _build_docs(self):
|
def _build_docs(self):
|
||||||
"""Render .asciidoc files to .html sites."""
|
"""Render .asciidoc files to .html sites."""
|
||||||
files = self.FILES[:]
|
files = [('doc/{}.asciidoc'.format(f),
|
||||||
|
'qutebrowser/html/doc/{}.html'.format(f))
|
||||||
|
for f in self.FILES]
|
||||||
for src in glob.glob('doc/help/*.asciidoc'):
|
for src in glob.glob('doc/help/*.asciidoc'):
|
||||||
name, _ext = os.path.splitext(os.path.basename(src))
|
name, _ext = os.path.splitext(os.path.basename(src))
|
||||||
dst = 'qutebrowser/html/doc/{}.html'.format(name)
|
dst = 'qutebrowser/html/doc/{}.html'.format(name)
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
NAME ?= qutebrowser
|
NAME ?= qutebrowser
|
||||||
|
|
||||||
SOURCE_DIR ?= .
|
SOURCE_DIR ?= .
|
||||||
SOURCE_FILES ?= dist/qutebrowser.app COPYING
|
SOURCE_FILES ?= dist/qutebrowser.app LICENSE
|
||||||
|
|
||||||
TEMPLATE_DMG ?= template.dmg
|
TEMPLATE_DMG ?= template.dmg
|
||||||
TEMPLATE_SIZE ?= 300m
|
TEMPLATE_SIZE ?= 300m
|
||||||
|
@ -149,8 +149,6 @@ PERFECT_FILES = [
|
|||||||
'utils/jinja.py'),
|
'utils/jinja.py'),
|
||||||
('tests/unit/utils/test_error.py',
|
('tests/unit/utils/test_error.py',
|
||||||
'utils/error.py'),
|
'utils/error.py'),
|
||||||
('tests/unit/utils/test_typing.py',
|
|
||||||
'utils/typing.py'),
|
|
||||||
('tests/unit/utils/test_javascript.py',
|
('tests/unit/utils/test_javascript.py',
|
||||||
'utils/javascript.py'),
|
'utils/javascript.py'),
|
||||||
|
|
||||||
@ -291,7 +289,7 @@ def main_check_all():
|
|||||||
tests.
|
tests.
|
||||||
|
|
||||||
This runs pytest with the used executable, so check_coverage.py should be
|
This runs pytest with the used executable, so check_coverage.py should be
|
||||||
called with something like ./.tox/py34/bin/python.
|
called with something like ./.tox/py36/bin/python.
|
||||||
"""
|
"""
|
||||||
for test_file, src_file in PERFECT_FILES:
|
for test_file, src_file in PERFECT_FILES:
|
||||||
if test_file is None:
|
if test_file is None:
|
||||||
|
@ -5,10 +5,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
case $TESTENV in
|
case $TESTENV in
|
||||||
py34-cov)
|
|
||||||
exe=/usr/bin/python3.4
|
|
||||||
full=full
|
|
||||||
;;
|
|
||||||
py3*-pyqt*)
|
py3*-pyqt*)
|
||||||
exe=$(readlink -f .tox/$TESTENV/bin/python)
|
exe=$(readlink -f .tox/$TESTENV/bin/python)
|
||||||
full=
|
full=
|
||||||
|
@ -121,7 +121,8 @@ setupdata = {
|
|||||||
'Operating System :: Microsoft :: Windows :: Windows 7',
|
'Operating System :: Microsoft :: Windows :: Windows 7',
|
||||||
'Operating System :: POSIX :: Linux',
|
'Operating System :: POSIX :: Linux',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Programming Language :: Python :: 3.4',
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
'Topic :: Internet :: WWW/HTTP',
|
'Topic :: Internet :: WWW/HTTP',
|
||||||
'Topic :: Internet :: WWW/HTTP :: Browsers',
|
'Topic :: Internet :: WWW/HTTP :: Browsers',
|
||||||
|
@ -35,14 +35,15 @@ from helpers import logfail
|
|||||||
from helpers.logfail import fail_on_logging
|
from helpers.logfail import fail_on_logging
|
||||||
from helpers.messagemock import message_mock
|
from helpers.messagemock import message_mock
|
||||||
from helpers.fixtures import *
|
from helpers.fixtures import *
|
||||||
from qutebrowser.utils import qtutils, standarddir
|
from qutebrowser.utils import qtutils, standarddir, usertypes
|
||||||
|
from qutebrowser.misc import objects
|
||||||
|
|
||||||
import qutebrowser.app # To register commands
|
import qutebrowser.app # To register commands
|
||||||
|
|
||||||
|
|
||||||
# Set hypothesis settings
|
# Set hypothesis settings
|
||||||
hypothesis.settings.register_profile('default',
|
hypothesis.settings.register_profile(
|
||||||
hypothesis.settings(strict=True))
|
'default', hypothesis.settings(strict=True, deadline=400))
|
||||||
hypothesis.settings.load_profile('default')
|
hypothesis.settings.load_profile('default')
|
||||||
|
|
||||||
|
|
||||||
@ -62,7 +63,6 @@ def _apply_platform_markers(config, item):
|
|||||||
('no_ci', 'CI' in os.environ, "Skipped on CI."),
|
('no_ci', 'CI' in os.environ, "Skipped on CI."),
|
||||||
('issue2478', os.name == 'nt' and config.webengine,
|
('issue2478', os.name == 'nt' and config.webengine,
|
||||||
"Broken with QtWebEngine on Windows"),
|
"Broken with QtWebEngine on Windows"),
|
||||||
('qt55', not qtutils.version_check('5.5'), "Requires Qt 5.5 or newer"),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
for searched_marker, condition, default_reason in markers:
|
for searched_marker, condition, default_reason in markers:
|
||||||
@ -128,12 +128,9 @@ def pytest_collection_modifyitems(config, items):
|
|||||||
item.add_marker(pytest.mark.xfail(run=False))
|
item.add_marker(pytest.mark.xfail(run=False))
|
||||||
if item.get_marker('js_prompt'):
|
if item.get_marker('js_prompt'):
|
||||||
if config.webengine:
|
if config.webengine:
|
||||||
js_prompt_pyqt_version = 0x050700
|
item.add_marker(pytest.mark.skipif(
|
||||||
else:
|
PYQT_VERSION <= 0x050700,
|
||||||
js_prompt_pyqt_version = 0x050300
|
reason='JS prompts are not supported with PyQt 5.7'))
|
||||||
item.add_marker(pytest.mark.skipif(
|
|
||||||
PYQT_VERSION <= js_prompt_pyqt_version,
|
|
||||||
reason='JS prompts are not supported with this PyQt version'))
|
|
||||||
|
|
||||||
if deselected:
|
if deselected:
|
||||||
deselected_items.append(item)
|
deselected_items.append(item)
|
||||||
@ -188,6 +185,14 @@ def check_display(request):
|
|||||||
raise Exception("No display and no Xvfb available!")
|
raise Exception("No display and no Xvfb available!")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def set_backend(monkeypatch, request):
|
||||||
|
"""Make sure the backend global is set."""
|
||||||
|
backend = (usertypes.Backend.QtWebEngine if request.config.webengine
|
||||||
|
else usertypes.Backend.QtWebKit)
|
||||||
|
monkeypatch.setattr(objects, 'backend', backend)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
"""Make test information available in fixtures.
|
"""Make test information available in fixtures.
|
||||||
|
@ -34,7 +34,7 @@ from PyQt5.QtCore import PYQT_VERSION
|
|||||||
|
|
||||||
pytest.register_assert_rewrite('end2end.fixtures')
|
pytest.register_assert_rewrite('end2end.fixtures')
|
||||||
|
|
||||||
from end2end.fixtures.webserver import httpbin, httpbin_after_test, ssl_server
|
from end2end.fixtures.webserver import server, server_after_test, ssl_server
|
||||||
from end2end.fixtures.quteprocess import (quteproc_process, quteproc,
|
from end2end.fixtures.quteprocess import (quteproc_process, quteproc,
|
||||||
quteproc_new)
|
quteproc_new)
|
||||||
from end2end.fixtures.testprocess import pytest_runtest_makereport
|
from end2end.fixtures.testprocess import pytest_runtest_makereport
|
||||||
@ -109,8 +109,6 @@ def _get_backend_tag(tag):
|
|||||||
'qtwebengine_todo': pytest.mark.qtwebengine_todo,
|
'qtwebengine_todo': pytest.mark.qtwebengine_todo,
|
||||||
'qtwebengine_skip': pytest.mark.qtwebengine_skip,
|
'qtwebengine_skip': pytest.mark.qtwebengine_skip,
|
||||||
'qtwebkit_skip': pytest.mark.qtwebkit_skip,
|
'qtwebkit_skip': pytest.mark.qtwebkit_skip,
|
||||||
'qtwebkit_ng_xfail': pytest.mark.qtwebkit_ng_xfail,
|
|
||||||
'qtwebkit_ng_skip': pytest.mark.qtwebkit_ng_skip,
|
|
||||||
}
|
}
|
||||||
if not any(tag.startswith(t + ':') for t in pytest_marks):
|
if not any(tag.startswith(t + ':') for t in pytest_marks):
|
||||||
return None
|
return None
|
||||||
@ -143,10 +141,6 @@ def pytest_collection_modifyitems(config, items):
|
|||||||
config.webengine),
|
config.webengine),
|
||||||
('qtwebkit_skip', 'Skipped with QtWebKit', pytest.mark.skipif,
|
('qtwebkit_skip', 'Skipped with QtWebKit', pytest.mark.skipif,
|
||||||
not config.webengine),
|
not config.webengine),
|
||||||
('qtwebkit_ng_xfail', 'Failing with QtWebKit-NG', pytest.mark.xfail,
|
|
||||||
not config.webengine and qtutils.is_qtwebkit_ng()),
|
|
||||||
('qtwebkit_ng_skip', 'Skipped with QtWebKit-NG', pytest.mark.skipif,
|
|
||||||
not config.webengine and qtutils.is_qtwebkit_ng()),
|
|
||||||
('qtwebengine_flaky', 'Flaky with QtWebEngine', pytest.mark.skipif,
|
('qtwebengine_flaky', 'Flaky with QtWebEngine', pytest.mark.skipif,
|
||||||
config.webengine),
|
config.webengine),
|
||||||
('qtwebengine_mac_xfail', 'Fails on macOS with QtWebEngine',
|
('qtwebengine_mac_xfail', 'Fails on macOS with QtWebEngine',
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<title>Failing download when redirecting/aborting</title>
|
<title>Failing download when redirecting/aborting</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a href="/custom/redirect-later?delay=1">redirect after 1s</a>
|
<a href="/redirect-later?delay=1">redirect after 1s</a>
|
||||||
<a href="/does-not-exist">404</a>
|
<a href="/does-not-exist">404</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
|
|
||||||
<h2>...and how?</h2>
|
<h2>...and how?</h2>
|
||||||
|
|
||||||
<p>See <a href="https://github.com/qutebrowser/qutebrowser/blob/master/CONTRIBUTING.asciidoc">
|
<p>See <a href="https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc">
|
||||||
here</a> for more information.</p>
|
here</a> for more information.</p>
|
||||||
|
|
||||||
<h2>More useless trivia!</h2>
|
<h2>More useless trivia!</h2>
|
||||||
|
@ -97,7 +97,7 @@ the
|
|||||||
<h2>...and how?</h2>
|
<h2>...and how?</h2>
|
||||||
=20
|
=20
|
||||||
<p>See <a href=3D"https://github.com/qutebrowser/qutebrowser/blob/m=
|
<p>See <a href=3D"https://github.com/qutebrowser/qutebrowser/blob/m=
|
||||||
aster/CONTRIBUTING.asciidoc">
|
aster/doc/contributing.asciidoc">
|
||||||
here</a> for more information.</p>
|
here</a> for more information.</p>
|
||||||
=20
|
=20
|
||||||
<h2>More useless trivia!</h2>
|
<h2>More useless trivia!</h2>
|
||||||
|
@ -6,11 +6,7 @@
|
|||||||
var my_window;
|
var my_window;
|
||||||
|
|
||||||
function open_modal() {
|
function open_modal() {
|
||||||
if (window.showModalDialog) {
|
window.open('about:blank', 'window', 'modal');
|
||||||
window.showModalDialog();
|
|
||||||
} else {
|
|
||||||
window.open('about:blank', 'window', 'modal');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function open_normal() {
|
function open_normal() {
|
||||||
|
@ -116,14 +116,14 @@ def pytest_runtest_makereport(item, call):
|
|||||||
|
|
||||||
|
|
||||||
@bdd.given(bdd.parsers.parse("I set {opt} to {value}"))
|
@bdd.given(bdd.parsers.parse("I set {opt} to {value}"))
|
||||||
def set_setting_given(quteproc, httpbin, opt, value):
|
def set_setting_given(quteproc, server, opt, value):
|
||||||
"""Set a qutebrowser setting.
|
"""Set a qutebrowser setting.
|
||||||
|
|
||||||
This is available as "Given:" step so it can be used as "Background:".
|
This is available as "Given:" step so it can be used as "Background:".
|
||||||
"""
|
"""
|
||||||
if value == '<empty>':
|
if value == '<empty>':
|
||||||
value = ''
|
value = ''
|
||||||
value = value.replace('(port)', str(httpbin.port))
|
value = value.replace('(port)', str(server.port))
|
||||||
quteproc.set_setting(opt, value)
|
quteproc.set_setting(opt, value)
|
||||||
|
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ def pdfjs_available():
|
|||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse("I open {path}"))
|
@bdd.when(bdd.parsers.parse("I open {path}"))
|
||||||
def open_path(quteproc, httpbin, path):
|
def open_path(quteproc, server, path):
|
||||||
"""Open a URL.
|
"""Open a URL.
|
||||||
|
|
||||||
- If used like "When I open ... in a new tab", the URL is opened in a new
|
- If used like "When I open ... in a new tab", the URL is opened in a new
|
||||||
@ -183,7 +183,7 @@ def open_path(quteproc, httpbin, path):
|
|||||||
- With "... in a private window" it's opened in a new private window.
|
- With "... in a private window" it's opened in a new private window.
|
||||||
- With "... as a URL", it's opened according to new_instance_open_target.
|
- With "... as a URL", it's opened according to new_instance_open_target.
|
||||||
"""
|
"""
|
||||||
path = path.replace('(port)', str(httpbin.port))
|
path = path.replace('(port)', str(server.port))
|
||||||
|
|
||||||
new_tab = False
|
new_tab = False
|
||||||
new_bg_tab = False
|
new_bg_tab = False
|
||||||
@ -227,16 +227,16 @@ def open_path(quteproc, httpbin, path):
|
|||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse("I set {opt} to {value}"))
|
@bdd.when(bdd.parsers.parse("I set {opt} to {value}"))
|
||||||
def set_setting(quteproc, httpbin, opt, value):
|
def set_setting(quteproc, server, opt, value):
|
||||||
"""Set a qutebrowser setting."""
|
"""Set a qutebrowser setting."""
|
||||||
if value == '<empty>':
|
if value == '<empty>':
|
||||||
value = ''
|
value = ''
|
||||||
value = value.replace('(port)', str(httpbin.port))
|
value = value.replace('(port)', str(server.port))
|
||||||
quteproc.set_setting(opt, value)
|
quteproc.set_setting(opt, value)
|
||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse("I run {command}"))
|
@bdd.when(bdd.parsers.parse("I run {command}"))
|
||||||
def run_command(quteproc, httpbin, tmpdir, command):
|
def run_command(quteproc, server, tmpdir, command):
|
||||||
"""Run a qutebrowser command.
|
"""Run a qutebrowser command.
|
||||||
|
|
||||||
The suffix "with count ..." can be used to pass a count to the command.
|
The suffix "with count ..." can be used to pass a count to the command.
|
||||||
@ -254,7 +254,7 @@ def run_command(quteproc, httpbin, tmpdir, command):
|
|||||||
else:
|
else:
|
||||||
invalid = False
|
invalid = False
|
||||||
|
|
||||||
command = command.replace('(port)', str(httpbin.port))
|
command = command.replace('(port)', str(server.port))
|
||||||
command = command.replace('(testdata)', utils.abs_datapath())
|
command = command.replace('(testdata)', utils.abs_datapath())
|
||||||
command = command.replace('(tmpdir)', str(tmpdir))
|
command = command.replace('(tmpdir)', str(tmpdir))
|
||||||
command = command.replace('(dirsep)', os.sep)
|
command = command.replace('(dirsep)', os.sep)
|
||||||
@ -264,9 +264,9 @@ def run_command(quteproc, httpbin, tmpdir, command):
|
|||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse("I reload"))
|
@bdd.when(bdd.parsers.parse("I reload"))
|
||||||
def reload(qtbot, httpbin, quteproc, command):
|
def reload(qtbot, server, quteproc, command):
|
||||||
"""Reload and wait until a new request is received."""
|
"""Reload and wait until a new request is received."""
|
||||||
with qtbot.waitSignal(httpbin.new_request):
|
with qtbot.waitSignal(server.new_request):
|
||||||
quteproc.send_cmd(':reload')
|
quteproc.send_cmd(':reload')
|
||||||
|
|
||||||
|
|
||||||
@ -294,10 +294,10 @@ def wait_in_log(quteproc, is_regex, pattern, do_skip):
|
|||||||
|
|
||||||
@bdd.when(bdd.parsers.re(r'I wait for the (?P<category>error|message|warning) '
|
@bdd.when(bdd.parsers.re(r'I wait for the (?P<category>error|message|warning) '
|
||||||
r'"(?P<message>.*)"'))
|
r'"(?P<message>.*)"'))
|
||||||
def wait_for_message(quteproc, httpbin, category, message):
|
def wait_for_message(quteproc, server, category, message):
|
||||||
"""Wait for a given statusbar message/error/warning."""
|
"""Wait for a given statusbar message/error/warning."""
|
||||||
quteproc.log_summary('Waiting for {} "{}"'.format(category, message))
|
quteproc.log_summary('Waiting for {} "{}"'.format(category, message))
|
||||||
expect_message(quteproc, httpbin, category, message)
|
expect_message(quteproc, server, category, message)
|
||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse("I wait {delay}s"))
|
@bdd.when(bdd.parsers.parse("I wait {delay}s"))
|
||||||
@ -328,8 +328,8 @@ def selection_not_supported(qapp):
|
|||||||
|
|
||||||
@bdd.when(bdd.parsers.re(r'I put "(?P<content>.*)" into the '
|
@bdd.when(bdd.parsers.re(r'I put "(?P<content>.*)" into the '
|
||||||
r'(?P<what>primary selection|clipboard)'))
|
r'(?P<what>primary selection|clipboard)'))
|
||||||
def fill_clipboard(quteproc, httpbin, what, content):
|
def fill_clipboard(quteproc, server, what, content):
|
||||||
content = content.replace('(port)', str(httpbin.port))
|
content = content.replace('(port)', str(server.port))
|
||||||
content = content.replace(r'\n', '\n')
|
content = content.replace(r'\n', '\n')
|
||||||
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(content))
|
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(content))
|
||||||
|
|
||||||
@ -337,8 +337,8 @@ def fill_clipboard(quteproc, httpbin, what, content):
|
|||||||
@bdd.when(bdd.parsers.re(r'I put the following lines into the '
|
@bdd.when(bdd.parsers.re(r'I put the following lines into the '
|
||||||
r'(?P<what>primary selection|clipboard):\n'
|
r'(?P<what>primary selection|clipboard):\n'
|
||||||
r'(?P<content>.+)$', flags=re.DOTALL))
|
r'(?P<content>.+)$', flags=re.DOTALL))
|
||||||
def fill_clipboard_multiline(quteproc, httpbin, what, content):
|
def fill_clipboard_multiline(quteproc, server, what, content):
|
||||||
fill_clipboard(quteproc, httpbin, what, textwrap.dedent(content))
|
fill_clipboard(quteproc, server, what, textwrap.dedent(content))
|
||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse('I hint with args "{args}"'))
|
@bdd.when(bdd.parsers.parse('I hint with args "{args}"'))
|
||||||
@ -395,28 +395,28 @@ def path_should_be_loaded(quteproc, path):
|
|||||||
|
|
||||||
|
|
||||||
@bdd.then(bdd.parsers.parse("{path} should be requested"))
|
@bdd.then(bdd.parsers.parse("{path} should be requested"))
|
||||||
def path_should_be_requested(httpbin, path):
|
def path_should_be_requested(server, path):
|
||||||
"""Make sure the given path was loaded from the webserver."""
|
"""Make sure the given path was loaded from the webserver."""
|
||||||
httpbin.wait_for(verb='GET', path='/' + path)
|
server.wait_for(verb='GET', path='/' + path)
|
||||||
|
|
||||||
|
|
||||||
@bdd.then(bdd.parsers.parse("The requests should be:\n{pages}"))
|
@bdd.then(bdd.parsers.parse("The requests should be:\n{pages}"))
|
||||||
def list_of_requests(httpbin, pages):
|
def list_of_requests(server, pages):
|
||||||
"""Make sure the given requests were done from the webserver."""
|
"""Make sure the given requests were done from the webserver."""
|
||||||
expected_requests = [httpbin.ExpectedRequest('GET', '/' + path.strip())
|
expected_requests = [server.ExpectedRequest('GET', '/' + path.strip())
|
||||||
for path in pages.split('\n')]
|
for path in pages.split('\n')]
|
||||||
actual_requests = httpbin.get_requests()
|
actual_requests = server.get_requests()
|
||||||
assert actual_requests == expected_requests
|
assert actual_requests == expected_requests
|
||||||
|
|
||||||
|
|
||||||
@bdd.then(bdd.parsers.parse("The unordered requests should be:\n{pages}"))
|
@bdd.then(bdd.parsers.parse("The unordered requests should be:\n{pages}"))
|
||||||
def list_of_requests_unordered(httpbin, pages):
|
def list_of_requests_unordered(server, pages):
|
||||||
"""Make sure the given requests were done (in no particular order)."""
|
"""Make sure the given requests were done (in no particular order)."""
|
||||||
expected_requests = [httpbin.ExpectedRequest('GET', '/' + path.strip())
|
expected_requests = [server.ExpectedRequest('GET', '/' + path.strip())
|
||||||
for path in pages.split('\n')]
|
for path in pages.split('\n')]
|
||||||
actual_requests = httpbin.get_requests()
|
actual_requests = server.get_requests()
|
||||||
# Requests are not hashable, we need to convert to ExpectedRequests
|
# Requests are not hashable, we need to convert to ExpectedRequests
|
||||||
actual_requests = [httpbin.ExpectedRequest.from_request(req)
|
actual_requests = [server.ExpectedRequest.from_request(req)
|
||||||
for req in actual_requests]
|
for req in actual_requests]
|
||||||
assert (collections.Counter(actual_requests) ==
|
assert (collections.Counter(actual_requests) ==
|
||||||
collections.Counter(expected_requests))
|
collections.Counter(expected_requests))
|
||||||
@ -424,14 +424,14 @@ def list_of_requests_unordered(httpbin, pages):
|
|||||||
|
|
||||||
@bdd.then(bdd.parsers.re(r'the (?P<category>error|message|warning) '
|
@bdd.then(bdd.parsers.re(r'the (?P<category>error|message|warning) '
|
||||||
r'"(?P<message>.*)" should be shown'))
|
r'"(?P<message>.*)" should be shown'))
|
||||||
def expect_message(quteproc, httpbin, category, message):
|
def expect_message(quteproc, server, category, message):
|
||||||
"""Expect the given message in the qutebrowser log."""
|
"""Expect the given message in the qutebrowser log."""
|
||||||
category_to_loglevel = {
|
category_to_loglevel = {
|
||||||
'message': logging.INFO,
|
'message': logging.INFO,
|
||||||
'error': logging.ERROR,
|
'error': logging.ERROR,
|
||||||
'warning': logging.WARNING,
|
'warning': logging.WARNING,
|
||||||
}
|
}
|
||||||
message = message.replace('(port)', str(httpbin.port))
|
message = message.replace('(port)', str(server.port))
|
||||||
quteproc.mark_expected(category='message',
|
quteproc.mark_expected(category='message',
|
||||||
loglevel=category_to_loglevel[category],
|
loglevel=category_to_loglevel[category],
|
||||||
message=message)
|
message=message)
|
||||||
@ -439,12 +439,12 @@ def expect_message(quteproc, httpbin, category, message):
|
|||||||
|
|
||||||
@bdd.then(bdd.parsers.re(r'(?P<is_regex>regex )?"(?P<pattern>[^"]+)" should '
|
@bdd.then(bdd.parsers.re(r'(?P<is_regex>regex )?"(?P<pattern>[^"]+)" should '
|
||||||
r'be logged'))
|
r'be logged'))
|
||||||
def should_be_logged(quteproc, httpbin, is_regex, pattern):
|
def should_be_logged(quteproc, server, is_regex, pattern):
|
||||||
"""Expect the given pattern on regex in the log."""
|
"""Expect the given pattern on regex in the log."""
|
||||||
if is_regex:
|
if is_regex:
|
||||||
pattern = re.compile(pattern)
|
pattern = re.compile(pattern)
|
||||||
else:
|
else:
|
||||||
pattern = pattern.replace('(port)', str(httpbin.port))
|
pattern = pattern.replace('(port)', str(server.port))
|
||||||
line = quteproc.wait_for(message=pattern)
|
line = quteproc.wait_for(message=pattern)
|
||||||
line.expected = True
|
line.expected = True
|
||||||
|
|
||||||
@ -493,7 +493,7 @@ def no_crash():
|
|||||||
def check_header(quteproc, header, value):
|
def check_header(quteproc, header, value):
|
||||||
"""Check if a given header is set correctly.
|
"""Check if a given header is set correctly.
|
||||||
|
|
||||||
This assumes we're on the httpbin header page.
|
This assumes we're on the server header page.
|
||||||
"""
|
"""
|
||||||
content = quteproc.get_content()
|
content = quteproc.get_content()
|
||||||
data = json.loads(content)
|
data = json.loads(content)
|
||||||
@ -589,16 +589,16 @@ def check_open_tabs(quteproc, request, tabs):
|
|||||||
|
|
||||||
@bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should '
|
@bdd.then(bdd.parsers.re(r'the (?P<what>primary selection|clipboard) should '
|
||||||
r'contain "(?P<content>.*)"'))
|
r'contain "(?P<content>.*)"'))
|
||||||
def clipboard_contains(quteproc, httpbin, what, content):
|
def clipboard_contains(quteproc, server, what, content):
|
||||||
expected = content.replace('(port)', str(httpbin.port))
|
expected = content.replace('(port)', str(server.port))
|
||||||
expected = expected.replace('\\n', '\n')
|
expected = expected.replace('\\n', '\n')
|
||||||
quteproc.wait_for(message='Setting fake {}: {}'.format(
|
quteproc.wait_for(message='Setting fake {}: {}'.format(
|
||||||
what, json.dumps(expected)))
|
what, json.dumps(expected)))
|
||||||
|
|
||||||
|
|
||||||
@bdd.then(bdd.parsers.parse('the clipboard should contain:\n{content}'))
|
@bdd.then(bdd.parsers.parse('the clipboard should contain:\n{content}'))
|
||||||
def clipboard_contains_multiline(quteproc, httpbin, content):
|
def clipboard_contains_multiline(quteproc, server, content):
|
||||||
expected = textwrap.dedent(content).replace('(port)', str(httpbin.port))
|
expected = textwrap.dedent(content).replace('(port)', str(server.port))
|
||||||
quteproc.wait_for(message='Setting fake clipboard: {}'.format(
|
quteproc.wait_for(message='Setting fake clipboard: {}'.format(
|
||||||
json.dumps(expected)))
|
json.dumps(expected)))
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ Feature: Downloading things from a website.
|
|||||||
|
|
||||||
Scenario: Opening a mhtml download directly
|
Scenario: Opening a mhtml download directly
|
||||||
When I set downloads.location.prompt to true
|
When I set downloads.location.prompt to true
|
||||||
And I open html
|
And I open /
|
||||||
And I run :download --mhtml
|
And I run :download --mhtml
|
||||||
And I wait for the download prompt for "*"
|
And I wait for the download prompt for "*"
|
||||||
And I directly open the download
|
And I directly open the download
|
||||||
@ -572,27 +572,27 @@ Feature: Downloading things from a website.
|
|||||||
|
|
||||||
Scenario: Downloading with redirect to itself
|
Scenario: Downloading with redirect to itself
|
||||||
When I set downloads.location.prompt to false
|
When I set downloads.location.prompt to false
|
||||||
And I run :download http://localhost:(port)/custom/redirect-self
|
And I run :download http://localhost:(port)/redirect-self
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
Then the downloaded file redirect-self should exist
|
Then the downloaded file redirect-self should exist
|
||||||
|
|
||||||
Scenario: Downloading with absolute redirect
|
Scenario: Downloading with absolute redirect
|
||||||
When I set downloads.location.prompt to false
|
When I set downloads.location.prompt to false
|
||||||
And I run :download http://localhost:(port)/absolute-redirect/1
|
And I run :download http://localhost:(port)/absolute-redirect
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
Then the downloaded file 1 should exist
|
Then the downloaded file absolute-redirect should exist
|
||||||
|
|
||||||
Scenario: Downloading with relative redirect
|
Scenario: Downloading with relative redirect
|
||||||
When I set downloads.location.prompt to false
|
When I set downloads.location.prompt to false
|
||||||
And I run :download http://localhost:(port)/relative-redirect/1
|
And I run :download http://localhost:(port)/relative-redirect
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
Then the downloaded file 1 should exist
|
Then the downloaded file relative-redirect should exist
|
||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
Scenario: Download without a content-size
|
Scenario: Download without a content-size
|
||||||
When I set downloads.location.prompt to false
|
When I set downloads.location.prompt to false
|
||||||
When I run :download http://localhost:(port)/custom/content-size
|
When I run :download http://localhost:(port)/content-size
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
Then the downloaded file content-size should exist
|
Then the downloaded file content-size should exist
|
||||||
|
|
||||||
@ -604,13 +604,13 @@ Feature: Downloading things from a website.
|
|||||||
|
|
||||||
Scenario: Downloading 20MB file
|
Scenario: Downloading 20MB file
|
||||||
When I set downloads.location.prompt to false
|
When I set downloads.location.prompt to false
|
||||||
And I run :download http://localhost:(port)/custom/twenty-mb
|
And I run :download http://localhost:(port)/twenty-mb
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
Then the downloaded file twenty-mb should be 20971520 bytes big
|
Then the downloaded file twenty-mb should be 20971520 bytes big
|
||||||
|
|
||||||
Scenario: Downloading 20MB file with late prompt confirmation
|
Scenario: Downloading 20MB file with late prompt confirmation
|
||||||
When I set downloads.location.prompt to true
|
When I set downloads.location.prompt to true
|
||||||
And I run :download http://localhost:(port)/custom/twenty-mb
|
And I run :download http://localhost:(port)/twenty-mb
|
||||||
And I wait 1s
|
And I wait 1s
|
||||||
And I run :prompt-accept
|
And I run :prompt-accept
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
@ -645,12 +645,6 @@ Feature: Downloading things from a website.
|
|||||||
Then the downloaded file download.bin should exist
|
Then the downloaded file download.bin should exist
|
||||||
And the downloaded file download2.bin should not exist
|
And the downloaded file download2.bin should not exist
|
||||||
|
|
||||||
Scenario: Downloading a file with unknown size
|
|
||||||
When I set downloads.location.prompt to false
|
|
||||||
And I open stream-bytes/1024 without waiting
|
|
||||||
And I wait until the download is finished
|
|
||||||
Then the downloaded file 1024 should exist
|
|
||||||
|
|
||||||
@qtwebengine_skip: We can't get the UA from the page there
|
@qtwebengine_skip: We can't get the UA from the page there
|
||||||
Scenario: user-agent when using :download
|
Scenario: user-agent when using :download
|
||||||
When I open user-agent
|
When I open user-agent
|
||||||
@ -660,16 +654,14 @@ Feature: Downloading things from a website.
|
|||||||
|
|
||||||
@qtwebengine_skip: We can't get the UA from the page there
|
@qtwebengine_skip: We can't get the UA from the page there
|
||||||
Scenario: user-agent when using hints
|
Scenario: user-agent when using hints
|
||||||
When I set hints.mode to number
|
When I open /
|
||||||
And I open /
|
|
||||||
And I run :hint links download
|
And I run :hint links download
|
||||||
And I press the keys "us" # user-agent
|
And I run :follow-hint a
|
||||||
And I run :follow-hint 0
|
|
||||||
And I wait until the download is finished
|
And I wait until the download is finished
|
||||||
Then the downloaded file user-agent should contain Safari/
|
Then the downloaded file user-agent should contain Safari/
|
||||||
|
|
||||||
@qtwebengine_skip: Handled by QtWebEngine, not by us
|
@qtwebengine_skip: Handled by QtWebEngine, not by us
|
||||||
Scenario: Downloading a "Internal server error" with disposition: inline (#2304)
|
Scenario: Downloading a "Internal server error" with disposition: inline (#2304)
|
||||||
When I set downloads.location.prompt to false
|
When I set downloads.location.prompt to false
|
||||||
And I open custom/500-inline
|
And I open 500-inline
|
||||||
Then the error "Download error: *INTERNAL SERVER ERROR" should be shown
|
Then the error "Download error: *INTERNAL SERVER ERROR" should be shown
|
||||||
|
@ -46,10 +46,10 @@ Feature: Page history
|
|||||||
|
|
||||||
@qtwebengine_todo: Error page message is not implemented
|
@qtwebengine_todo: Error page message is not implemented
|
||||||
Scenario: History with a 404
|
Scenario: History with a 404
|
||||||
When I open status/404 without waiting
|
When I open 404 without waiting
|
||||||
And I wait for "Error while loading http://localhost:*/status/404: NOT FOUND" in the log
|
And I wait for "Error while loading http://localhost:*/404: NOT FOUND" in the log
|
||||||
Then the history should contain:
|
Then the history should contain:
|
||||||
http://localhost:(port)/status/404 Error loading page: http://localhost:(port)/status/404
|
http://localhost:(port)/404 Error loading page: http://localhost:(port)/404
|
||||||
|
|
||||||
Scenario: History with invalid URL
|
Scenario: History with invalid URL
|
||||||
When I run :tab-only
|
When I run :tab-only
|
||||||
@ -103,13 +103,4 @@ Feature: Page history
|
|||||||
And I open qute:history without waiting
|
And I open qute:history without waiting
|
||||||
And I wait until qute://history is loaded
|
And I wait until qute://history is loaded
|
||||||
Then the page should contain the plaintext "3.txt"
|
Then the page should contain the plaintext "3.txt"
|
||||||
Then the page should contain the plaintext "4.txt"
|
Then the page should contain the plaintext "4.txt"
|
||||||
|
|
||||||
## Bugs
|
|
||||||
|
|
||||||
@qtwebengine_skip @qtwebkit_ng_skip
|
|
||||||
Scenario: Opening a valid URL which turns out invalid
|
|
||||||
When I set url.auto_search to naive
|
|
||||||
And I run :open http://foo%40bar@baz
|
|
||||||
Then "QFSFileEngine::open: No file name specified" should be logged
|
|
||||||
And "Error while loading : Host not found" should be logged
|
|
@ -17,7 +17,7 @@ Feature: Javascript stuff
|
|||||||
And I run :click-element id close-normal
|
And I run :click-element id close-normal
|
||||||
Then "Focus object changed: *" should be logged
|
Then "Focus object changed: *" should be logged
|
||||||
|
|
||||||
@qtwebkit_ng_skip
|
@qtwebkit_skip
|
||||||
Scenario: Opening/closing a modal window via JS
|
Scenario: Opening/closing a modal window via JS
|
||||||
When I open data/javascript/window_open.html
|
When I open data/javascript/window_open.html
|
||||||
And I run :tab-only
|
And I run :tab-only
|
||||||
@ -26,7 +26,6 @@ Feature: Javascript stuff
|
|||||||
And I run :tab-focus 1
|
And I run :tab-focus 1
|
||||||
And I run :click-element id close-normal
|
And I run :click-element id close-normal
|
||||||
Then "Focus object changed: *" should be logged
|
Then "Focus object changed: *" should be logged
|
||||||
# WebModalDialog with QtWebKit, WebDialog with QtWebEngine
|
|
||||||
And "Web*Dialog requested, but we don't support that!" should be logged
|
And "Web*Dialog requested, but we don't support that!" should be logged
|
||||||
|
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/906
|
# https://github.com/qutebrowser/qutebrowser/issues/906
|
||||||
|
@ -186,15 +186,15 @@ Feature: Various utility commands.
|
|||||||
Given I have a fresh instance
|
Given I have a fresh instance
|
||||||
# We can't use "When I open" because we don't want to wait for load
|
# We can't use "When I open" because we don't want to wait for load
|
||||||
# finished
|
# finished
|
||||||
When I run :open http://localhost:(port)/custom/redirect-later?delay=-1
|
When I run :open http://localhost:(port)/redirect-later?delay=-1
|
||||||
And I wait for "emitting: cur_load_status_changed('loading') (tab *)" in the log
|
And I wait for "emitting: cur_load_status_changed('loading') (tab *)" in the log
|
||||||
And I wait 1s
|
And I wait 1s
|
||||||
And I run :stop
|
And I run :stop
|
||||||
And I open custom/redirect-later-continue in a new tab
|
And I open redirect-later-continue in a new tab
|
||||||
And I wait 1s
|
And I wait 1s
|
||||||
Then the unordered requests should be:
|
Then the unordered requests should be:
|
||||||
custom/redirect-later-continue
|
redirect-later-continue
|
||||||
custom/redirect-later?delay=-1
|
redirect-later?delay=-1
|
||||||
# no request on / because we stopped the redirect
|
# no request on / because we stopped the redirect
|
||||||
|
|
||||||
Scenario: :stop with wrong count
|
Scenario: :stop with wrong count
|
||||||
|
@ -42,7 +42,6 @@ Feature: Using private browsing
|
|||||||
|
|
||||||
## https://github.com/qutebrowser/qutebrowser/issues/1219
|
## https://github.com/qutebrowser/qutebrowser/issues/1219
|
||||||
|
|
||||||
@qtwebkit_ng_skip: private browsing is not implemented yet
|
|
||||||
Scenario: Sharing cookies with private browsing
|
Scenario: Sharing cookies with private browsing
|
||||||
When I open cookies/set?qute-test=42 without waiting in a private window
|
When I open cookies/set?qute-test=42 without waiting in a private window
|
||||||
And I wait until cookies is loaded
|
And I wait until cookies is loaded
|
||||||
|
@ -25,7 +25,7 @@ bdd.scenarios('adblock.feature')
|
|||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse('I set up "{lists}" as block lists'))
|
@bdd.when(bdd.parsers.parse('I set up "{lists}" as block lists'))
|
||||||
def set_up_blocking(quteproc, lists, httpbin):
|
def set_up_blocking(quteproc, lists, server):
|
||||||
url = 'http://localhost:{}/data/adblock/'.format(httpbin.port)
|
url = 'http://localhost:{}/data/adblock/'.format(server.port)
|
||||||
urls = [url + item.strip() for item in lists.split(',')]
|
urls = [url + item.strip() for item in lists.split(',')]
|
||||||
quteproc.set_setting('content.host_blocking.lists', json.dumps(urls))
|
quteproc.set_setting('content.host_blocking.lists', json.dumps(urls))
|
||||||
|
@ -27,9 +27,9 @@ bdd.scenarios('editor.feature')
|
|||||||
|
|
||||||
@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by '
|
@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by '
|
||||||
'"{replacement}"'))
|
'"{replacement}"'))
|
||||||
def set_up_editor_replacement(quteproc, httpbin, tmpdir, text, replacement):
|
def set_up_editor_replacement(quteproc, server, tmpdir, text, replacement):
|
||||||
"""Set up editor.command to a small python script doing a replacement."""
|
"""Set up editor.command to a small python script doing a replacement."""
|
||||||
text = text.replace('(port)', str(httpbin.port))
|
text = text.replace('(port)', str(server.port))
|
||||||
script = tmpdir / 'script.py'
|
script = tmpdir / 'script.py'
|
||||||
script.write(textwrap.dedent("""
|
script.write(textwrap.dedent("""
|
||||||
import sys
|
import sys
|
||||||
@ -47,7 +47,7 @@ def set_up_editor_replacement(quteproc, httpbin, tmpdir, text, replacement):
|
|||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse('I set up a fake editor returning "{text}"'))
|
@bdd.when(bdd.parsers.parse('I set up a fake editor returning "{text}"'))
|
||||||
def set_up_editor(quteproc, httpbin, tmpdir, text):
|
def set_up_editor(quteproc, server, tmpdir, text):
|
||||||
"""Set up editor.command to a small python script inserting a text."""
|
"""Set up editor.command to a small python script inserting a text."""
|
||||||
script = tmpdir / 'script.py'
|
script = tmpdir / 'script.py'
|
||||||
script.write(textwrap.dedent("""
|
script.write(textwrap.dedent("""
|
||||||
|
@ -35,7 +35,7 @@ def turn_on_sql_history(quteproc):
|
|||||||
|
|
||||||
|
|
||||||
@bdd.then(bdd.parsers.parse("the history should contain:\n{expected}"))
|
@bdd.then(bdd.parsers.parse("the history should contain:\n{expected}"))
|
||||||
def check_history(quteproc, httpbin, tmpdir, expected):
|
def check_history(quteproc, server, tmpdir, expected):
|
||||||
path = tmpdir / 'history'
|
path = tmpdir / 'history'
|
||||||
quteproc.send_cmd(':debug-dump-history "{}"'.format(path))
|
quteproc.send_cmd(':debug-dump-history "{}"'.format(path))
|
||||||
quteproc.wait_for(category='message', loglevel=logging.INFO,
|
quteproc.wait_for(category='message', loglevel=logging.INFO,
|
||||||
@ -45,10 +45,10 @@ def check_history(quteproc, httpbin, tmpdir, expected):
|
|||||||
# ignore access times, they will differ in each run
|
# ignore access times, they will differ in each run
|
||||||
actual = '\n'.join(re.sub('^\\d+-?', '', line).strip() for line in f)
|
actual = '\n'.join(re.sub('^\\d+-?', '', line).strip() for line in f)
|
||||||
|
|
||||||
expected = expected.replace('(port)', str(httpbin.port))
|
expected = expected.replace('(port)', str(server.port))
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
|
|
||||||
@bdd.then("the history should be empty")
|
@bdd.then("the history should be empty")
|
||||||
def check_history_empty(quteproc, httpbin, tmpdir):
|
def check_history_empty(quteproc, server, tmpdir):
|
||||||
check_history(quteproc, httpbin, tmpdir, '')
|
check_history(quteproc, server, tmpdir, '')
|
||||||
|
@ -27,7 +27,7 @@ bdd.scenarios('private.feature')
|
|||||||
def check_cookie(quteproc, name, value):
|
def check_cookie(quteproc, name, value):
|
||||||
"""Check if a given cookie is set correctly.
|
"""Check if a given cookie is set correctly.
|
||||||
|
|
||||||
This assumes we're on the httpbin cookies page.
|
This assumes we're on the server cookies page.
|
||||||
"""
|
"""
|
||||||
content = quteproc.get_content()
|
content = quteproc.get_content()
|
||||||
data = json.loads(content)
|
data = json.loads(content)
|
||||||
|
@ -34,13 +34,13 @@ def create_session_file(quteproc, name, contents):
|
|||||||
|
|
||||||
@bdd.when(bdd.parsers.parse('I replace "{pattern}" by "{replacement}" in the '
|
@bdd.when(bdd.parsers.parse('I replace "{pattern}" by "{replacement}" in the '
|
||||||
'"{name}" session file'))
|
'"{name}" session file'))
|
||||||
def session_replace(quteproc, httpbin, pattern, replacement, name):
|
def session_replace(quteproc, server, pattern, replacement, name):
|
||||||
# First wait until the session was actually saved
|
# First wait until the session was actually saved
|
||||||
quteproc.wait_for(category='message', loglevel=logging.INFO,
|
quteproc.wait_for(category='message', loglevel=logging.INFO,
|
||||||
message='Saved session {}.'.format(name))
|
message='Saved session {}.'.format(name))
|
||||||
filename = os.path.join(quteproc.basedir, 'data', 'sessions',
|
filename = os.path.join(quteproc.basedir, 'data', 'sessions',
|
||||||
name + '.yml')
|
name + '.yml')
|
||||||
replacement = replacement.replace('(port)', str(httpbin.port)) # yo dawg
|
replacement = replacement.replace('(port)', str(server.port)) # yo dawg
|
||||||
with open(filename, 'r', encoding='utf-8') as f:
|
with open(filename, 'r', encoding='utf-8') as f:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
with open(filename, 'w', encoding='utf-8') as f:
|
with open(filename, 'w', encoding='utf-8') as f:
|
||||||
|
@ -21,8 +21,6 @@ import pytest
|
|||||||
|
|
||||||
import pytest_bdd as bdd
|
import pytest_bdd as bdd
|
||||||
|
|
||||||
from PyQt5.QtCore import PYQT_VERSION
|
|
||||||
|
|
||||||
|
|
||||||
bdd.scenarios('yankpaste.feature')
|
bdd.scenarios('yankpaste.feature')
|
||||||
|
|
||||||
@ -34,10 +32,6 @@ def init_fake_clipboard(quteproc):
|
|||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse('I insert "{value}" into the text field'))
|
@bdd.when(bdd.parsers.parse('I insert "{value}" into the text field'))
|
||||||
def set_text_field(request, quteproc, value):
|
def set_text_field(quteproc, value):
|
||||||
if request.config.webengine and PYQT_VERSION >= 0x50700:
|
quteproc.send_cmd(":jseval --world=0 set_text('{}')".format(value))
|
||||||
cmd = ":jseval --world=0 set_text('{}')".format(value)
|
|
||||||
else:
|
|
||||||
cmd = ":jseval set_text('{}')".format(value)
|
|
||||||
quteproc.send_cmd(cmd)
|
|
||||||
quteproc.wait_for_js('textarea set to: ' + value)
|
quteproc.wait_for_js('textarea set to: ' + value)
|
||||||
|
@ -415,10 +415,10 @@ class QuteProc(testprocess.Process):
|
|||||||
if any(path.startswith(scheme) for scheme in special_schemes):
|
if any(path.startswith(scheme) for scheme in special_schemes):
|
||||||
return path
|
return path
|
||||||
else:
|
else:
|
||||||
httpbin = self.request.getfixturevalue('httpbin')
|
server = self.request.getfixturevalue('server')
|
||||||
return '{}://localhost:{}/{}'.format(
|
return '{}://localhost:{}/{}'.format(
|
||||||
'https' if https else 'http',
|
'https' if https else 'http',
|
||||||
httpbin.port if port is None else port,
|
server.port if port is None else port,
|
||||||
path if path != '/' else '')
|
path if path != '/' else '')
|
||||||
|
|
||||||
def wait_for_js(self, message):
|
def wait_for_js(self, message):
|
||||||
@ -778,7 +778,7 @@ def _xpath_escape(text):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='module')
|
@pytest.fixture(scope='module')
|
||||||
def quteproc_process(qapp, httpbin, request):
|
def quteproc_process(qapp, server, request):
|
||||||
"""Fixture for qutebrowser process which is started once per file."""
|
"""Fixture for qutebrowser process which is started once per file."""
|
||||||
# Passing request so it has an initial config
|
# Passing request so it has an initial config
|
||||||
proc = QuteProc(request)
|
proc = QuteProc(request)
|
||||||
@ -788,7 +788,7 @@ def quteproc_process(qapp, httpbin, request):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def quteproc(quteproc_process, httpbin, request):
|
def quteproc(quteproc_process, server, request):
|
||||||
"""Per-test qutebrowser fixture which uses the per-file process."""
|
"""Per-test qutebrowser fixture which uses the per-file process."""
|
||||||
request.node._quteproc_log = quteproc_process.captured_log
|
request.node._quteproc_log = quteproc_process.captured_log
|
||||||
quteproc_process.before_test()
|
quteproc_process.before_test()
|
||||||
@ -798,7 +798,7 @@ def quteproc(quteproc_process, httpbin, request):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def quteproc_new(qapp, httpbin, request):
|
def quteproc_new(qapp, server, request):
|
||||||
"""Per-test qutebrowser process to test invocations."""
|
"""Per-test qutebrowser process to test invocations."""
|
||||||
proc = QuteProc(request)
|
proc = QuteProc(request)
|
||||||
request.node._quteproc_log = proc.captured_log
|
request.node._quteproc_log = proc.captured_log
|
||||||
|
@ -66,23 +66,23 @@ class FakeRequest:
|
|||||||
|
|
||||||
"""Fake for request."""
|
"""Fake for request."""
|
||||||
|
|
||||||
def __init__(self, node, config, httpbin):
|
def __init__(self, node, config, server):
|
||||||
self.node = node
|
self.node = node
|
||||||
self.config = config
|
self.config = config
|
||||||
self._httpbin = httpbin
|
self._server = server
|
||||||
|
|
||||||
def getfixturevalue(self, name):
|
def getfixturevalue(self, name):
|
||||||
assert name == 'httpbin'
|
assert name == 'server'
|
||||||
return self._httpbin
|
return self._server
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def request_mock(quteproc, monkeypatch, httpbin):
|
def request_mock(quteproc, monkeypatch, server):
|
||||||
"""Patch out a pytest request."""
|
"""Patch out a pytest request."""
|
||||||
fake_call = FakeRepCall()
|
fake_call = FakeRepCall()
|
||||||
fake_config = FakeConfig()
|
fake_config = FakeConfig()
|
||||||
fake_node = FakeNode(fake_call)
|
fake_node = FakeNode(fake_call)
|
||||||
fake_request = FakeRequest(fake_node, fake_config, httpbin)
|
fake_request = FakeRequest(fake_node, fake_config, server)
|
||||||
assert not hasattr(fake_request.node.rep_call, 'wasxfail')
|
assert not hasattr(fake_request.node.rep_call, 'wasxfail')
|
||||||
monkeypatch.setattr(quteproc, 'request', fake_request)
|
monkeypatch.setattr(quteproc, 'request', fake_request)
|
||||||
return fake_request
|
return fake_request
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""Test the httpbin webserver used for tests."""
|
"""Test the server webserver used for tests."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import urllib.request
|
import urllib.request
|
||||||
@ -27,14 +27,14 @@ import pytest
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('path, content, expected', [
|
@pytest.mark.parametrize('path, content, expected', [
|
||||||
('/', '<title>httpbin(1): HTTP Client Testing Service</title>', True),
|
('/', 'qutebrowser test webserver', True),
|
||||||
# https://github.com/Runscope/httpbin/issues/245
|
# https://github.com/Runscope/server/issues/245
|
||||||
('/', 'www.google-analytics.com', False),
|
('/', 'www.google-analytics.com', False),
|
||||||
('/data/hello.txt', 'Hello World!', True),
|
('/data/hello.txt', 'Hello World!', True),
|
||||||
])
|
])
|
||||||
def test_httpbin(httpbin, qtbot, path, content, expected):
|
def test_server(server, qtbot, path, content, expected):
|
||||||
with qtbot.waitSignal(httpbin.new_request, timeout=100):
|
with qtbot.waitSignal(server.new_request, timeout=100):
|
||||||
url = 'http://localhost:{}{}'.format(httpbin.port, path)
|
url = 'http://localhost:{}{}'.format(server.port, path)
|
||||||
try:
|
try:
|
||||||
response = urllib.request.urlopen(url)
|
response = urllib.request.urlopen(url)
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
@ -47,7 +47,7 @@ def test_httpbin(httpbin, qtbot, path, content, expected):
|
|||||||
|
|
||||||
data = response.read().decode('utf-8')
|
data = response.read().decode('utf-8')
|
||||||
|
|
||||||
assert httpbin.get_requests() == [httpbin.ExpectedRequest('GET', path)]
|
assert server.get_requests() == [server.ExpectedRequest('GET', path)]
|
||||||
assert (content in data) == expected
|
assert (content in data) == expected
|
||||||
|
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ def test_httpbin(httpbin, qtbot, path, content, expected):
|
|||||||
({'verb': 'GET', 'path': '/', 'status': 200}, 'GET', '/foo', False),
|
({'verb': 'GET', 'path': '/', 'status': 200}, 'GET', '/foo', False),
|
||||||
({'verb': 'POST', 'path': '/', 'status': 200}, 'GET', '/', False),
|
({'verb': 'POST', 'path': '/', 'status': 200}, 'GET', '/', False),
|
||||||
])
|
])
|
||||||
def test_expected_request(httpbin, line, verb, path, equal):
|
def test_expected_request(server, line, verb, path, equal):
|
||||||
expected = httpbin.ExpectedRequest(verb, path)
|
expected = server.ExpectedRequest(verb, path)
|
||||||
request = httpbin.Request(json.dumps(line))
|
request = server.Request(json.dumps(line))
|
||||||
assert (expected == request) == equal
|
assert (expected == request) == equal
|
||||||
|
@ -90,7 +90,7 @@ def _render_log(data, threshold=100):
|
|||||||
|
|
||||||
@pytest.hookimpl(hookwrapper=True)
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
def pytest_runtest_makereport(item, call):
|
def pytest_runtest_makereport(item, call):
|
||||||
"""Add qutebrowser/httpbin sections to captured output if a test failed."""
|
"""Add qutebrowser/server sections to captured output if a test failed."""
|
||||||
outcome = yield
|
outcome = yield
|
||||||
if call.when not in ['call', 'teardown']:
|
if call.when not in ['call', 'teardown']:
|
||||||
return
|
return
|
||||||
@ -100,7 +100,7 @@ def pytest_runtest_makereport(item, call):
|
|||||||
return
|
return
|
||||||
|
|
||||||
quteproc_log = getattr(item, '_quteproc_log', None)
|
quteproc_log = getattr(item, '_quteproc_log', None)
|
||||||
httpbin_log = getattr(item, '_httpbin_log', None)
|
server_log = getattr(item, '_server_log', None)
|
||||||
|
|
||||||
if not hasattr(report.longrepr, 'addsection'):
|
if not hasattr(report.longrepr, 'addsection'):
|
||||||
# In some conditions (on macOS and Windows it seems), report.longrepr
|
# In some conditions (on macOS and Windows it seems), report.longrepr
|
||||||
@ -114,8 +114,8 @@ def pytest_runtest_makereport(item, call):
|
|||||||
if quteproc_log is not None:
|
if quteproc_log is not None:
|
||||||
report.longrepr.addsection("qutebrowser output",
|
report.longrepr.addsection("qutebrowser output",
|
||||||
_render_log(quteproc_log))
|
_render_log(quteproc_log))
|
||||||
if httpbin_log is not None:
|
if server_log is not None:
|
||||||
report.longrepr.addsection("httpbin output", _render_log(httpbin_log))
|
report.longrepr.addsection("server output", _render_log(server_log))
|
||||||
|
|
||||||
|
|
||||||
class Process(QObject):
|
class Process(QObject):
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""Fixtures for the httpbin webserver."""
|
"""Fixtures for the server webserver."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@ -35,7 +35,7 @@ from qutebrowser.utils import utils
|
|||||||
|
|
||||||
class Request(testprocess.Line):
|
class Request(testprocess.Line):
|
||||||
|
|
||||||
"""A parsed line from the httpbin/flask log output.
|
"""A parsed line from the flask log output.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
verb/path/status: Parsed from the log output.
|
verb/path/status: Parsed from the log output.
|
||||||
@ -67,22 +67,20 @@ class Request(testprocess.Line):
|
|||||||
'/favicon.ico': [http.client.NOT_FOUND],
|
'/favicon.ico': [http.client.NOT_FOUND],
|
||||||
'/does-not-exist': [http.client.NOT_FOUND],
|
'/does-not-exist': [http.client.NOT_FOUND],
|
||||||
'/does-not-exist-2': [http.client.NOT_FOUND],
|
'/does-not-exist-2': [http.client.NOT_FOUND],
|
||||||
'/status/404': [http.client.NOT_FOUND],
|
'/404': [http.client.NOT_FOUND],
|
||||||
|
|
||||||
'/custom/redirect-later': [http.client.FOUND],
|
'/redirect-later': [http.client.FOUND],
|
||||||
'/custom/redirect-self': [http.client.FOUND],
|
'/redirect-self': [http.client.FOUND],
|
||||||
'/redirect-to': [http.client.FOUND],
|
'/redirect-to': [http.client.FOUND],
|
||||||
|
'/relative-redirect': [http.client.FOUND],
|
||||||
|
'/absolute-redirect': [http.client.FOUND],
|
||||||
|
|
||||||
'/cookies/set': [http.client.FOUND],
|
'/cookies/set': [http.client.FOUND],
|
||||||
|
|
||||||
'/custom/500-inline': [http.client.INTERNAL_SERVER_ERROR],
|
'/500-inline': [http.client.INTERNAL_SERVER_ERROR],
|
||||||
}
|
}
|
||||||
for i in range(15):
|
for i in range(15):
|
||||||
path_to_statuses['/redirect/{}'.format(i)] = [http.client.FOUND]
|
path_to_statuses['/redirect/{}'.format(i)] = [http.client.FOUND]
|
||||||
path_to_statuses['/relative-redirect/{}'.format(i)] = [
|
|
||||||
http.client.FOUND]
|
|
||||||
path_to_statuses['/absolute-redirect/{}'.format(i)] = [
|
|
||||||
http.client.FOUND]
|
|
||||||
for suffix in ['', '1', '2', '3', '4', '5', '6']:
|
for suffix in ['', '1', '2', '3', '4', '5', '6']:
|
||||||
key = '/basic-auth/user{}/password{}'.format(suffix, suffix)
|
key = '/basic-auth/user{}/password{}'.format(suffix, suffix)
|
||||||
path_to_statuses[key] = [http.client.UNAUTHORIZED, http.client.OK]
|
path_to_statuses[key] = [http.client.UNAUTHORIZED, http.client.OK]
|
||||||
@ -130,7 +128,7 @@ class ExpectedRequest:
|
|||||||
|
|
||||||
class WebserverProcess(testprocess.Process):
|
class WebserverProcess(testprocess.Process):
|
||||||
|
|
||||||
"""Abstraction over a running HTTPbin server process.
|
"""Abstraction over a running Flask server process.
|
||||||
|
|
||||||
Reads the log from its stdout and parses it.
|
Reads the log from its stdout and parses it.
|
||||||
|
|
||||||
@ -186,31 +184,31 @@ class WebserverProcess(testprocess.Process):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session', autouse=True)
|
@pytest.fixture(scope='session', autouse=True)
|
||||||
def httpbin(qapp):
|
def server(qapp):
|
||||||
"""Fixture for an httpbin object which ensures clean setup/teardown."""
|
"""Fixture for an server object which ensures clean setup/teardown."""
|
||||||
httpbin = WebserverProcess('webserver_sub')
|
server = WebserverProcess('webserver_sub')
|
||||||
httpbin.start()
|
server.start()
|
||||||
yield httpbin
|
yield server
|
||||||
httpbin.cleanup()
|
server.cleanup()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def httpbin_after_test(httpbin, request):
|
def server_after_test(server, request):
|
||||||
"""Fixture to clean httpbin request list after each test."""
|
"""Fixture to clean server request list after each test."""
|
||||||
request.node._httpbin_log = httpbin.captured_log
|
request.node._server_log = server.captured_log
|
||||||
yield
|
yield
|
||||||
httpbin.after_test()
|
server.after_test()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ssl_server(request, qapp):
|
def ssl_server(request, qapp):
|
||||||
"""Fixture for a webserver with a self-signed SSL certificate.
|
"""Fixture for a webserver with a self-signed SSL certificate.
|
||||||
|
|
||||||
This needs to be explicitly used in a test, and overwrites the httpbin log
|
This needs to be explicitly used in a test, and overwrites the server log
|
||||||
used in that test.
|
used in that test.
|
||||||
"""
|
"""
|
||||||
server = WebserverProcess('webserver_sub_ssl')
|
server = WebserverProcess('webserver_sub_ssl')
|
||||||
request.node._httpbin_log = server.captured_log
|
request.node._server_log = server.captured_log
|
||||||
server.start()
|
server.start()
|
||||||
yield server
|
yield server
|
||||||
server.after_test()
|
server.after_test()
|
||||||
|
@ -17,9 +17,13 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""httpbin web server for end2end tests.
|
"""Web server for end2end tests.
|
||||||
|
|
||||||
This script gets called as a QProcess from end2end/conftest.py.
|
This script gets called as a QProcess from end2end/conftest.py.
|
||||||
|
|
||||||
|
Some of the handlers here are inspired by the server project, but simplified
|
||||||
|
for qutebrowser's needs. Note that it probably doesn't handle e.g. multiple
|
||||||
|
parameters or headers with the same name properly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@ -29,15 +33,20 @@ import signal
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from httpbin.core import app
|
|
||||||
from httpbin.structures import CaseInsensitiveDict
|
|
||||||
import cheroot.wsgi
|
import cheroot.wsgi
|
||||||
import flask
|
import flask
|
||||||
|
|
||||||
|
app = flask.Flask(__name__)
|
||||||
_redirect_later_event = None
|
_redirect_later_event = None
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def root():
|
||||||
|
"""Show simple text."""
|
||||||
|
return flask.Response(b'qutebrowser test webserver, '
|
||||||
|
b'<a href="/user-agent">user agent</a>')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/data/<path:path>')
|
@app.route('/data/<path:path>')
|
||||||
def send_data(path):
|
def send_data(path):
|
||||||
"""Send a given data file to qutebrowser.
|
"""Send a given data file to qutebrowser.
|
||||||
@ -57,15 +66,14 @@ def send_data(path):
|
|||||||
return flask.send_from_directory(data_dir, path)
|
return flask.send_from_directory(data_dir, path)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/custom/redirect-later')
|
@app.route('/redirect-later')
|
||||||
def redirect_later():
|
def redirect_later():
|
||||||
"""302 redirect to / after the given delay.
|
"""302 redirect to / after the given delay.
|
||||||
|
|
||||||
If delay is -1, wait until a request on redirect-later-continue is done.
|
If delay is -1, wait until a request on redirect-later-continue is done.
|
||||||
"""
|
"""
|
||||||
global _redirect_later_event
|
global _redirect_later_event
|
||||||
args = CaseInsensitiveDict(flask.request.args.items())
|
delay = int(flask.request.args.get('delay', '1'))
|
||||||
delay = int(args.get('delay', '1'))
|
|
||||||
if delay == -1:
|
if delay == -1:
|
||||||
_redirect_later_event = threading.Event()
|
_redirect_later_event = threading.Event()
|
||||||
ok = _redirect_later_event.wait(timeout=30 * 1000)
|
ok = _redirect_later_event.wait(timeout=30 * 1000)
|
||||||
@ -77,7 +85,7 @@ def redirect_later():
|
|||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
@app.route('/custom/redirect-later-continue')
|
@app.route('/redirect-later-continue')
|
||||||
def redirect_later_continue():
|
def redirect_later_continue():
|
||||||
"""Continue a redirect-later request."""
|
"""Continue a redirect-later request."""
|
||||||
if _redirect_later_event is None:
|
if _redirect_later_event is None:
|
||||||
@ -87,7 +95,50 @@ def redirect_later_continue():
|
|||||||
return flask.Response(b'Continued redirect.')
|
return flask.Response(b'Continued redirect.')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/custom/content-size')
|
@app.route('/redirect-self')
|
||||||
|
def redirect_self():
|
||||||
|
"""302 Redirects to itself."""
|
||||||
|
return app.make_response(flask.redirect(flask.url_for('redirect_self')))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/redirect/<int:n>')
|
||||||
|
def redirect_n_times(n):
|
||||||
|
"""302 Redirects n times."""
|
||||||
|
assert n > 0
|
||||||
|
return flask.redirect(flask.url_for('redirect_n_times', n=n-1))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/relative-redirect')
|
||||||
|
def relative_redirect():
|
||||||
|
"""302 Redirect once."""
|
||||||
|
response = app.make_response('')
|
||||||
|
response.status_code = 302
|
||||||
|
response.headers['Location'] = flask.url_for('root')
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/absolute-redirect')
|
||||||
|
def absolute_redirect():
|
||||||
|
"""302 Redirect once."""
|
||||||
|
response = app.make_response('')
|
||||||
|
response.status_code = 302
|
||||||
|
response.headers['Location'] = flask.url_for('root', _external=True)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/redirect-to')
|
||||||
|
def redirect_to():
|
||||||
|
"""302/3XX Redirects to the given URL."""
|
||||||
|
# We need to build the response manually and convert to UTF-8 to prevent
|
||||||
|
# werkzeug from "fixing" the URL. This endpoint should set the Location
|
||||||
|
# header to the exact string supplied.
|
||||||
|
response = app.make_response('')
|
||||||
|
response.status_code = 302
|
||||||
|
response.headers['Location'] = flask.request.args['url'].encode('utf-8')
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/content-size')
|
||||||
def content_size():
|
def content_size():
|
||||||
"""Send two bytes of data without a content-size."""
|
"""Send two bytes of data without a content-size."""
|
||||||
def generate_bytes():
|
def generate_bytes():
|
||||||
@ -102,7 +153,7 @@ def content_size():
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.route('/custom/twenty-mb')
|
@app.route('/twenty-mb')
|
||||||
def twenty_mb():
|
def twenty_mb():
|
||||||
"""Send 20MB of data."""
|
"""Send 20MB of data."""
|
||||||
def generate_bytes():
|
def generate_bytes():
|
||||||
@ -116,13 +167,7 @@ def twenty_mb():
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.route('/custom/redirect-self')
|
@app.route('/500-inline')
|
||||||
def redirect_self():
|
|
||||||
"""302 Redirects to itself."""
|
|
||||||
return app.make_response(flask.redirect(flask.url_for('redirect_self')))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/custom/500-inline')
|
|
||||||
def internal_error_attachment():
|
def internal_error_attachment():
|
||||||
"""A 500 error with Content-Disposition: inline."""
|
"""A 500 error with Content-Disposition: inline."""
|
||||||
response = flask.Response(b"", headers={
|
response = flask.Response(b"", headers={
|
||||||
@ -133,6 +178,85 @@ def internal_error_attachment():
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/cookies')
|
||||||
|
def view_cookies():
|
||||||
|
"""Show cookies."""
|
||||||
|
return flask.jsonify(cookies=flask.request.cookies)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/cookies/set')
|
||||||
|
def set_cookies():
|
||||||
|
"""Set cookie(s) as provided by the query string."""
|
||||||
|
r = app.make_response(flask.redirect(flask.url_for('view_cookies')))
|
||||||
|
for key, value in flask.request.args.items():
|
||||||
|
r.set_cookie(key=key, value=value)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/basic-auth/<user>/<passwd>')
|
||||||
|
def basic_auth(user='user', passwd='passwd'):
|
||||||
|
"""Prompt the user for authorization using HTTP Basic Auth."""
|
||||||
|
auth = flask.request.authorization
|
||||||
|
if not auth or auth.username != user or auth.password != passwd:
|
||||||
|
r = flask.make_response()
|
||||||
|
r.status_code = 401
|
||||||
|
r.headers = {'WWW-Authenticate': 'Basic realm="Fake Realm"'}
|
||||||
|
return r
|
||||||
|
|
||||||
|
return flask.jsonify(authenticated=True, user=user)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/drip')
|
||||||
|
def drip():
|
||||||
|
"""Drip data over a duration."""
|
||||||
|
duration = float(flask.request.args.get('duration'))
|
||||||
|
numbytes = int(flask.request.args.get('numbytes'))
|
||||||
|
pause = duration / numbytes
|
||||||
|
|
||||||
|
def generate_bytes():
|
||||||
|
for _ in range(numbytes):
|
||||||
|
yield u"*".encode('utf-8')
|
||||||
|
time.sleep(pause)
|
||||||
|
|
||||||
|
response = flask.Response(generate_bytes(), headers={
|
||||||
|
"Content-Type": "application/octet-stream",
|
||||||
|
"Content-Length": str(numbytes),
|
||||||
|
})
|
||||||
|
response.status_code = 200
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/404')
|
||||||
|
def status_404():
|
||||||
|
r = flask.make_response()
|
||||||
|
r.status_code = 404
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/headers')
|
||||||
|
def view_headers():
|
||||||
|
"""Return HTTP headers."""
|
||||||
|
return flask.jsonify(headers=dict(flask.request.headers))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/response-headers')
|
||||||
|
def response_headers():
|
||||||
|
"""Return a set of response headers from the query string."""
|
||||||
|
headers = flask.request.args
|
||||||
|
response = flask.jsonify(headers)
|
||||||
|
response.headers.extend(headers)
|
||||||
|
|
||||||
|
response = flask.jsonify(dict(response.headers))
|
||||||
|
response.headers.extend(headers)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/user-agent')
|
||||||
|
def view_user_agent():
|
||||||
|
"""Return User-Agent."""
|
||||||
|
return flask.jsonify({'user-agent': flask.request.headers['user-agent']})
|
||||||
|
|
||||||
|
|
||||||
@app.after_request
|
@app.after_request
|
||||||
def log_request(response):
|
def log_request(response):
|
||||||
"""Log a webserver request."""
|
"""Log a webserver request."""
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user