Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
718f73be2e | ||
|
|
da4865d408 | ||
|
|
fd4ff3c9ce | ||
|
|
ad9b50601c | ||
|
|
c88a94f1cc | ||
|
|
13332cd2cf | ||
|
|
48808e59cb | ||
|
|
5571ef4e62 | ||
|
|
b4de889df9 | ||
|
|
4c9360237f | ||
|
|
10538738e0 | ||
|
|
bd5f84bddf | ||
|
|
93ae6ad592 | ||
|
|
146f8e72ae | ||
|
|
f61662fa52 | ||
|
|
e9b4c2a66e | ||
|
|
6a7ab7edb3 | ||
|
|
f7f96484e8 | ||
|
|
840d2e4423 | ||
|
|
e54f2a090a | ||
|
|
55ce4b7ed2 | ||
|
|
3daf823da8 | ||
|
|
72feb2c19f | ||
|
|
6d04508490 | ||
|
|
93ebd846ab | ||
|
|
9ee473a54c | ||
|
|
d1cced0da4 | ||
|
|
22644c41da | ||
|
|
6b5857ef7d | ||
|
|
b0f4cc6924 | ||
|
|
218637af84 | ||
|
|
dd84845f01 | ||
|
|
cf53f9042a | ||
|
|
f48266f72f | ||
|
|
5dac848968 | ||
|
|
341aa1e700 | ||
|
|
ebf81c06ae | ||
|
|
a3eb8d6561 | ||
|
|
af4b02bf46 | ||
|
|
ac29c579ff | ||
|
|
db5ec363cd | ||
|
|
f352c72d1d | ||
|
|
b1f1a0cafa | ||
|
|
749056ff90 | ||
|
|
e7b00ace73 | ||
|
|
442bdd4a4f | ||
|
|
900efe4a36 | ||
|
|
f8a78a0962 | ||
|
|
60e8abaa89 |
@@ -5,15 +5,15 @@ cache:
|
||||
build: off
|
||||
environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python36-x64\python.exe
|
||||
PYTHON: C:\Python36\python.exe
|
||||
matrix:
|
||||
- TESTENV: py36-pyqt511
|
||||
- TESTENV: py36-pyqt510
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
- '%PYTHON% -m pip install -U pip'
|
||||
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
|
||||
- 'set PATH=C:\Python36-x64;%PATH'
|
||||
- 'set PATH=%PATH%;C:\Python36'
|
||||
|
||||
test_script:
|
||||
- '%PYTHON% -m tox -e %TESTENV%'
|
||||
|
||||
2
.flake8
2
.flake8
@@ -44,7 +44,7 @@ ignore =
|
||||
min-version = 3.4.0
|
||||
max-complexity = 12
|
||||
per-file-ignores =
|
||||
/tests/**/*.py : D100,D101,D401
|
||||
/tests/**/test_*.py : D100,D101,D401
|
||||
/tests/unit/browser/test_history.py : N806
|
||||
/tests/helpers/fixtures.py : N806
|
||||
/tests/unit/browser/webkit/http/test_content_disposition.py : D400
|
||||
|
||||
5
.github/CONTRIBUTING.asciidoc
vendored
5
.github/CONTRIBUTING.asciidoc
vendored
@@ -1,8 +1,3 @@
|
||||
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
|
||||
because of exams coming up. Review of non-trivial pull requests will thus be
|
||||
delayed until then. If you're reading this note after mid-September, please
|
||||
open an issue.
|
||||
|
||||
- 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.
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -41,4 +41,3 @@ TODO
|
||||
/scripts/testbrowser/cpp/webengine/testbrowser
|
||||
/scripts/testbrowser/cpp/webengine/.qmake.stash
|
||||
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
||||
/misc/file_version_info.txt
|
||||
|
||||
19
.travis.yml
19
.travis.yml
@@ -18,19 +18,20 @@ matrix:
|
||||
python: 3.5
|
||||
env: TESTENV=py35-pyqt571
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt59
|
||||
env: TESTENV=py36-pyqt59-cov
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt510
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt511-cov
|
||||
# https://github.com/travis-ci/travis-ci/issues/9069
|
||||
- os: linux
|
||||
python: 3.7
|
||||
# We need a newer Xvfb as a WORKAROUND for:
|
||||
# https://bugreports.qt.io/browse/QTBUG-64928
|
||||
sudo: required
|
||||
dist: xenial
|
||||
env: TESTENV=py37-pyqt511
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
|
||||
packages:
|
||||
- xvfb
|
||||
- os: osx
|
||||
env: TESTENV=py37 OSX=sierra
|
||||
env: TESTENV=py36 OSX=sierra
|
||||
osx_image: xcode9.2
|
||||
language: generic
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
recursive-include qutebrowser *.py
|
||||
recursive-include qutebrowser/img *.svg *.png
|
||||
recursive-include qutebrowser/test *.py
|
||||
recursive-include qutebrowser/javascript *.js
|
||||
graft qutebrowser/html
|
||||
graft qutebrowser/3rdparty
|
||||
@@ -26,18 +27,20 @@ prune scripts/dev
|
||||
prune scripts/testbrowser/cpp
|
||||
prune .github
|
||||
exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
recursive-exclude doc *.asciidoc
|
||||
include doc/qutebrowser.1.asciidoc
|
||||
include doc/changelog.asciidoc
|
||||
prune tests
|
||||
prune qutebrowser/3rdparty
|
||||
exclude pytest.ini
|
||||
exclude qutebrowser.rcc
|
||||
exclude qutebrowser/javascript/.eslintrc.yaml
|
||||
exclude qutebrowser/javascript/.eslintignore
|
||||
exclude doc/help
|
||||
exclude .*
|
||||
exclude misc/appveyor_install.py
|
||||
exclude misc/qutebrowser.spec
|
||||
exclude misc/qutebrowser.nsi
|
||||
exclude misc/qutebrowser.rcc
|
||||
|
||||
global-exclude __pycache__ *.pyc *.pyo
|
||||
|
||||
@@ -9,6 +9,8 @@ qutebrowser
|
||||
// QUTE_WEB_HIDE
|
||||
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
|
||||
|
||||
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/LICENSE"]
|
||||
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
|
||||
image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"]
|
||||
image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"]
|
||||
image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"]
|
||||
@@ -97,7 +99,7 @@ Requirements
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
||||
* http://qt.io/[Qt] 5.7.1 or newer (5.11.1 recommended) with the following modules:
|
||||
* http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules:
|
||||
- QtCore / qtbase
|
||||
- QtQuick (part of qtbase in some distributions)
|
||||
- QtSQL (part of qtbase in some distributions)
|
||||
@@ -107,7 +109,7 @@ The following software and libraries are required to run qutebrowser:
|
||||
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
|
||||
supported
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||
(5.11.2 recommended) for Python 3
|
||||
(5.10 recommended) for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
|
||||
@@ -15,132 +15,6 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.5.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Rare crash when an error occurs in downloads.
|
||||
|
||||
v1.4.0
|
||||
------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Support for the bundled `sip` module in PyQt 5.11 and other changes in
|
||||
Qt/PyQt 5.11.x.
|
||||
- New `--debug-flag log-requests` to log requests to the debug log for
|
||||
debugging.
|
||||
- New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically
|
||||
selects the first hint.
|
||||
- New `input.escape_quits_reporter` setting which can be used to avoid
|
||||
accidentally quitting the crash reporter when pressing escape.
|
||||
- New `qute-lastpass` userscript which uses the LastPass CLI to fill passwords.
|
||||
- The Makefile now installs a `/usr/share/metainfo/qutebrowser.appdata.xml` file.
|
||||
- QtWebEngine: Support for printing from webpages via `window.print`.
|
||||
- QtWebEngine: Support for muting tabs:
|
||||
* New `{audio}` field for `window.title_format` and `tabs.title.format` which
|
||||
displays `[M]`/`[A]` for muted/recently audible tabs.
|
||||
* New `:tab-mute` command (bound to `<Alt-m>`) to mute/unmute a tab.
|
||||
- QtWebEngine: Support for `content.cookies.accept` with third-party cookies
|
||||
blocked by default (requires Qt 5.11).
|
||||
- QtWebEngine: New settings:
|
||||
* Support for requesting persistent storage via
|
||||
`navigator.webkitPersistentStorage.requestQuota` with a new
|
||||
`content.persistent_storage` setting (requires Qt 5.11).
|
||||
This setting also supports URL patterns.
|
||||
* Support for registering custom protocol handlers via
|
||||
`navigator.registerProtocolHandler` with a new
|
||||
`content.register_protocol_handler` setting (requires Qt 5.11).
|
||||
This setting also supports URL patterns.
|
||||
* Support for WebRTC screen sharing with a new `content.desktop_capture`
|
||||
setting (requires Qt 5.10).
|
||||
This setting also supports URL patterns.
|
||||
* New `content.autoplay` setting to enable/disable automatic video playback
|
||||
(requires Qt 5.10).
|
||||
* New `content.webrtc_public_interfaces_only` setting to only expose public
|
||||
interfaces over WebRTC (requires Qt 5.9.2 or 5.11).
|
||||
* New `content.canvas_reading` setting to disable reading from canvas
|
||||
elements.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The following settings now support URL patterns:
|
||||
* `content.headers.do_not_track`
|
||||
* `content.headers.custom`
|
||||
* `content.headers.accept_language`
|
||||
* `content.headers.user_agent`
|
||||
* `content.ssl_strict`
|
||||
* `content.geolocation`
|
||||
* `content.notifications`
|
||||
* `content.media_capture`
|
||||
- The Windows/macOS releases now bundle Qt 5.11.1 which is based on
|
||||
Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87.
|
||||
- New short flags for commandline arguments: `-B` and `-T` for `--basedir` and
|
||||
`--temp-basedir`; `-d` and `-D` for `--debug` and `--debug-flag`.
|
||||
- Deleting history items via `:history-clear` or `:completion-item-del` now
|
||||
also removes that URL from QtWebEngine's visited links.
|
||||
- There's now completion for commands taking a variable count of arguments
|
||||
(like `:config-cycle`).
|
||||
- QtWebEngine: On Qt 5.11.1, no reloads are needed anymore when switching
|
||||
between pages with changed settings (e.g. `content.javascript.enabled`).
|
||||
- The `qt.force_software_rendering` setting changed from a boolean to taking
|
||||
different values (`software-opengl`, `qt-quick` and `chromium`) for different
|
||||
kinds of software rendering workarounds.
|
||||
- On Qt 5.11, using wayland with QtWebEngine is now possible when using
|
||||
software rendering.
|
||||
- GreaseMonkey scripts now get their own global scope (based on the page's
|
||||
one), which allows scripts like OneeChan to work.
|
||||
- Rapid hinting is now supported with the `yank` and `yank-primary` targets,
|
||||
copying newline-separated links.
|
||||
- QtWebEngine: On Qt 5.11, the developer tools (inspector) can now be used
|
||||
securely and without requiring the `--enable-webengine-inspector` option.
|
||||
- The `<Enter>` key (`:follow-selected`) now follows the currently focused
|
||||
element if there's no selection.
|
||||
- The `--logfilter` argument now can be prepended with an exclamation mark
|
||||
(e.g. `--logfilter '!init,destroy'`) to invert the filter.
|
||||
- `:view-source` now has a `--pygments` flag which uses the "old" way of
|
||||
rendering sources even with QtWebEngine.
|
||||
- Improved error messages when a setting needs a newer Qt version.
|
||||
- QtWebEngine: Various improvements to make the cursor more visible in caret
|
||||
browsing.
|
||||
- When a prompt is opened in insert/passthrough mode, the mode is restored
|
||||
after closing the prompt.
|
||||
- On Qt 5.10 or newer, dictionaries are now read from the qutebrowser data
|
||||
directory (e.g. `~/.local/share/qutebrowser`) instead of `/usr/share/qt`.
|
||||
Existing dictionaries are copied over.
|
||||
- If an error while parsing `~/.netrc` occurs, the cause of the error is now
|
||||
logged.
|
||||
- On Qt 5.9 or newer, certificate errors now show Chromium's detailed error
|
||||
page.
|
||||
- Greasemonkey scripts now support a "@qute-js-world" tag to run them in a
|
||||
different JavaScript context.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Various subtle keyboard focus issues.
|
||||
- The security fix in v1.3.3 caused URLs with ampersands
|
||||
(`www.example.com?one=1&two=2`) to send the wrong arguments when clicked on
|
||||
the `qute://history` page.
|
||||
- Crash when opening a PDF page with PDF.js enabled (on QtWebKit), but no
|
||||
PDF.js installed.
|
||||
- Crash when closing a tab shortly after opening it.
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- No prebuilt binaries for 32-bit Windows are supplied anymore. This is due to
|
||||
Qt removing QtWebEngine support for those upstream. It might be possible to
|
||||
distribute 32-bit binaries again with Qt 5.12 in December, but that will only
|
||||
happen if it turns out enough people actually need 32-bit support.
|
||||
- `:tab-detach` which has been deprecated in v1.1.0 has been removed.
|
||||
- The `content.developer_extras` setting got removed. On QtWebKit, developer
|
||||
extras are now automatically enabled when opening the inspector.
|
||||
|
||||
v1.3.3
|
||||
------
|
||||
|
||||
@@ -191,7 +65,6 @@ Fixed
|
||||
- Don't crash when a ² key is pressed (e.g. on AZERTY keyboards).
|
||||
- Don't crash when a tab is opened and quickly closed again.
|
||||
|
||||
|
||||
v1.3.0
|
||||
------
|
||||
|
||||
|
||||
@@ -5,11 +5,6 @@ The Compiler <mail@qutebrowser.org>
|
||||
:data-uri:
|
||||
:toc:
|
||||
|
||||
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
|
||||
because of exams coming up. Review of non-trivial pull requests will thus be
|
||||
delayed until then. If you're reading this note after mid-September, please
|
||||
open an issue.
|
||||
|
||||
I `<3` footnote:[Of course, that says `<3` in HTML.] contributors!
|
||||
|
||||
This document contains guidelines for contributing to qutebrowser, as well as
|
||||
@@ -90,16 +85,6 @@ git format-patch origin/master <1>
|
||||
<1> Replace `master` by the branch your work was based on, e.g.,
|
||||
`origin/develop`.
|
||||
|
||||
Running qutebrowser
|
||||
-------------------
|
||||
|
||||
After link:install.asciidoc#tox[installing qutebrowser via tox], you can run
|
||||
`.venv/bin/qutebrowser --debug --temp-basedir` to test your changes with debug
|
||||
logging enabled and without affecting existing running instances.
|
||||
|
||||
Alternatively, you can install qutebrowser's dependencies system-wide and run
|
||||
`python3 -m qutebrowser --debug --temp-basedir`.
|
||||
|
||||
Useful utilities
|
||||
----------------
|
||||
|
||||
@@ -203,8 +188,8 @@ There are some useful functions for debugging in the `qutebrowser.utils.debug`
|
||||
module.
|
||||
|
||||
When starting qutebrowser with the `--debug` flag, you also get useful debug
|
||||
logs. You can add +--logfilter _[!]category[,category,...]_+ to restrict
|
||||
logging to the given categories.
|
||||
logs. You can add +--logfilter _category[,category,...]_+ to restrict logging
|
||||
to the given categories.
|
||||
|
||||
With `--debug` there are also some additional +debug-_*_+ commands available,
|
||||
for example `:debug-all-objects` and `:debug-all-widgets` which print a list of
|
||||
@@ -581,23 +566,6 @@ can be useful for debugging:
|
||||
- chrome://gpuclean/ (crashes the current renderer process!)
|
||||
- chrome://ppapiflashcrash/
|
||||
- chrome://ppapiflashhang/
|
||||
- chrome://quota-internals/ (Qt 5.11)
|
||||
- chrome://taskscheduler-internals/ (Qt 5.11)
|
||||
- chrome://sandbox/ (Qt 5.11, Linux only)
|
||||
|
||||
QtWebEngine internals
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is mostly useful for qutebrowser maintainers to work around issues in Qt - if you don't understand it, don't worry, just ignore it.
|
||||
|
||||
The hierarchy of widgets when QtWebEngine is involved looks like this:
|
||||
|
||||
- qutebrowser has a `WebEngineTab` object, which is its abstraction over QtWebKit/QtWebEngine.
|
||||
- The `WebEngineTab` has a `_widget` attribute, which is the https://doc.qt.io/qt-5/qwebengineview.html[QWebEngineView]
|
||||
- That view has a https://doc.qt.io/qt-5/qwebenginepage.html[QWebEnginePage] for everything which doesn't require rendering.
|
||||
- The view also has a layout with exactly one element (which also is its `focusProxy()`)
|
||||
- That element is the http://code.qt.io/cgit/qt/qtwebengine.git/tree/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp[RenderWidgetHostViewQtDelegateWidget] (it inherits https://doc.qt.io/qt-5/qquickwidget.html[QQuickWidget]) - also often referred to as RWHV or RWHVQDW. It can be obtained via `sip.cast(tab._widget.focusProxy(), QQuickWidget)`.
|
||||
- Calling `rootObject()` on that gives us the https://doc.qt.io/qt-5/qquickitem.html[QQuickItem] where Chromium renders into (?). With it, we can do things like `.setRotation(20)`.
|
||||
|
||||
Style conventions
|
||||
-----------------
|
||||
@@ -694,6 +662,8 @@ New PyQt release
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* See above.
|
||||
* Install new PyQt in Windows VM (32- and 64-bit).
|
||||
* Download new installer and update PyQt installer path in `ci_install.py`.
|
||||
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
|
||||
|
||||
qutebrowser release
|
||||
@@ -715,8 +685,8 @@ qutebrowser release
|
||||
as closed.
|
||||
|
||||
* Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||
* Windows: Run `git checkout v1.X.Y; py -3.6 scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `pyenv shell 3.6.6 && git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* On server:
|
||||
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
||||
- Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser`
|
||||
|
||||
@@ -111,7 +111,6 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<tab-focus,tab-focus>>|Select the tab given as argument/[count].
|
||||
|<<tab-give,tab-give>>|Give the current tab to a new or existing window if win_id given.
|
||||
|<<tab-move,tab-move>>|Move the current tab according to the argument and [count].
|
||||
|<<tab-mute,tab-mute>>|Mute/Unmute the current/[count]th tab.
|
||||
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
|
||||
|<<tab-only,tab-only>>|Close all tabs except for the current one.
|
||||
|<<tab-pin,tab-pin>>|Pin/Unpin the current/[count]th tab.
|
||||
@@ -531,7 +530,7 @@ Show help about a command or setting.
|
||||
|
||||
[[hint]]
|
||||
=== hint
|
||||
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*] [*--first*]
|
||||
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*]
|
||||
['group'] ['target'] ['args' ['args' ...]]+
|
||||
|
||||
Start hinting.
|
||||
@@ -601,7 +600,6 @@ Start hinting.
|
||||
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
|
||||
* +*-f*+, +*--first*+: Click the first hinted element without prompting.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
@@ -1285,13 +1283,6 @@ If moving relatively: Offset. If moving absolutely: New position (default: 0). T
|
||||
overrides the index argument, if given.
|
||||
|
||||
|
||||
[[tab-mute]]
|
||||
=== tab-mute
|
||||
Mute/Unmute the current/[count]th tab.
|
||||
|
||||
==== count
|
||||
The tab index to mute or unmute
|
||||
|
||||
[[tab-next]]
|
||||
=== tab-next
|
||||
Switch to the next tab, or switch [count] tabs forward.
|
||||
@@ -1365,16 +1356,12 @@ Show version information.
|
||||
|
||||
[[view-source]]
|
||||
=== view-source
|
||||
Syntax: +:view-source [*--edit*] [*--pygments*]+
|
||||
Syntax: +:view-source [*--edit*]+
|
||||
|
||||
Show the source of the current page in a new tab.
|
||||
|
||||
==== optional arguments
|
||||
* +*-e*+, +*--edit*+: Edit the source in the editor instead of opening a tab.
|
||||
* +*-p*+, +*--pygments*+: Use pygments to generate the view. This is always the case for QtWebKit. For QtWebEngine it may display
|
||||
slightly different source.
|
||||
Some JavaScript processing may be applied.
|
||||
|
||||
|
||||
[[window-only]]
|
||||
=== window-only
|
||||
|
||||
@@ -390,12 +390,6 @@ xresources = read_xresources('*')
|
||||
c.colors.statusbar.normal.bg = xresources['*.background']
|
||||
----
|
||||
|
||||
Pre-built colorschemes
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager].
|
||||
- Two implementations of the https://github.com/arcticicestudio/nord[Nord] colorscheme for qutebrowser exist: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon]
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -109,15 +109,13 @@
|
||||
|<<completion.use_best_match,completion.use_best_match>>|Execute the best-matching command on a partial match.
|
||||
|<<completion.web_history_max_items,completion.web_history_max_items>>|Number of URLs to show in the web history.
|
||||
|<<confirm_quit,confirm_quit>>|Require a confirmation before quitting the application.
|
||||
|<<content.autoplay,content.autoplay>>|Automatically start playing `<video>` elements.
|
||||
|<<content.cache.appcache,content.cache.appcache>>|Enable support for the HTML 5 web application cache feature.
|
||||
|<<content.cache.maximum_pages,content.cache.maximum_pages>>|Maximum number of pages to hold in the global memory page cache.
|
||||
|<<content.cache.size,content.cache.size>>|Size (in bytes) of the HTTP network cache. Null to use the default value.
|
||||
|<<content.canvas_reading,content.canvas_reading>>|Allow websites to read canvas elements.
|
||||
|<<content.cookies.accept,content.cookies.accept>>|Which cookies to accept.
|
||||
|<<content.cookies.store,content.cookies.store>>|Store cookies.
|
||||
|<<content.default_encoding,content.default_encoding>>|Default encoding to use for websites.
|
||||
|<<content.desktop_capture,content.desktop_capture>>|Allow websites to share screen content.
|
||||
|<<content.developer_extras,content.developer_extras>>|Enable extra tools for Web developers.
|
||||
|<<content.dns_prefetch,content.dns_prefetch>>|Try to pre-fetch DNS entries to speed up browsing.
|
||||
|<<content.frame_flattening,content.frame_flattening>>|Expand each subframe to its contents.
|
||||
|<<content.geolocation,content.geolocation>>|Allow websites to request geolocations.
|
||||
@@ -146,17 +144,14 @@
|
||||
|<<content.netrc_file,content.netrc_file>>|Netrc-file for HTTP authentication.
|
||||
|<<content.notifications,content.notifications>>|Allow websites to show notifications.
|
||||
|<<content.pdfjs,content.pdfjs>>|Allow pdf.js to view PDF files in the browser.
|
||||
|<<content.persistent_storage,content.persistent_storage>>|Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
|
||||
|<<content.plugins,content.plugins>>|Enable plugins in Web pages.
|
||||
|<<content.print_element_backgrounds,content.print_element_backgrounds>>|Draw the background color and images also when the page is printed.
|
||||
|<<content.private_browsing,content.private_browsing>>|Open new windows in private browsing mode which does not record visited pages.
|
||||
|<<content.proxy,content.proxy>>|Proxy to use.
|
||||
|<<content.proxy_dns_requests,content.proxy_dns_requests>>|Send DNS requests over the configured proxy.
|
||||
|<<content.register_protocol_handler,content.register_protocol_handler>>|Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
||||
|<<content.ssl_strict,content.ssl_strict>>|Validate SSL handshakes.
|
||||
|<<content.user_stylesheets,content.user_stylesheets>>|List of user stylesheet filenames to use.
|
||||
|<<content.webgl,content.webgl>>|Enable WebGL.
|
||||
|<<content.webrtc_public_interfaces_only,content.webrtc_public_interfaces_only>>|Only expose public interfaces via WebRTC.
|
||||
|<<content.windowed_fullscreen,content.windowed_fullscreen>>|Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
|<<content.xss_auditing,content.xss_auditing>>|Monitor load requests for cross-site scripting attempts.
|
||||
|<<downloads.location.directory,downloads.location.directory>>|Directory to save downloads to.
|
||||
@@ -205,7 +200,6 @@
|
||||
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|
||||
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
||||
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|
||||
|<<input.escape_quits_reporter,input.escape_quits_reporter>>|Allow Escape to quit the crash reporter.
|
||||
|<<input.forward_unbound_keys,input.forward_unbound_keys>>|Which unbound keys to forward to the webview in normal mode.
|
||||
|<<input.insert_mode.auto_enter,input.insert_mode.auto_enter>>|Enter insert mode if an editable element is clicked.
|
||||
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
|
||||
@@ -493,7 +487,6 @@ Default:
|
||||
* +pass:[<Alt-7>]+: +pass:[tab-focus 7]+
|
||||
* +pass:[<Alt-8>]+: +pass:[tab-focus 8]+
|
||||
* +pass:[<Alt-9>]+: +pass:[tab-focus -1]+
|
||||
* +pass:[<Alt-m>]+: +pass:[tab-mute]+
|
||||
* +pass:[<Ctrl-A>]+: +pass:[navigate increment]+
|
||||
* +pass:[<Ctrl-Alt-p>]+: +pass:[print]+
|
||||
* +pass:[<Ctrl-B>]+: +pass:[scroll-page 0 -1]+
|
||||
@@ -507,7 +500,6 @@ Default:
|
||||
* +pass:[<Ctrl-Return>]+: +pass:[follow-selected -t]+
|
||||
* +pass:[<Ctrl-Shift-N>]+: +pass:[open -p]+
|
||||
* +pass:[<Ctrl-Shift-T>]+: +pass:[undo]+
|
||||
* +pass:[<Ctrl-Shift-Tab>]+: +pass:[nop]+
|
||||
* +pass:[<Ctrl-Shift-W>]+: +pass:[close]+
|
||||
* +pass:[<Ctrl-T>]+: +pass:[open -t]+
|
||||
* +pass:[<Ctrl-Tab>]+: +pass:[tab-focus last]+
|
||||
@@ -570,7 +562,6 @@ Default:
|
||||
* +pass:[gd]+: +pass:[download]+
|
||||
* +pass:[gf]+: +pass:[view-source]+
|
||||
* +pass:[gg]+: +pass:[scroll-to-perc 0]+
|
||||
* +pass:[gi]+: +pass:[hint inputs --first]+
|
||||
* +pass:[gl]+: +pass:[tab-move -]+
|
||||
* +pass:[gm]+: +pass:[tab-move]+
|
||||
* +pass:[go]+: +pass:[set-cmd-text :open {url:pretty}]+
|
||||
@@ -1471,19 +1462,6 @@ Default:
|
||||
|
||||
- +pass:[never]+
|
||||
|
||||
[[content.autoplay]]
|
||||
=== content.autoplay
|
||||
Automatically start playing `<video>` elements.
|
||||
Note this option needs a restart with QtWebEngine on Qt < 5.11.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.10 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.cache.appcache]]
|
||||
=== content.cache.appcache
|
||||
Enable support for the HTML 5 web application cache feature.
|
||||
@@ -1518,18 +1496,6 @@ Type: <<types,Int>>
|
||||
|
||||
Default: empty
|
||||
|
||||
[[content.canvas_reading]]
|
||||
=== content.canvas_reading
|
||||
Allow websites to read canvas elements.
|
||||
Note this is needed for some websites to work properly.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.cookies.accept]]
|
||||
=== content.cookies.accept
|
||||
Which cookies to accept.
|
||||
@@ -1540,12 +1506,12 @@ Valid values:
|
||||
|
||||
* +all+: Accept all cookies.
|
||||
* +no-3rdparty+: Accept cookies from the same origin only.
|
||||
* +no-unknown-3rdparty+: Accept cookies from the same origin only, unless a cookie is already set for the domain. On QtWebEngine, this is the same as no-3rdparty.
|
||||
* +no-unknown-3rdparty+: Accept cookies from the same origin only, unless a cookie is already set for the domain.
|
||||
* +never+: Don't accept cookies at all.
|
||||
|
||||
Default: +pass:[no-3rdparty]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.cookies.store]]
|
||||
=== content.cookies.store
|
||||
@@ -1565,22 +1531,16 @@ Type: <<types,String>>
|
||||
|
||||
Default: +pass:[iso-8859-1]+
|
||||
|
||||
[[content.desktop_capture]]
|
||||
=== content.desktop_capture
|
||||
Allow websites to share screen content.
|
||||
On Qt < 5.10, a dialog box is always displayed, even if this is set to "true".
|
||||
[[content.developer_extras]]
|
||||
=== content.developer_extras
|
||||
Enable extra tools for Web developers.
|
||||
This needs to be enabled for `:inspector` to work and also adds an _Inspect_ entry to the context menu. For QtWebEngine, see `--enable-webengine-inspector` in `qutebrowser --help` instead.
|
||||
|
||||
This setting supports URL patterns.
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
Default: +pass:[false]+
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +ask+
|
||||
|
||||
Default: +pass:[ask]+
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.dns_prefetch]]
|
||||
=== content.dns_prefetch
|
||||
@@ -1611,8 +1571,6 @@ This setting is only available with the QtWebKit backend.
|
||||
=== content.geolocation
|
||||
Allow websites to request geolocations.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@@ -1626,9 +1584,6 @@ Default: +pass:[ask]+
|
||||
[[content.headers.accept_language]]
|
||||
=== content.headers.accept_language
|
||||
Value to send in the `Accept-Language` header.
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
@@ -1638,8 +1593,6 @@ Default: +pass:[en-US,en]+
|
||||
=== content.headers.custom
|
||||
Custom headers for qutebrowser HTTP requests.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Dict>>
|
||||
|
||||
Default: empty
|
||||
@@ -1649,8 +1602,6 @@ Default: empty
|
||||
Value to send in the `DNT` header.
|
||||
When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@@ -1675,9 +1626,6 @@ This setting is only available with the QtWebKit backend.
|
||||
[[content.headers.user_agent]]
|
||||
=== content.headers.user_agent
|
||||
User agent to send. Unset to send the default.
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
@@ -1857,8 +1805,6 @@ Default: +pass:[true]+
|
||||
=== content.media_capture
|
||||
Allow websites to record audio/video.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@@ -1884,8 +1830,6 @@ Default: empty
|
||||
=== content.notifications
|
||||
Allow websites to show notifications.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@@ -1909,26 +1853,6 @@ Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.persistent_storage]]
|
||||
=== content.persistent_storage
|
||||
Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +ask+
|
||||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.plugins]]
|
||||
=== content.plugins
|
||||
Enable plugins in Web pages.
|
||||
@@ -1983,32 +1907,10 @@ Default: +pass:[true]+
|
||||
|
||||
This setting is only available with the QtWebKit backend.
|
||||
|
||||
[[content.register_protocol_handler]]
|
||||
=== content.register_protocol_handler
|
||||
Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +ask+
|
||||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.11 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.ssl_strict]]
|
||||
=== content.ssl_strict
|
||||
Validate SSL handshakes.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@@ -2037,19 +1939,6 @@ Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[content.webrtc_public_interfaces_only]]
|
||||
=== content.webrtc_public_interfaces_only
|
||||
Only expose public interfaces via WebRTC.
|
||||
On Qt 5.9, this option requires a restart. On Qt 5.10, this option doesn't work at all because of a Qt bug. On Qt >= 5.11, no restart is required.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.9.2 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.windowed_fullscreen]]
|
||||
=== content.windowed_fullscreen
|
||||
Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
@@ -2496,14 +2385,6 @@ Type: <<types,Int>>
|
||||
|
||||
Default: +pass:[30]+
|
||||
|
||||
[[input.escape_quits_reporter]]
|
||||
=== input.escape_quits_reporter
|
||||
Allow Escape to quit the crash reporter.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[input.forward_unbound_keys]]
|
||||
=== input.forward_unbound_keys
|
||||
Which unbound keys to forward to the webview in normal mode.
|
||||
@@ -2696,19 +2577,12 @@ Default: empty
|
||||
[[qt.force_software_rendering]]
|
||||
=== qt.force_software_rendering
|
||||
Force software rendering for QtWebEngine.
|
||||
This is needed for QtWebEngine to work with Nouveau drivers and can be useful in other scenarios related to graphic issues.
|
||||
This is needed for QtWebEngine to work with Nouveau drivers.
|
||||
This setting requires a restart.
|
||||
|
||||
Type: <<types,String>>
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +software-opengl+: Tell LibGL to use a software implementation of GL (`LIBGL_ALWAYS_SOFTWARE` / `QT_XCB_FORCE_SOFTWARE_OPENGL`)
|
||||
* +qt-quick+: Tell Qt Quick to use a software renderer instead of OpenGL. (`QT_QUICK_BACKEND=software`)
|
||||
* +chromium+: Tell Chromium to disable GPU support and use Skia software rendering instead. (`--disable-gpu`)
|
||||
* +none+: Don't force software rendering.
|
||||
|
||||
Default: +pass:[none]+
|
||||
Default: +pass:[false]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
@@ -3170,12 +3044,11 @@ The following placeholders are defined:
|
||||
* `{private}`: Indicates when private mode is enabled.
|
||||
* `{current_url}`: URL of the current web page.
|
||||
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||
* `{audio}`: Indicator for audio/mute status.
|
||||
|
||||
|
||||
Type: <<types,FormatString>>
|
||||
|
||||
Default: +pass:[{audio}{index}: {title}]+
|
||||
Default: +pass:[{index}: {title}]+
|
||||
|
||||
[[tabs.title.format_pinned]]
|
||||
=== tabs.title.format_pinned
|
||||
|
||||
@@ -57,8 +57,7 @@ You'll need to download three packages:
|
||||
(or both) depending on the backend you want to use. QtWebEngine is the
|
||||
default/recommended choice.
|
||||
|
||||
After downloading, install the packages (make sure to install all the
|
||||
downloaded qutebrowser deb files in one apt command):
|
||||
After downloading, install the packages:
|
||||
|
||||
----
|
||||
# apt install ./python3-pypeg2_*_all.deb
|
||||
@@ -382,8 +381,8 @@ $ git clone https://github.com/qutebrowser/qutebrowser.git
|
||||
$ cd qutebrowser
|
||||
----
|
||||
|
||||
Installing dependencies (including Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Installing depdendencies (including Qt)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Then run tox inside the qutebrowser repository to set up a
|
||||
https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
@@ -392,10 +391,6 @@ https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
$ tox -e mkvenv-pypi
|
||||
----
|
||||
|
||||
If your system comes with Python 3.5.3 or older (such as Ubuntu 16.04 LTS), use
|
||||
`tox -e mkvenv-pypi-old` instead. This installs an older Qt version (5.10) due
|
||||
to bugs in newer versions.
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder.
|
||||
|
||||
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few
|
||||
|
||||
@@ -38,7 +38,7 @@ show it.
|
||||
*-h*, *--help*::
|
||||
show this help message and exit
|
||||
|
||||
*-B* 'BASEDIR', *--basedir* 'BASEDIR'::
|
||||
*--basedir* 'BASEDIR'::
|
||||
Base directory for all storage.
|
||||
|
||||
*-V*, *--version*::
|
||||
@@ -60,7 +60,7 @@ show it.
|
||||
Which backend to use.
|
||||
|
||||
*--enable-webengine-inspector*::
|
||||
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details. This is not needed anymore since Qt 5.11 where the inspector is always enabled and secure.
|
||||
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
|
||||
|
||||
=== debug arguments
|
||||
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
|
||||
@@ -72,7 +72,7 @@ show it.
|
||||
*--loglines* 'LOGLINES'::
|
||||
How many lines of the debug log to keep in RAM (-1: unlimited).
|
||||
|
||||
*-d*, *--debug*::
|
||||
*--debug*::
|
||||
Turn on debugging options.
|
||||
|
||||
*--json-logging*::
|
||||
@@ -87,7 +87,7 @@ show it.
|
||||
*--nowindow*::
|
||||
Don't show the main window.
|
||||
|
||||
*-T*, *--temp-basedir*::
|
||||
*--temp-basedir*::
|
||||
Use a temporary basedir.
|
||||
|
||||
*--no-err-windows*::
|
||||
@@ -99,7 +99,7 @@ show it.
|
||||
*--qt-flag* 'QT_FLAG'::
|
||||
Pass an argument to Qt as flag.
|
||||
|
||||
*-D* 'DEBUG_FLAGS', *--debug-flag* 'DEBUG_FLAGS'::
|
||||
*--debug-flag* 'DEBUG_FLAGS'::
|
||||
Pass name of debugging feature to be turned on.
|
||||
// QUTE_OPTIONS_END
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ doc/qutebrowser.1.html:
|
||||
|
||||
install: doc/qutebrowser.1.html
|
||||
$(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS)
|
||||
install -Dm644 misc/qutebrowser.appdata.xml \
|
||||
"$(DESTDIR)$(PREFIX)/share/metainfo/qutebrowser.appdata.xml"
|
||||
install -Dm644 doc/qutebrowser.1 \
|
||||
"$(DESTDIR)$(PREFIX)/share/man/man1/qutebrowser.1"
|
||||
install -Dm644 misc/qutebrowser.desktop \
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
<description>
|
||||
<p>
|
||||
qutebrowser is a keyboard-focused browser with a minimal GUI.
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl,
|
||||
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl,
|
||||
and is based on Python and PyQt5.
|
||||
</p>
|
||||
</p>
|
||||
</description>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
@@ -40,9 +40,4 @@
|
||||
<url type="help">https://qutebrowser.org/doc/help/</url>
|
||||
<url type="bugtracker">https://github.com/qutebrowser/qutebrowser/issues/</url>
|
||||
<url type="donation">https://github.com/qutebrowser/qutebrowser#donating</url>
|
||||
<releases>
|
||||
<release version="1.3.0" date="2018-05-04"/>
|
||||
<release version="1.2.1" date="2018-03-14"/>
|
||||
<release version="1.2.0" date="2018-03-09"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=qutebrowser
|
||||
GenericName=Web Browser
|
||||
Comment=A keyboard-driven, vim-like browser based on PyQt5
|
||||
Icon=qutebrowser
|
||||
Type=Application
|
||||
Categories=Network;WebBrowser;
|
||||
|
||||
@@ -40,8 +40,6 @@ Section "Install"
|
||||
; Uninstall old versions
|
||||
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{633F41F9-FE9B-42D1-9CC4-718CBD01EE11}'
|
||||
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{9331D947-AC86-4542-A755-A833429C6E69}'
|
||||
RMDir /r "$INSTDIR\*.*"
|
||||
CreateDirectory "$INSTDIR"
|
||||
|
||||
SetOutPath "$INSTDIR"
|
||||
|
||||
|
||||
@@ -59,8 +59,7 @@ exe = EXE(pyz,
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=False,
|
||||
console=False,
|
||||
version='misc/file_version_info.txt')
|
||||
console=False )
|
||||
coll = COLLECT(exe,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
|
||||
@@ -4,6 +4,6 @@ certifi==2018.4.16
|
||||
chardet==3.0.4
|
||||
codecov==2.0.15
|
||||
coverage==4.5.1
|
||||
idna==2.7
|
||||
requests==2.19.1
|
||||
idna==2.6
|
||||
requests==2.18.4
|
||||
urllib3==1.22
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==18.1.0
|
||||
attrs==17.4.0
|
||||
flake8==3.5.0
|
||||
flake8-bugbear==18.2.0
|
||||
flake8-builtins==1.4.1 # rq.filter: != 1.4.0
|
||||
flake8-builtins==1.3.1
|
||||
flake8-comprehensions==1.4.1
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==3.1.0
|
||||
@@ -18,10 +18,10 @@ flake8-tidy-imports==1.1.0
|
||||
flake8-tuple==0.2.13
|
||||
mccabe==0.6.1
|
||||
pathmatch==0.2.1
|
||||
pep8-naming==0.7.0
|
||||
pep8-naming==0.5.0
|
||||
pycodestyle==2.3.1 # rq.filter: < 2.4.0
|
||||
pydocstyle==2.1.1
|
||||
pyflakes==2.0.0
|
||||
pyflakes==1.6.0
|
||||
six==1.11.0
|
||||
snowballstemmer==1.2.1
|
||||
typing==3.6.4
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
flake8
|
||||
flake8-bugbear
|
||||
flake8-builtins!=1.4.0
|
||||
flake8-builtins
|
||||
flake8-comprehensions
|
||||
flake8-copyright
|
||||
flake8-debugger
|
||||
@@ -18,6 +18,3 @@ pyflakes
|
||||
|
||||
# https://github.com/PyCQA/pycodestyle/issues/741
|
||||
#@ filter: pycodestyle < 2.4.0
|
||||
|
||||
# https://github.com/gforcada/flake8-builtins/issues/36
|
||||
#@ filter: flake8-builtins != 1.4.0
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
appdirs==1.4.3
|
||||
packaging==17.1
|
||||
pyparsing==2.2.0
|
||||
setuptools==39.2.0
|
||||
setuptools==39.1.0
|
||||
six==1.11.0
|
||||
wheel==0.31.1
|
||||
wheel==0.31.0
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
certifi==2018.4.16
|
||||
chardet==3.0.4
|
||||
github3.py==1.1.0
|
||||
idna==2.7
|
||||
idna==2.6
|
||||
isort==4.3.4
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||
python-dateutil==2.7.3
|
||||
python-dateutil==2.7.2
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.19.1
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
uritemplate==3.0.0
|
||||
urllib3==1.22
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==1.6.5
|
||||
astroid==1.6.3
|
||||
certifi==2018.4.16
|
||||
chardet==3.0.4
|
||||
github3.py==1.1.0
|
||||
idna==2.7
|
||||
idna==2.6
|
||||
isort==4.3.4
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.9.2
|
||||
python-dateutil==2.7.3
|
||||
pylint==1.8.4
|
||||
python-dateutil==2.7.2
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.19.1
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
uritemplate==3.0.0
|
||||
urllib3==1.22
|
||||
|
||||
4
misc/requirements/requirements-pyqt-old.txt
Normal file
4
misc/requirements/requirements-pyqt-old.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.10 # rq.filter: != 5.10.1
|
||||
sip==4.19.8
|
||||
2
misc/requirements/requirements-pyqt-old.txt-raw
Normal file
2
misc/requirements/requirements-pyqt-old.txt-raw
Normal file
@@ -0,0 +1,2 @@
|
||||
PyQt5==5.10.0
|
||||
#@ filter: PyQt5 != 5.10.1
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.11.2
|
||||
PyQt5-sip==4.19.11
|
||||
PyQt5==5.10.1
|
||||
sip==4.19.8
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.14
|
||||
pyroma==2.3.1
|
||||
pyroma==2.3
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
Jinja2
|
||||
Pygments
|
||||
pyPEG2
|
||||
PyYAML!=4.1
|
||||
PyYAML
|
||||
colorama
|
||||
cssutils
|
||||
attrs
|
||||
|
||||
#@ filter: PyYAML != 4.1
|
||||
|
||||
@@ -35,4 +35,8 @@ git+https://github.com/pallets/markupsafe.git
|
||||
hg+http://bitbucket.org/birkenfeld/pygments-main
|
||||
hg+https://bitbucket.org/fdik/pypeg
|
||||
git+https://github.com/python-attrs/attrs.git
|
||||
git+https://github.com/yaml/pyyaml.git
|
||||
|
||||
# Fails to build:
|
||||
# gcc: error: ext/_yaml.c: No such file or directory
|
||||
# hg+https://bitbucket.org/xi/pyyaml
|
||||
PyYAML==3.12
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==18.1.0
|
||||
attrs==17.4.0
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==6.3.2
|
||||
cheroot==6.2.4
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.5.1
|
||||
EasyProcess==0.2.3
|
||||
fields==5.0.0
|
||||
Flask==1.0.2
|
||||
Flask==1.0.1
|
||||
glob2==0.6
|
||||
hunter==2.0.2
|
||||
hypothesis==3.65.0
|
||||
hypothesis==3.56.5
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.10
|
||||
Mako==1.0.7
|
||||
# MarkupSafe==1.0
|
||||
more-itertools==4.2.0
|
||||
parse==1.8.4
|
||||
more-itertools==4.1.0
|
||||
parse==1.8.2
|
||||
parse-type==0.4.2
|
||||
pluggy==0.6.0
|
||||
py==1.5.4
|
||||
py==1.5.3
|
||||
py-cpuinfo==4.0.0
|
||||
pytest==3.6.2
|
||||
pytest==3.5.1
|
||||
pytest-bdd==2.21.0
|
||||
pytest-benchmark==3.1.1
|
||||
pytest-cov==2.5.1
|
||||
pytest-faulthandler==1.5.0
|
||||
pytest-instafail==0.4.0
|
||||
pytest-mock==1.10.0
|
||||
pytest-qt==2.4.1
|
||||
pytest-instafail==0.3.0
|
||||
pytest-mock==1.9.0
|
||||
pytest-qt==2.3.1
|
||||
pytest-repeat==0.4.1
|
||||
pytest-rerunfailures==4.1
|
||||
pytest-rerunfailures==4.0
|
||||
pytest-travis-fold==1.3.0
|
||||
pytest-xvfb==1.1.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.11.0
|
||||
vulture==0.27
|
||||
vulture==0.26
|
||||
Werkzeug==0.14.1
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pluggy==0.6.0
|
||||
py==1.5.4
|
||||
py==1.5.3
|
||||
six==1.11.0
|
||||
tox==3.0.0
|
||||
virtualenv==16.0.0
|
||||
virtualenv==15.2.0
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==0.27
|
||||
vulture==0.26
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2017 Chris Braun (cryzed) <cryzed@googlemail.com>
|
||||
# Adapted for LastPass by Wayne Cheng (welps) <waynethecheng@gmail.com>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published bjy
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
Insert login information using lastpass CLI and a dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...).
|
||||
A short demonstration can be seen here: https://i.imgur.com/zA61NrF.gifv.
|
||||
"""
|
||||
|
||||
USAGE = """The domain of the site has to be in the name of the LastPass entry, for example: "github.com/cryzed" or
|
||||
"websites/github.com". The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
|
||||
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
|
||||
|
||||
You must log into LastPass CLI using `lpass login <email>` prior to use of this script. The LastPass CLI agent only holds your master password for an hour by default. If you wish to change this, please see `man lpass`.
|
||||
|
||||
To use in qutebrowser, run: `spawn --userscript qute-lastpass`
|
||||
"""
|
||||
|
||||
EPILOG = """Dependencies: tldextract (Python 3 module), LastPass CLI (1.3 or newer)
|
||||
|
||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
|
||||
you decide to submit a crash report!"""
|
||||
|
||||
import argparse
|
||||
import enum
|
||||
import fnmatch
|
||||
import functools
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import json
|
||||
import tldextract
|
||||
|
||||
argument_parser = argparse.ArgumentParser(
|
||||
description=__doc__, usage=USAGE, epilog=EPILOG)
|
||||
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
|
||||
argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu',
|
||||
help='Invocation used to execute a dmenu-provider')
|
||||
argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false',
|
||||
help="Don't automatically enter insert mode")
|
||||
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
|
||||
help='Encoding used to communicate with subprocesses')
|
||||
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
||||
help='Merge pass candidates for fully-qualified and registered domain name')
|
||||
group = argument_parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--username-only', '-e',
|
||||
action='store_true', help='Only insert username')
|
||||
group.add_argument('--password-only', '-w',
|
||||
action='store_true', help='Only insert password')
|
||||
|
||||
stderr = functools.partial(print, file=sys.stderr)
|
||||
|
||||
class ExitCodes(enum.IntEnum):
|
||||
SUCCESS = 0
|
||||
FAILURE = 1
|
||||
# 1 is automatically used if Python throws an exception
|
||||
NO_PASS_CANDIDATES = 2
|
||||
COULD_NOT_MATCH_USERNAME = 3
|
||||
COULD_NOT_MATCH_PASSWORD = 4
|
||||
|
||||
def qute_command(command):
|
||||
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
|
||||
fifo.write(command + '\n')
|
||||
fifo.flush()
|
||||
|
||||
def pass_(domain, encoding):
|
||||
args = ['lpass', 'show', '-x', '-j', '-G', '.*{:s}.*'.format(domain)]
|
||||
process = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
err = process.stderr.decode(encoding).strip()
|
||||
if err:
|
||||
msg = "LastPass CLI returned for {:s} - {:s}".format(domain, err)
|
||||
stderr(msg)
|
||||
return '[]'
|
||||
|
||||
out = process.stdout.decode(encoding).strip()
|
||||
|
||||
return out
|
||||
|
||||
def dmenu(items, invocation, encoding):
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(
|
||||
items).encode(encoding), stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
|
||||
|
||||
def fake_key_raw(text):
|
||||
for character in text:
|
||||
# Escape all characters by default, space requires special handling
|
||||
sequence = '" "' if character == ' ' else '\{}'.format(character)
|
||||
qute_command('fake-key {}'.format(sequence))
|
||||
|
||||
|
||||
def main(arguments):
|
||||
if not arguments.url:
|
||||
argument_parser.print_help()
|
||||
return ExitCodes.FAILURE
|
||||
|
||||
extract_result = tldextract.extract(arguments.url)
|
||||
|
||||
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
|
||||
# the registered domain name and finally: the IPv4 address if that's what
|
||||
# the URL represents
|
||||
candidates = []
|
||||
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.subdomain + extract_result.domain, extract_result.domain, extract_result.ipv4]):
|
||||
target_candidates = json.loads(pass_(target, arguments.io_encoding))
|
||||
if not target_candidates:
|
||||
continue
|
||||
|
||||
candidates = candidates + target_candidates
|
||||
if not arguments.merge_candidates:
|
||||
break
|
||||
else:
|
||||
if not candidates:
|
||||
stderr('No pass candidates for URL {!r} found!'.format(
|
||||
arguments.url))
|
||||
return ExitCodes.NO_PASS_CANDIDATES
|
||||
|
||||
if len(candidates) == 1:
|
||||
selection = candidates.pop()
|
||||
else:
|
||||
choices = ["{:s} | {:s} | {:s} | {:s}".format(c["id"], c["name"], c["url"], c["username"]) for c in candidates]
|
||||
choice = dmenu(choices, arguments.dmenu_invocation, arguments.io_encoding)
|
||||
choiceId = choice.split("|")[0].strip()
|
||||
selection = next((c for (i, c) in enumerate(candidates) if c["id"] == choiceId), None)
|
||||
|
||||
# Nothing was selected, simply return
|
||||
if not selection:
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
username = selection["username"]
|
||||
password = selection["password"]
|
||||
|
||||
if arguments.username_only:
|
||||
fake_key_raw(username)
|
||||
elif arguments.password_only:
|
||||
fake_key_raw(password)
|
||||
else:
|
||||
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
|
||||
# back into insert-mode, so the form can be directly submitted by
|
||||
# hitting enter afterwards
|
||||
fake_key_raw(username)
|
||||
qute_command('fake-key <Tab>')
|
||||
fake_key_raw(password)
|
||||
|
||||
if arguments.insert_mode:
|
||||
qute_command('enter-mode insert')
|
||||
|
||||
return ExitCodes.SUCCESS
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
arguments = argument_parser.parse_args()
|
||||
sys.exit(main(arguments))
|
||||
@@ -49,7 +49,7 @@ msg() {
|
||||
|
||||
MPV_COMMAND=${MPV_COMMAND:-mpv}
|
||||
# Warning: spaces in single flags are not supported
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl}
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl --ytdl-raw-options=yes-playlist=}
|
||||
IFS=" " read -r -a video_command <<< "$MPV_COMMAND $MPV_FLAGS"
|
||||
|
||||
js() {
|
||||
|
||||
@@ -30,7 +30,6 @@ markers =
|
||||
qtbug60673: Tests which are broken if the conversion from orange selection to real selection is flaky
|
||||
fake_os: Fake utils.is_* to a fake operating system
|
||||
unicode_locale: Tests which need an unicode locale to work
|
||||
qtwebkit6021_skip: Tests which would fail on WebKit version 602.1
|
||||
qt_log_level_fail = WARNING
|
||||
qt_log_ignore =
|
||||
^SpellCheck: .*
|
||||
@@ -63,8 +62,4 @@ qt_log_ignore =
|
||||
^inotify_add_watch\(".*"\) failed: "No space left on device"
|
||||
^QSettings::value: Empty key passed
|
||||
^Icon theme ".*" not found
|
||||
^Error receiving trust for a CA certificate
|
||||
xfail_strict = true
|
||||
filterwarnings =
|
||||
# This happens in many qutebrowser dependencies...
|
||||
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning
|
||||
|
||||
@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 4, 0)
|
||||
__version_info__ = (1, 3, 3)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
||||
@@ -22,22 +22,18 @@
|
||||
import enum
|
||||
import itertools
|
||||
|
||||
import sip
|
||||
import attr
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QWidget, QApplication
|
||||
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
|
||||
urlutils, message)
|
||||
from qutebrowser.misc import miscwidgets, objects
|
||||
from qutebrowser.browser import mouse, hints
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
tab_id_gen = itertools.count(0)
|
||||
@@ -99,8 +95,6 @@ class TabData:
|
||||
keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
|
||||
load.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
viewing_source: Set if we're currently showing a source view.
|
||||
Only used when sources are shown via pygments.
|
||||
open_target: Where to open the next link.
|
||||
Only used for QtWebKit.
|
||||
override_target: Override for open_target for fake clicks (like hints).
|
||||
@@ -112,7 +106,6 @@ class TabData:
|
||||
"""
|
||||
|
||||
keep_icon = attr.ib(False)
|
||||
viewing_source = attr.ib(False)
|
||||
inspector = attr.ib(None)
|
||||
open_target = attr.ib(usertypes.ClickTarget.normal)
|
||||
override_target = attr.ib(None)
|
||||
@@ -157,31 +150,10 @@ class AbstractAction:
|
||||
raise WebTabError("{} is not a valid web action!".format(name))
|
||||
self._widget.triggerPageAction(member)
|
||||
|
||||
def show_source(self,
|
||||
pygments=False): # pylint: disable=redefined-outer-name
|
||||
def show_source(self):
|
||||
"""Show the source of the current page in a new tab."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _show_source_pygments(self):
|
||||
|
||||
def show_source_cb(source):
|
||||
"""Show source as soon as it's ready."""
|
||||
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
|
||||
# pylint: disable=no-member
|
||||
lexer = pygments.lexers.HtmlLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table')
|
||||
# pylint: enable=no-member
|
||||
highlighted = pygments.highlight(source, lexer, formatter)
|
||||
|
||||
tb = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._tab.win_id)
|
||||
new_tab = tb.tabopen(background=False, related=True)
|
||||
new_tab.set_html(highlighted, self._tab.url())
|
||||
new_tab.data.viewing_source = True
|
||||
|
||||
self._tab.dump_async(show_source_cb)
|
||||
|
||||
|
||||
class AbstractPrinting:
|
||||
|
||||
@@ -442,13 +414,6 @@ class AbstractCaret(QObject):
|
||||
def selection(self, callback):
|
||||
raise NotImplementedError
|
||||
|
||||
def _follow_enter(self, tab):
|
||||
"""Follow a link by faking an enter press."""
|
||||
if tab:
|
||||
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
|
||||
else:
|
||||
self._tab.key_press(Qt.Key_Enter)
|
||||
|
||||
def follow_selected(self, *, tab=False):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -634,33 +599,6 @@ class AbstractElements:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractAudio(QObject):
|
||||
|
||||
"""Handling of audio/muting for this tab."""
|
||||
|
||||
muted_changed = pyqtSignal(bool)
|
||||
recently_audible_changed = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._widget = None
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
"""Set this tab as muted or not."""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_muted(self):
|
||||
"""Whether this tab is muted."""
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle_muted(self):
|
||||
self.set_muted(not self.is_muted())
|
||||
|
||||
def is_recently_audible(self):
|
||||
"""Whether this tab has had audio playing recently."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractTab(QWidget):
|
||||
|
||||
"""A wrapper over the given widget to hide its API and expose another one.
|
||||
@@ -755,7 +693,6 @@ class AbstractTab(QWidget):
|
||||
self.printing._widget = widget
|
||||
self.action._widget = widget
|
||||
self.elements._widget = widget
|
||||
self.audio._widget = widget
|
||||
self.settings._settings = widget.settings()
|
||||
|
||||
self._install_event_filter()
|
||||
@@ -816,7 +753,6 @@ class AbstractTab(QWidget):
|
||||
def _on_load_started(self):
|
||||
self._progress = 0
|
||||
self._has_ssl_errors = False
|
||||
self.data.viewing_source = False
|
||||
self._set_load_status(usertypes.LoadStatus.loading)
|
||||
self.load_started.emit()
|
||||
|
||||
@@ -893,6 +829,10 @@ class AbstractTab(QWidget):
|
||||
self._progress = perc
|
||||
self.load_progress.emit(perc)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
def url(self, requested=False):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@@ -566,6 +566,12 @@ class CommandDispatcher:
|
||||
tabbed_browser.tabopen(self._current_url())
|
||||
self._tabbed_browser.close_tab(self._current_widget(), add_undo=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
deprecated='Use :tab-give instead!')
|
||||
def tab_detach(self):
|
||||
"""Deprecated way to detach a tab."""
|
||||
self.tab_give()
|
||||
|
||||
def _back_forward(self, tab, bg, window, count, forward):
|
||||
"""Helper function for :back/:forward."""
|
||||
history = self._current_widget().history
|
||||
@@ -1455,7 +1461,6 @@ class CommandDispatcher:
|
||||
if tab.data.inspector is None:
|
||||
tab.data.inspector = inspector.create()
|
||||
tab.data.inspector.inspect(page)
|
||||
tab.data.inspector.show()
|
||||
else:
|
||||
tab.data.inspector.toggle(page)
|
||||
except inspector.WebInspectorError as e:
|
||||
@@ -1516,15 +1521,11 @@ class CommandDispatcher:
|
||||
)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def view_source(self, edit=False, pygments=False):
|
||||
def view_source(self, edit=False):
|
||||
"""Show the source of the current page in a new tab.
|
||||
|
||||
Args:
|
||||
edit: Edit the source in the editor instead of opening a tab.
|
||||
pygments: Use pygments to generate the view. This is always
|
||||
the case for QtWebKit. For QtWebEngine it may display
|
||||
slightly different source.
|
||||
Some JavaScript processing may be applied.
|
||||
"""
|
||||
tab = self._current_widget()
|
||||
try:
|
||||
@@ -1532,15 +1533,14 @@ class CommandDispatcher:
|
||||
except cmdexc.CommandError as e:
|
||||
message.error(str(e))
|
||||
return
|
||||
|
||||
if current_url.scheme() == 'view-source' or tab.data.viewing_source:
|
||||
if current_url.scheme() == 'view-source':
|
||||
raise cmdexc.CommandError("Already viewing source!")
|
||||
|
||||
if edit:
|
||||
ed = editor.ExternalEditor(self._tabbed_browser)
|
||||
tab.dump_async(ed.edit)
|
||||
else:
|
||||
tab.action.show_source(pygments)
|
||||
tab.action.show_source()
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
debug=True)
|
||||
@@ -1674,7 +1674,7 @@ class CommandDispatcher:
|
||||
"""
|
||||
try:
|
||||
elem.set_value(text)
|
||||
except webelem.OrphanedError:
|
||||
except webelem.OrphanedError as e:
|
||||
message.error('Edited element vanished')
|
||||
ed.backup()
|
||||
except webelem.Error as e:
|
||||
@@ -2230,20 +2230,3 @@ class CommandDispatcher:
|
||||
|
||||
window = self._tabbed_browser.widget.window()
|
||||
window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
name='tab-mute')
|
||||
@cmdutils.argument('count', count=True)
|
||||
def tab_mute(self, count=None):
|
||||
"""Mute/Unmute the current/[count]th tab.
|
||||
|
||||
Args:
|
||||
count: The tab index to mute or unmute, or None
|
||||
"""
|
||||
tab = self._cntwidget(count)
|
||||
if tab is None:
|
||||
return
|
||||
try:
|
||||
tab.audio.toggle_muted()
|
||||
except browsertab.WebTabError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
@@ -29,6 +29,7 @@ import pathlib
|
||||
import tempfile
|
||||
import enum
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
QTimer, QAbstractListModel, QUrl)
|
||||
|
||||
@@ -36,7 +37,6 @@ from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
|
||||
qtutils)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
|
||||
import functools
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
|
||||
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
|
||||
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import qtutils, utils, objreg
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
def update_geometry(obj):
|
||||
|
||||
@@ -31,10 +31,9 @@ import attr
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||
|
||||
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
|
||||
javascript, urlmatch, version, usertypes)
|
||||
javascript, urlmatch)
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.misc import objects
|
||||
|
||||
|
||||
def _scripts_dir():
|
||||
@@ -58,7 +57,6 @@ class GreasemonkeyScript:
|
||||
self.run_at = None
|
||||
self.script_meta = None
|
||||
self.runs_on_sub_frames = True
|
||||
self.jsworld = "main"
|
||||
for name, value in properties:
|
||||
if name == 'name':
|
||||
self.name = value
|
||||
@@ -78,8 +76,6 @@ class GreasemonkeyScript:
|
||||
self.runs_on_sub_frames = False
|
||||
elif name == 'require':
|
||||
self.requires.append(value)
|
||||
elif name == 'qute-js-world':
|
||||
self.jsworld = value
|
||||
|
||||
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||
@@ -112,18 +108,13 @@ class GreasemonkeyScript:
|
||||
browser's debugger/inspector will not match up to the line
|
||||
numbers in the source script directly.
|
||||
"""
|
||||
# Don't use Proxy on this webkit version, the support isn't there.
|
||||
use_proxy = not (
|
||||
objects.backend == usertypes.Backend.QtWebKit and
|
||||
version.qWebKitVersion() == '602.1')
|
||||
template = jinja.js_environment.get_template('greasemonkey_wrapper.js')
|
||||
return template.render(
|
||||
scriptName=javascript.string_escape(
|
||||
"/".join([self.namespace or '', self.name])),
|
||||
scriptInfo=self._meta_json(),
|
||||
scriptMeta=javascript.string_escape(self.script_meta),
|
||||
scriptSource=self._code,
|
||||
use_proxy=use_proxy)
|
||||
scriptSource=self._code)
|
||||
|
||||
def _meta_json(self):
|
||||
return json.dumps({
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
import collections
|
||||
import functools
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import html
|
||||
import enum
|
||||
@@ -155,7 +154,6 @@ class HintContext:
|
||||
to_follow: The link to follow when enter is pressed.
|
||||
args: Custom arguments for userscript/spawn
|
||||
rapid: Whether to do rapid hinting.
|
||||
first_run: Whether the action is run for the 1st time in rapid hinting.
|
||||
add_history: Whether to add yanked or spawned link to the history.
|
||||
filterstr: Used to save the filter string for restoring in rapid mode.
|
||||
tab: The WebTab object we started hinting in.
|
||||
@@ -168,14 +166,12 @@ class HintContext:
|
||||
baseurl = attr.ib(None)
|
||||
to_follow = attr.ib(None)
|
||||
rapid = attr.ib(False)
|
||||
first_run = attr.ib(True)
|
||||
add_history = attr.ib(False)
|
||||
filterstr = attr.ib(None)
|
||||
args = attr.ib(attr.Factory(list))
|
||||
tab = attr.ib(None)
|
||||
group = attr.ib(None)
|
||||
hint_mode = attr.ib(None)
|
||||
first = attr.ib(False)
|
||||
|
||||
def get_args(self, urlstr):
|
||||
"""Get the arguments, with {hint-url} replaced by the given URL."""
|
||||
@@ -244,18 +240,7 @@ class HintActions:
|
||||
if url.scheme() == 'mailto':
|
||||
flags |= QUrl.RemoveScheme
|
||||
urlstr = url.toString(flags)
|
||||
|
||||
new_content = urlstr
|
||||
|
||||
# only second and consecutive yanks are to append to the clipboard
|
||||
if context.rapid and not context.first_run:
|
||||
try:
|
||||
old_content = utils.get_clipboard(selection=sel)
|
||||
except utils.ClipboardEmptyError:
|
||||
pass
|
||||
else:
|
||||
new_content = os.linesep.join([old_content, new_content])
|
||||
utils.set_clipboard(new_content, selection=sel)
|
||||
utils.set_clipboard(urlstr, selection=sel)
|
||||
|
||||
msg = "Yanked URL to {}: {}".format(
|
||||
"primary selection" if sel else "clipboard",
|
||||
@@ -627,9 +612,6 @@ class HintManager(QObject):
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
|
||||
if self._context.first:
|
||||
self._fire(strings[0])
|
||||
return
|
||||
# to make auto_follow == 'always' work
|
||||
self._handle_auto_follow()
|
||||
|
||||
@@ -638,8 +620,7 @@ class HintManager(QObject):
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def start(self, # pylint: disable=keyword-arg-before-vararg
|
||||
group=webelem.Group.all, target=Target.normal,
|
||||
*args, win_id, mode=None, add_history=False, rapid=False,
|
||||
first=False):
|
||||
*args, win_id, mode=None, add_history=False, rapid=False):
|
||||
"""Start hinting.
|
||||
|
||||
Args:
|
||||
@@ -650,7 +631,6 @@ class HintManager(QObject):
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
add_history: Whether to add the spawned or yanked link to the
|
||||
browsing history.
|
||||
first: Click the first hinted element without prompting.
|
||||
group: The element types to hint.
|
||||
|
||||
- `all`: All clickable elements.
|
||||
@@ -714,8 +694,7 @@ class HintManager(QObject):
|
||||
if rapid:
|
||||
if target in [Target.tab_bg, Target.window, Target.run,
|
||||
Target.hover, Target.userscript, Target.spawn,
|
||||
Target.download, Target.normal, Target.current,
|
||||
Target.yank, Target.yank_primary]:
|
||||
Target.download, Target.normal, Target.current]:
|
||||
pass
|
||||
elif target == Target.tab and config.val.tabs.background:
|
||||
pass
|
||||
@@ -734,7 +713,6 @@ class HintManager(QObject):
|
||||
self._context.rapid = rapid
|
||||
self._context.hint_mode = mode
|
||||
self._context.add_history = add_history
|
||||
self._context.first = first
|
||||
try:
|
||||
self._context.baseurl = tabbed_browser.current_url()
|
||||
except qtutils.QtValueError:
|
||||
@@ -929,9 +907,6 @@ class HintManager(QObject):
|
||||
except HintingError as e:
|
||||
message.error(str(e))
|
||||
|
||||
if self._context is not None:
|
||||
self._context.first_run = False
|
||||
|
||||
@cmdutils.register(instance='hintmanager', scope='tab',
|
||||
modes=[usertypes.KeyMode.hint])
|
||||
def follow_hint(self, select=False, keystring=None):
|
||||
|
||||
@@ -23,7 +23,7 @@ import os
|
||||
import time
|
||||
import contextlib
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
|
||||
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
|
||||
@@ -52,11 +52,6 @@ class WebHistory(sql.SqlTable):
|
||||
|
||||
"""The global history of visited pages."""
|
||||
|
||||
# All web history cleared
|
||||
history_cleared = pyqtSignal()
|
||||
# one url cleared
|
||||
url_cleared = pyqtSignal(QUrl)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
|
||||
constraints={'url': 'NOT NULL',
|
||||
@@ -162,7 +157,6 @@ class WebHistory(sql.SqlTable):
|
||||
with self._handle_sql_errors():
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
self.history_cleared.emit()
|
||||
|
||||
def delete_url(self, url):
|
||||
"""Remove all history entries with the given url.
|
||||
@@ -174,7 +168,6 @@ class WebHistory(sql.SqlTable):
|
||||
qtutils.ensure_valid(qurl)
|
||||
self.delete('url', self._format_url(qurl))
|
||||
self.completion.delete('url', self._format_completion_url(qurl))
|
||||
self.url_cleared.emit(qurl)
|
||||
|
||||
@pyqtSlot(QUrl, QUrl, str)
|
||||
def add_from_tab(self, url, requested_url, title):
|
||||
|
||||
@@ -87,8 +87,6 @@ class AbstractWebInspector(QWidget):
|
||||
data = bytes(self.saveGeometry())
|
||||
geom = base64.b64encode(data).decode('ASCII')
|
||||
configfiles.state['geometry']['inspector'] = geom
|
||||
|
||||
self.inspect(None)
|
||||
super().closeEvent(e)
|
||||
|
||||
def inspect(self, page):
|
||||
@@ -101,4 +99,3 @@ class AbstractWebInspector(QWidget):
|
||||
self.hide()
|
||||
else:
|
||||
self.inspect(page)
|
||||
self.show()
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes, qtutils, objreg
|
||||
from qutebrowser.utils import message, log, usertypes, qtutils
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
@@ -40,12 +40,11 @@ class ChildEventFilter(QObject):
|
||||
_widget: The widget expected to send out childEvents.
|
||||
"""
|
||||
|
||||
def __init__(self, eventfilter, widget, win_id, parent=None):
|
||||
def __init__(self, eventfilter, widget, parent=None):
|
||||
super().__init__(parent)
|
||||
self._filter = eventfilter
|
||||
assert widget is not None
|
||||
self._widget = widget
|
||||
self._win_id = win_id
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Act on ChildAdded events."""
|
||||
@@ -58,22 +57,7 @@ class ChildEventFilter(QObject):
|
||||
|
||||
if qtutils.version_check('5.11', compiled=False, exact=True):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
|
||||
pass_modes = [usertypes.KeyMode.command,
|
||||
usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno]
|
||||
if modeman.instance(self._win_id).mode not in pass_modes:
|
||||
tabbed_browser = objreg.get('tabbed-browser',
|
||||
scope='window',
|
||||
window=self._win_id)
|
||||
current_index = tabbed_browser.widget.currentIndex()
|
||||
try:
|
||||
widget_index = tabbed_browser.widget.indexOf(
|
||||
self._widget.parent())
|
||||
except RuntimeError:
|
||||
widget_index = -1
|
||||
if current_index == widget_index:
|
||||
QTimer.singleShot(0, self._widget.setFocus)
|
||||
|
||||
QTimer.singleShot(0, self._widget.setFocus)
|
||||
elif event.type() == QEvent.ChildRemoved:
|
||||
child = event.child()
|
||||
log.mouse.debug("{}: removed child {}".format(obj, child))
|
||||
|
||||
@@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
@@ -307,14 +307,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
"""Handle QNetworkReply errors."""
|
||||
if code == QNetworkReply.OperationCanceledError:
|
||||
return
|
||||
|
||||
if self._reply is None:
|
||||
error = "Unknown error: {}".format(
|
||||
debug.qenum_key(QNetworkReply, code))
|
||||
else:
|
||||
error = self._reply.errorString()
|
||||
|
||||
self._die(error)
|
||||
self._die(self._reply.errorString())
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_read_timer_timeout(self):
|
||||
|
||||
@@ -34,6 +34,7 @@ import urllib
|
||||
import collections
|
||||
|
||||
import pkg_resources
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
|
||||
import qutebrowser
|
||||
@@ -41,7 +42,6 @@ from qutebrowser.config import config, configdata, configexc, configdiff
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg, urlutils)
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
pyeval_output = ":pyeval was never called"
|
||||
@@ -178,7 +178,7 @@ def data_for_url(url):
|
||||
except OSError as e:
|
||||
# FIXME:qtwebengine how to handle this?
|
||||
raise QuteSchemeOSError(e)
|
||||
except QuteSchemeError:
|
||||
except QuteSchemeError as e:
|
||||
raise
|
||||
|
||||
assert mimetype is not None, url
|
||||
@@ -242,7 +242,7 @@ def history_data(start_time, offset=None):
|
||||
end_time = start_time - 24*60*60
|
||||
entries = hist.entries_between(end_time, start_time)
|
||||
|
||||
return [{"url": e.url,
|
||||
return [{"url": html.escape(e.url),
|
||||
"title": html.escape(e.title) or html.escape(e.url),
|
||||
"time": e.atime} for e in entries]
|
||||
|
||||
|
||||
@@ -34,22 +34,21 @@ class CallSuper(Exception):
|
||||
"""Raised when the caller should call the superclass instead."""
|
||||
|
||||
|
||||
def custom_headers(url):
|
||||
def custom_headers():
|
||||
"""Get the combined custom headers."""
|
||||
headers = {}
|
||||
|
||||
dnt_config = config.instance.get('content.headers.do_not_track', url=url)
|
||||
dnt_config = config.val.content.headers.do_not_track
|
||||
if dnt_config is not None:
|
||||
dnt = b'1' if dnt_config else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
conf_headers = config.instance.get('content.headers.custom', url=url)
|
||||
conf_headers = config.val.content.headers.custom
|
||||
for header, value in conf_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
|
||||
accept_language = config.instance.get('content.headers.accept_language',
|
||||
url=url)
|
||||
accept_language = config.val.content.headers.accept_language
|
||||
if accept_language is not None:
|
||||
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
||||
|
||||
@@ -157,7 +156,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.instance.get('content.ssl_strict', url=url)
|
||||
ssl_strict = config.val.content.ssl_strict
|
||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
|
||||
@@ -197,8 +196,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
blocking=False):
|
||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||
"""Handle a feature permission request.
|
||||
|
||||
Args:
|
||||
@@ -208,13 +206,11 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
yes_action: A callable to call if the request was approved
|
||||
no_action: A callable to call if the request was denied
|
||||
abort_on: A list of signals which interrupt the question.
|
||||
blocking: If True, ask a blocking question.
|
||||
|
||||
Return:
|
||||
The Question object if a question was asked (and blocking=False),
|
||||
None otherwise.
|
||||
The Question object if a question was asked, None otherwise.
|
||||
"""
|
||||
config_val = config.instance.get(option, url=url)
|
||||
config_val = config.instance.get(option)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
@@ -224,20 +220,10 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
urlstr = None
|
||||
text = "Allow the website to {}?".format(msg)
|
||||
|
||||
if blocking:
|
||||
answer = message.ask(abort_on=abort_on, title='Permission request',
|
||||
text=text, url=urlstr,
|
||||
mode=usertypes.PromptMode.yesno)
|
||||
if answer:
|
||||
yes_action()
|
||||
else:
|
||||
no_action()
|
||||
return None
|
||||
else:
|
||||
return message.confirm_async(
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
cancel_action=no_action, abort_on=abort_on,
|
||||
title='Permission request', text=text, url=urlstr)
|
||||
return message.confirm_async(
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
cancel_action=no_action, abort_on=abort_on,
|
||||
title='Permission request', text=text, url=urlstr)
|
||||
elif config_val:
|
||||
yes_action()
|
||||
return None
|
||||
@@ -313,10 +299,10 @@ def netrc_authentication(url, authenticator):
|
||||
(user, _account, password) = authenticators
|
||||
except FileNotFoundError:
|
||||
log.misc.debug("No .netrc file found")
|
||||
except OSError as e:
|
||||
log.misc.exception("Unable to read the netrc file: {}".format(e))
|
||||
except netrc.NetrcParseError as e:
|
||||
log.misc.exception("Error when parsing the netrc file: {}".format(e))
|
||||
except OSError:
|
||||
log.misc.exception("Unable to read the netrc file")
|
||||
except netrc.NetrcParseError:
|
||||
log.misc.exception("Error when parsing the netrc file")
|
||||
|
||||
if user is None:
|
||||
return False
|
||||
|
||||
@@ -28,10 +28,6 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
|
||||
"""A wrapper over a QWebEngineCertificateError."""
|
||||
|
||||
def __init__(self, error):
|
||||
super().__init__(error)
|
||||
self.ignore = False
|
||||
|
||||
def __str__(self):
|
||||
return self._error.errorDescription()
|
||||
|
||||
@@ -41,8 +37,5 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
self._error.error()),
|
||||
string=str(self))
|
||||
|
||||
def url(self):
|
||||
return self._error.url()
|
||||
|
||||
def is_overridable(self):
|
||||
return self._error.isOverridable()
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2018 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/>.
|
||||
|
||||
"""Filter for QtWebEngine cookies."""
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import utils
|
||||
|
||||
|
||||
def _accept_cookie(request):
|
||||
"""Check whether the given cookie should be accepted."""
|
||||
accept = config.val.content.cookies.accept
|
||||
if accept == 'all':
|
||||
return True
|
||||
elif accept in ['no-3rdparty', 'no-unknown-3rdparty']:
|
||||
return not request.thirdParty
|
||||
elif accept == 'never':
|
||||
return False
|
||||
else:
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
def install_filter(profile):
|
||||
"""Install the cookie filter on the given profile.
|
||||
|
||||
On Qt < 5.11, the filter isn't installed.
|
||||
"""
|
||||
store = profile.cookieStore()
|
||||
try:
|
||||
store.setCookieFilter(_accept_cookie)
|
||||
except AttributeError:
|
||||
pass
|
||||
@@ -19,22 +19,20 @@
|
||||
|
||||
"""A request interceptor taking care of adblocking and custom headers."""
|
||||
|
||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
|
||||
QWebEngineUrlRequestInfo)
|
||||
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.utils import utils, log, debug
|
||||
from qutebrowser.utils import utils, log
|
||||
|
||||
|
||||
class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||
|
||||
"""Handle ad blocking and custom headers."""
|
||||
|
||||
def __init__(self, host_blocker, args, parent=None):
|
||||
def __init__(self, host_blocker, parent=None):
|
||||
super().__init__(parent)
|
||||
self._host_blocker = host_blocker
|
||||
self._args = args
|
||||
|
||||
def install(self, profile):
|
||||
"""Install the interceptor on the given QWebEngineProfile."""
|
||||
@@ -56,29 +54,15 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||
Args:
|
||||
info: QWebEngineUrlRequestInfo &info
|
||||
"""
|
||||
if 'log-requests' in self._args.debug_flags:
|
||||
resource_type = debug.qenum_key(QWebEngineUrlRequestInfo,
|
||||
info.resourceType())
|
||||
navigation_type = debug.qenum_key(QWebEngineUrlRequestInfo,
|
||||
info.navigationType())
|
||||
log.webview.debug("{} {}, first-party {}, resource {}, "
|
||||
"navigation {}".format(
|
||||
bytes(info.requestMethod()).decode('ascii'),
|
||||
info.requestUrl().toDisplayString(),
|
||||
info.firstPartyUrl().toDisplayString(),
|
||||
resource_type, navigation_type))
|
||||
|
||||
url = info.requestUrl()
|
||||
|
||||
# FIXME:qtwebengine only block ads for NavigationTypeOther?
|
||||
if self._host_blocker.is_blocked(url):
|
||||
if self._host_blocker.is_blocked(info.requestUrl()):
|
||||
log.webview.info("Request to {} blocked by host blocker.".format(
|
||||
url.host()))
|
||||
info.requestUrl().host()))
|
||||
info.block(True)
|
||||
|
||||
for header, value in shared.custom_headers(url=url):
|
||||
for header, value in shared.custom_headers():
|
||||
info.setHttpHeader(header, value)
|
||||
|
||||
user_agent = config.instance.get('content.headers.user_agent', url=url)
|
||||
user_agent = config.val.content.headers.user_agent
|
||||
if user_agent is not None:
|
||||
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
||||
|
||||
@@ -21,12 +21,10 @@
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from PyQt5.QtCore import QLibraryInfo
|
||||
from qutebrowser.utils import log, message, standarddir, qtutils
|
||||
from qutebrowser.utils import log, message
|
||||
|
||||
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
||||
|
||||
@@ -41,12 +39,9 @@ def version(filename):
|
||||
return tuple(int(n) for n in match.group('version').split('-'))
|
||||
|
||||
|
||||
def dictionary_dir(old=False):
|
||||
def dictionary_dir():
|
||||
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
|
||||
if qtutils.version_check('5.10', compiled=False) and not old:
|
||||
datapath = standarddir.data()
|
||||
else:
|
||||
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
||||
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
||||
return os.path.join(datapath, 'qtwebengine_dictionaries')
|
||||
|
||||
|
||||
@@ -78,16 +73,3 @@ def local_filename(code):
|
||||
"""
|
||||
all_installed = local_files(code)
|
||||
return os.path.splitext(all_installed[0])[0] if all_installed else None
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the dictionary path if supported."""
|
||||
if qtutils.version_check('5.10', compiled=False):
|
||||
new_dir = dictionary_dir()
|
||||
old_dir = dictionary_dir(old=True)
|
||||
os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = new_dir
|
||||
try:
|
||||
if os.path.exists(old_dir) and not os.path.exists(new_dir):
|
||||
shutil.copytree(old_dir, new_dir)
|
||||
except OSError:
|
||||
log.misc.exception("Failed to copy old dictionaries")
|
||||
|
||||
@@ -39,8 +39,8 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
||||
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
|
||||
self._set_widget(view)
|
||||
|
||||
def _inspect_old(self, page):
|
||||
"""Set up the inspector for Qt < 5.11."""
|
||||
def inspect(self, _page):
|
||||
"""Set up the inspector."""
|
||||
try:
|
||||
port = int(os.environ['QTWEBENGINE_REMOTE_DEBUGGING'])
|
||||
except KeyError:
|
||||
@@ -48,18 +48,5 @@ class WebEngineInspector(inspector.AbstractWebInspector):
|
||||
"QtWebEngine inspector is not enabled. See "
|
||||
"'qutebrowser --help' for details.")
|
||||
url = QUrl('http://localhost:{}/'.format(port))
|
||||
|
||||
if page is None:
|
||||
self._widget.load(QUrl('about:blank'))
|
||||
else:
|
||||
self._widget.load(url)
|
||||
|
||||
def _inspect_new(self, page):
|
||||
"""Set up the inspector for Qt >= 5.11."""
|
||||
self._widget.page().setInspectedPage(page)
|
||||
|
||||
def inspect(self, page):
|
||||
try:
|
||||
self._inspect_new(page)
|
||||
except AttributeError:
|
||||
self._inspect_old(page)
|
||||
self._widget.load(url)
|
||||
self.show()
|
||||
|
||||
@@ -37,7 +37,6 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||
if qtutils.version_check('5.11', compiled=False):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||
profile.installUrlSchemeHandler(b'chrome-error', self)
|
||||
profile.installUrlSchemeHandler(b'chrome-extension', self)
|
||||
|
||||
def requestStarted(self, job):
|
||||
"""Handle a request for a qute: scheme.
|
||||
@@ -50,7 +49,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||
"""
|
||||
url = job.requestUrl()
|
||||
|
||||
if url.scheme() in ['chrome-error', 'chrome-extension']:
|
||||
if url.scheme() == 'chrome-error':
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
|
||||
return
|
||||
|
||||
@@ -27,12 +27,10 @@ Module attributes:
|
||||
import os
|
||||
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||
QWebEnginePage)
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
|
||||
|
||||
from qutebrowser.browser.webengine import spell
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||
from qutebrowser.utils import utils, standarddir, qtutils, message, log
|
||||
|
||||
# The default QWebEngineProfile
|
||||
@@ -89,40 +87,35 @@ class WebEngineSettings(websettings.AbstractSettings):
|
||||
|
||||
_ATTRIBUTES = {
|
||||
'content.xss_auditing':
|
||||
Attr(QWebEngineSettings.XSSAuditingEnabled),
|
||||
[QWebEngineSettings.XSSAuditingEnabled],
|
||||
'content.images':
|
||||
Attr(QWebEngineSettings.AutoLoadImages),
|
||||
[QWebEngineSettings.AutoLoadImages],
|
||||
'content.javascript.enabled':
|
||||
Attr(QWebEngineSettings.JavascriptEnabled),
|
||||
[QWebEngineSettings.JavascriptEnabled],
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attr(QWebEngineSettings.JavascriptCanOpenWindows),
|
||||
[QWebEngineSettings.JavascriptCanOpenWindows],
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attr(QWebEngineSettings.JavascriptCanAccessClipboard),
|
||||
[QWebEngineSettings.JavascriptCanAccessClipboard],
|
||||
'content.plugins':
|
||||
Attr(QWebEngineSettings.PluginsEnabled),
|
||||
[QWebEngineSettings.PluginsEnabled],
|
||||
'content.hyperlink_auditing':
|
||||
Attr(QWebEngineSettings.HyperlinkAuditingEnabled),
|
||||
[QWebEngineSettings.HyperlinkAuditingEnabled],
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attr(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
|
||||
[QWebEngineSettings.LocalContentCanAccessRemoteUrls],
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attr(QWebEngineSettings.LocalContentCanAccessFileUrls),
|
||||
[QWebEngineSettings.LocalContentCanAccessFileUrls],
|
||||
'content.webgl':
|
||||
Attr(QWebEngineSettings.WebGLEnabled),
|
||||
[QWebEngineSettings.WebGLEnabled],
|
||||
'content.local_storage':
|
||||
Attr(QWebEngineSettings.LocalStorageEnabled),
|
||||
'content.desktop_capture':
|
||||
Attr(QWebEngineSettings.ScreenCaptureEnabled,
|
||||
converter=lambda val: True if val == 'ask' else val),
|
||||
# 'ask' is handled via the permission system,
|
||||
# or a hardcoded dialog on Qt < 5.10
|
||||
[QWebEngineSettings.LocalStorageEnabled],
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attr(QWebEngineSettings.SpatialNavigationEnabled),
|
||||
[QWebEngineSettings.SpatialNavigationEnabled],
|
||||
'input.links_included_in_focus_chain':
|
||||
Attr(QWebEngineSettings.LinksIncludedInFocusChain),
|
||||
[QWebEngineSettings.LinksIncludedInFocusChain],
|
||||
|
||||
'scrolling.smooth':
|
||||
Attr(QWebEngineSettings.ScrollAnimatorEnabled),
|
||||
[QWebEngineSettings.ScrollAnimatorEnabled],
|
||||
}
|
||||
|
||||
_FONT_SIZES = {
|
||||
@@ -161,19 +154,15 @@ class WebEngineSettings(websettings.AbstractSettings):
|
||||
# Attributes which don't exist in all Qt versions.
|
||||
new_attributes = {
|
||||
# Qt 5.8
|
||||
'content.print_element_backgrounds':
|
||||
('PrintElementBackgrounds', None),
|
||||
# Qt 5.11
|
||||
'content.autoplay':
|
||||
('PlaybackRequiresUserGesture', lambda val: not val),
|
||||
'content.print_element_backgrounds': 'PrintElementBackgrounds',
|
||||
}
|
||||
for name, (attribute, converter) in new_attributes.items():
|
||||
for name, attribute in new_attributes.items():
|
||||
try:
|
||||
value = getattr(QWebEngineSettings, attribute)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
self._ATTRIBUTES[name] = Attr(value, converter=converter)
|
||||
self._ATTRIBUTES[name] = [value]
|
||||
|
||||
|
||||
class ProfileSetter:
|
||||
@@ -187,17 +176,8 @@ class ProfileSetter:
|
||||
"""Initialize settings on the given profile."""
|
||||
self.set_http_headers()
|
||||
self.set_http_cache_size()
|
||||
|
||||
settings = self._profile.settings()
|
||||
settings.setAttribute(
|
||||
self._profile.settings().setAttribute(
|
||||
QWebEngineSettings.FullScreenSupportEnabled, True)
|
||||
try:
|
||||
settings.setAttribute(
|
||||
QWebEngineSettings.FocusOnNavigationEnabled, False)
|
||||
except AttributeError:
|
||||
# Added in Qt 5.8
|
||||
pass
|
||||
|
||||
if qtutils.version_check('5.8'):
|
||||
self.set_dictionary_language()
|
||||
|
||||
@@ -294,12 +274,9 @@ def _init_profiles():
|
||||
|
||||
def init(args):
|
||||
"""Initialize the global QWebSettings."""
|
||||
if (args.enable_webengine_inspector and
|
||||
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
|
||||
if args.enable_webengine_inspector:
|
||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
||||
|
||||
spell.init()
|
||||
|
||||
_init_profiles()
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
|
||||
@@ -25,8 +25,9 @@ import sys
|
||||
import re
|
||||
import html as html_utils
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
|
||||
QUrl, QTimer, QObject, qVersion)
|
||||
QUrl, QTimer)
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@@ -36,12 +37,11 @@ from qutebrowser.config import configdata, config
|
||||
from qutebrowser.browser import browsertab, mouse, shared
|
||||
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
||||
interceptor, webenginequtescheme,
|
||||
cookies, webenginedownloads,
|
||||
webenginesettings, certificateerror)
|
||||
webenginedownloads,
|
||||
webenginesettings)
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
message, objreg, jinja, debug)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
_qute_scheme_handler = None
|
||||
@@ -62,9 +62,8 @@ def init():
|
||||
|
||||
log.init.debug("Initializing request interceptor...")
|
||||
host_blocker = objreg.get('host-blocker')
|
||||
args = objreg.get('args')
|
||||
req_interceptor = interceptor.RequestInterceptor(
|
||||
host_blocker, args=args, parent=app)
|
||||
host_blocker, parent=app)
|
||||
req_interceptor.install(webenginesettings.default_profile)
|
||||
req_interceptor.install(webenginesettings.private_profile)
|
||||
|
||||
@@ -74,18 +73,6 @@ def init():
|
||||
download_manager.install(webenginesettings.private_profile)
|
||||
objreg.register('webengine-download-manager', download_manager)
|
||||
|
||||
log.init.debug("Initializing cookie filter...")
|
||||
cookies.install_filter(webenginesettings.default_profile)
|
||||
cookies.install_filter(webenginesettings.private_profile)
|
||||
|
||||
# Clear visited links on web history clear
|
||||
hist = objreg.get('web-history')
|
||||
for p in [webenginesettings.default_profile,
|
||||
webenginesettings.private_profile]:
|
||||
hist.history_cleared.connect(p.clearAllVisitedLinks)
|
||||
hist.url_cleared.connect(lambda url, profile=p:
|
||||
profile.clearVisitedLinks([url]))
|
||||
|
||||
|
||||
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
|
||||
_JS_WORLD_MAP = {
|
||||
@@ -110,11 +97,7 @@ class WebEngineAction(browsertab.AbstractAction):
|
||||
"""Save the current page."""
|
||||
self._widget.triggerPageAction(QWebEnginePage.SavePage)
|
||||
|
||||
def show_source(self, pygments=False):
|
||||
if pygments:
|
||||
self._show_source_pygments()
|
||||
return
|
||||
|
||||
def show_source(self):
|
||||
try:
|
||||
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
|
||||
except AttributeError:
|
||||
@@ -246,8 +229,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||
self._tab.search.clear()
|
||||
|
||||
self._tab.run_js_async(
|
||||
javascript.assemble('caret',
|
||||
'setPlatform', sys.platform, qVersion()))
|
||||
javascript.assemble('caret', 'setPlatform', sys.platform))
|
||||
self._js_call('setInitialCursor', self._selection_cb)
|
||||
|
||||
def _selection_cb(self, enabled):
|
||||
@@ -344,11 +326,6 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||
"""
|
||||
if js_elem is None:
|
||||
return
|
||||
if js_elem == "focused":
|
||||
# we had a focused element, not a selected one. Just send <enter>
|
||||
self._follow_enter(tab)
|
||||
return
|
||||
|
||||
assert isinstance(js_elem, dict), js_elem
|
||||
elem = webengineelem.WebEngineElement(js_elem, tab=self._tab)
|
||||
if tab:
|
||||
@@ -371,11 +348,14 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||
|
||||
log.webview.debug("Clicking a searched link via fake key press.")
|
||||
# send a fake enter, clicking the orange selection box
|
||||
self._follow_enter(tab)
|
||||
if tab:
|
||||
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
|
||||
else:
|
||||
self._tab.key_press(Qt.Key_Enter)
|
||||
|
||||
else:
|
||||
# click an existing blue selection
|
||||
js_code = javascript.assemble('webelem',
|
||||
'find_selected_focused_link')
|
||||
js_code = javascript.assemble('webelem', 'find_selected_link')
|
||||
self._tab.run_js_async(js_code, lambda jsret:
|
||||
self._follow_selected_cb(jsret, tab))
|
||||
|
||||
@@ -626,172 +606,42 @@ class WebEngineElements(browsertab.AbstractElements):
|
||||
self._tab.run_js_async(js_code, js_cb)
|
||||
|
||||
|
||||
class WebEngineAudio(browsertab.AbstractAudio):
|
||||
class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
"""QtWebEngine implemementations related to audio/muting."""
|
||||
"""A QtWebEngine tab in the browser.
|
||||
|
||||
def _connect_signals(self):
|
||||
page = self._widget.page()
|
||||
page.audioMutedChanged.connect(self.muted_changed)
|
||||
page.recentlyAudibleChanged.connect(self.recently_audible_changed)
|
||||
Signals:
|
||||
_load_finished_fake:
|
||||
Used in place of unreliable loadFinished
|
||||
"""
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
page = self._widget.page()
|
||||
page.setAudioMuted(muted)
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||
_load_finished_fake = pyqtSignal(bool)
|
||||
|
||||
def is_muted(self):
|
||||
page = self._widget.page()
|
||||
return page.isAudioMuted()
|
||||
|
||||
def is_recently_audible(self):
|
||||
page = self._widget.page()
|
||||
return page.recentlyAudible()
|
||||
|
||||
|
||||
class _WebEnginePermissions(QObject):
|
||||
|
||||
"""Handling of various permission-related signals."""
|
||||
|
||||
_abort_questions = pyqtSignal()
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
|
||||
def connect_signals(self):
|
||||
"""Connect related signals from the QWebEnginePage."""
|
||||
page = self._widget.page()
|
||||
page.fullScreenRequested.connect(
|
||||
self._on_fullscreen_requested)
|
||||
page.featurePermissionRequested.connect(
|
||||
self._on_feature_permission_requested)
|
||||
|
||||
if qtutils.version_check('5.11'):
|
||||
page.quotaRequested.connect(
|
||||
self._on_quota_requested)
|
||||
page.registerProtocolHandlerRequested.connect(
|
||||
self._on_register_protocol_handler_requested)
|
||||
|
||||
self._tab.shutting_down.connect(self._abort_questions)
|
||||
self._tab.load_started.connect(self._abort_questions)
|
||||
|
||||
@pyqtSlot('QWebEngineFullScreenRequest')
|
||||
def _on_fullscreen_requested(self, request):
|
||||
request.accept()
|
||||
on = request.toggleOn()
|
||||
|
||||
self._tab.data.fullscreen = on
|
||||
self._tab.fullscreen_requested.emit(on)
|
||||
if on:
|
||||
notification = miscwidgets.FullscreenNotification(self._widget)
|
||||
notification.show()
|
||||
notification.set_timeout(3000)
|
||||
|
||||
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
|
||||
def _on_feature_permission_requested(self, url, feature):
|
||||
"""Ask the user for approval for geolocation/media/etc.."""
|
||||
options = {
|
||||
QWebEnginePage.Geolocation: 'content.geolocation',
|
||||
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
|
||||
}
|
||||
messages = {
|
||||
QWebEnginePage.Geolocation: 'access your location',
|
||||
QWebEnginePage.MediaAudioCapture: 'record audio',
|
||||
QWebEnginePage.MediaVideoCapture: 'record video',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
||||
}
|
||||
try:
|
||||
options.update({
|
||||
QWebEnginePage.DesktopVideoCapture:
|
||||
'content.desktop_capture',
|
||||
QWebEnginePage.DesktopAudioVideoCapture:
|
||||
'content.desktop_capture',
|
||||
})
|
||||
messages.update({
|
||||
QWebEnginePage.DesktopVideoCapture:
|
||||
'capture your desktop',
|
||||
QWebEnginePage.DesktopAudioVideoCapture:
|
||||
'capture your desktop and audio',
|
||||
})
|
||||
except AttributeError:
|
||||
# Added in Qt 5.10
|
||||
pass
|
||||
|
||||
assert options.keys() == messages.keys()
|
||||
|
||||
page = self._widget.page()
|
||||
|
||||
if feature not in options:
|
||||
log.webview.error("Unhandled feature permission {}".format(
|
||||
debug.qenum_key(QWebEnginePage, feature)))
|
||||
page.setFeaturePermission(url, feature,
|
||||
QWebEnginePage.PermissionDeniedByUser)
|
||||
return
|
||||
|
||||
yes_action = functools.partial(
|
||||
page.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionGrantedByUser)
|
||||
no_action = functools.partial(
|
||||
page.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionDeniedByUser)
|
||||
|
||||
question = shared.feature_permission(
|
||||
url=url, option=options[feature], msg=messages[feature],
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
abort_on=[self._abort_questions])
|
||||
|
||||
if question is not None:
|
||||
page.featurePermissionRequestCanceled.connect(
|
||||
functools.partial(self._on_feature_permission_cancelled,
|
||||
question, url, feature))
|
||||
|
||||
def _on_feature_permission_cancelled(self, question, url, feature,
|
||||
cancelled_url, cancelled_feature):
|
||||
"""Slot invoked when a feature permission request was cancelled.
|
||||
|
||||
To be used with functools.partial.
|
||||
"""
|
||||
if url == cancelled_url and feature == cancelled_feature:
|
||||
try:
|
||||
question.abort()
|
||||
except RuntimeError:
|
||||
# The question could already be deleted, e.g. because it was
|
||||
# aborted after a loadStarted signal.
|
||||
pass
|
||||
|
||||
def _on_quota_requested(self, request):
|
||||
size = utils.format_size(request.requestedSize())
|
||||
shared.feature_permission(
|
||||
url=request.origin(),
|
||||
option='content.persistent_storage',
|
||||
msg='use {} of persistent storage'.format(size),
|
||||
yes_action=request.accept, no_action=request.reject,
|
||||
abort_on=[self._abort_questions],
|
||||
blocking=True)
|
||||
|
||||
def _on_register_protocol_handler_requested(self, request):
|
||||
shared.feature_permission(
|
||||
url=request.origin(),
|
||||
option='content.register_protocol_handler',
|
||||
msg='open all {} links'.format(request.scheme()),
|
||||
yes_action=request.accept, no_action=request.reject,
|
||||
abort_on=[self._abort_questions],
|
||||
blocking=True)
|
||||
|
||||
|
||||
class _WebEngineScripts(QObject):
|
||||
|
||||
def __init__(self, tab, parent=None):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
self._greasemonkey = objreg.get('greasemonkey')
|
||||
|
||||
def connect_signals(self):
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
super().__init__(win_id=win_id, mode_manager=mode_manager,
|
||||
private=private, parent=parent)
|
||||
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
|
||||
private=private)
|
||||
self.history = WebEngineHistory(self)
|
||||
self.scroller = WebEngineScroller(self, parent=self)
|
||||
self.caret = WebEngineCaret(mode_manager=mode_manager,
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebEngineZoom(tab=self, parent=self)
|
||||
self.search = WebEngineSearch(parent=self)
|
||||
self.printing = WebEnginePrinting()
|
||||
self.elements = WebEngineElements(tab=self)
|
||||
self.action = WebEngineAction(tab=self)
|
||||
# We're assigning settings in _set_widget
|
||||
self.settings = webenginesettings.WebEngineSettings(settings=None)
|
||||
self._set_widget(widget)
|
||||
self._connect_signals()
|
||||
self.backend = usertypes.Backend.QtWebEngine
|
||||
self._child_event_filter = None
|
||||
self._saved_zoom = None
|
||||
self._reload_url = None
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
self._init_js()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
@@ -803,7 +653,7 @@ class _WebEngineScripts(QObject):
|
||||
"""Update the custom stylesheet in existing tabs."""
|
||||
css = shared.get_user_stylesheet()
|
||||
code = javascript.assemble('stylesheet', 'set_css', css)
|
||||
self._tab.run_js_async(code)
|
||||
self.run_js_async(code)
|
||||
|
||||
def _inject_early_js(self, name, js_code, *,
|
||||
world=QWebEngineScript.ApplicationWorld,
|
||||
@@ -838,7 +688,7 @@ class _WebEngineScripts(QObject):
|
||||
if not script.isNull():
|
||||
scripts.remove(script)
|
||||
|
||||
def init(self):
|
||||
def _init_js(self):
|
||||
"""Initialize global qutebrowser JavaScript."""
|
||||
js_code = javascript.wrap_global(
|
||||
'scripts',
|
||||
@@ -846,24 +696,13 @@ class _WebEngineScripts(QObject):
|
||||
utils.read_file('javascript/webelem.js'),
|
||||
utils.read_file('javascript/caret.js'),
|
||||
)
|
||||
self._inject_early_js('js',
|
||||
utils.read_file('javascript/print.js'),
|
||||
subframes=True,
|
||||
world=QWebEngineScript.MainWorld)
|
||||
# FIXME:qtwebengine what about subframes=True?
|
||||
self._inject_early_js('js', js_code, subframes=True)
|
||||
self._init_stylesheet()
|
||||
|
||||
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
||||
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
|
||||
# response to urlChanged.
|
||||
if not qtutils.version_check('5.8'):
|
||||
self._tab.url_changed.connect(
|
||||
self._inject_greasemonkey_scripts_for_url)
|
||||
else:
|
||||
self._greasemonkey.scripts_reloaded.connect(
|
||||
self._inject_all_greasemonkey_scripts)
|
||||
self._inject_all_greasemonkey_scripts()
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
greasemonkey.scripts_reloaded.connect(self._inject_userscripts)
|
||||
self._inject_userscripts()
|
||||
|
||||
def _init_stylesheet(self):
|
||||
"""Initialize custom stylesheets.
|
||||
@@ -880,123 +719,40 @@ class _WebEngineScripts(QObject):
|
||||
)
|
||||
self._inject_early_js('stylesheet', js_code, subframes=True)
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _inject_greasemonkey_scripts_for_url(self, url):
|
||||
matching_scripts = self._greasemonkey.scripts_for(url)
|
||||
self._inject_greasemonkey_scripts(
|
||||
matching_scripts.start, QWebEngineScript.DocumentCreation, True)
|
||||
self._inject_greasemonkey_scripts(
|
||||
matching_scripts.end, QWebEngineScript.DocumentReady, False)
|
||||
self._inject_greasemonkey_scripts(
|
||||
matching_scripts.idle, QWebEngineScript.Deferred, False)
|
||||
|
||||
@pyqtSlot()
|
||||
def _inject_all_greasemonkey_scripts(self):
|
||||
scripts = self._greasemonkey.all_scripts()
|
||||
self._inject_greasemonkey_scripts(scripts)
|
||||
|
||||
def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
|
||||
remove_first=True):
|
||||
"""Register user JavaScript files with the current tab.
|
||||
|
||||
Args:
|
||||
scripts: A list of GreasemonkeyScripts, or None to add all
|
||||
known by the Greasemonkey subsystem.
|
||||
injection_point: The QWebEngineScript::InjectionPoint stage
|
||||
to inject the script into, None to use
|
||||
auto-detection.
|
||||
remove_first: Whether to remove all previously injected
|
||||
scripts before adding these ones.
|
||||
"""
|
||||
if sip.isdeleted(self._widget):
|
||||
def _inject_userscripts(self):
|
||||
"""Register user JavaScript files with the global profiles."""
|
||||
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
||||
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
|
||||
# response to urlChanged.
|
||||
if not qtutils.version_check('5.8'):
|
||||
return
|
||||
|
||||
# Since we are inserting scripts into a per-tab collection,
|
||||
# rather than just injecting scripts on page load, we need to
|
||||
# make sure we replace existing scripts, not just add new ones.
|
||||
# While, taking care not to remove any other scripts that might
|
||||
# have been added elsewhere, like the one for stylesheets.
|
||||
page_scripts = self._widget.page().scripts()
|
||||
if remove_first:
|
||||
for script in page_scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug('Removing script: {}'
|
||||
.format(script.name()))
|
||||
removed = page_scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
# Since we are inserting scripts into profile.scripts they won't
|
||||
# just get replaced by new gm scripts like if we were injecting them
|
||||
# ourselves so we need to remove all gm scripts, while not removing
|
||||
# any other stuff that might have been added. Like the one for
|
||||
# stylesheets.
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
scripts = self._widget.page().scripts()
|
||||
for script in scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug('Removing script: {}'
|
||||
.format(script.name()))
|
||||
removed = scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
|
||||
if not scripts:
|
||||
return
|
||||
|
||||
for script in scripts:
|
||||
# Then add the new scripts.
|
||||
for script in greasemonkey.all_scripts():
|
||||
# @run-at (and @include/@exclude/@match) is parsed by
|
||||
# QWebEngineScript.
|
||||
new_script = QWebEngineScript()
|
||||
try:
|
||||
world = int(script.jsworld)
|
||||
except ValueError:
|
||||
try:
|
||||
world = _JS_WORLD_MAP[usertypes.JsWorld[
|
||||
script.jsworld.lower()]]
|
||||
except KeyError:
|
||||
log.greasemonkey.error(
|
||||
"script {} has invalid value for '@qute-js-world'"
|
||||
": {}".format(script.name, script.jsworld))
|
||||
continue
|
||||
new_script.setWorldId(world)
|
||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName("GM-{}".format(script.name))
|
||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||
# Override the @run-at value parsed by QWebEngineScript if desired.
|
||||
if injection_point:
|
||||
new_script.setInjectionPoint(injection_point)
|
||||
log.greasemonkey.debug('adding script: {}'
|
||||
.format(new_script.name()))
|
||||
page_scripts.insert(new_script)
|
||||
|
||||
|
||||
class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
"""A QtWebEngine tab in the browser.
|
||||
|
||||
Signals:
|
||||
_load_finished_fake:
|
||||
Used in place of unreliable loadFinished
|
||||
"""
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||
_load_finished_fake = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||
super().__init__(win_id=win_id, mode_manager=mode_manager,
|
||||
private=private, parent=parent)
|
||||
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
|
||||
private=private)
|
||||
self.history = WebEngineHistory(self)
|
||||
self.scroller = WebEngineScroller(self, parent=self)
|
||||
self.caret = WebEngineCaret(mode_manager=mode_manager,
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebEngineZoom(tab=self, parent=self)
|
||||
self.search = WebEngineSearch(parent=self)
|
||||
self.printing = WebEnginePrinting()
|
||||
self.elements = WebEngineElements(tab=self)
|
||||
self.action = WebEngineAction(tab=self)
|
||||
self.audio = WebEngineAudio(parent=self)
|
||||
self._permissions = _WebEnginePermissions(tab=self, parent=self)
|
||||
self._scripts = _WebEngineScripts(tab=self, parent=self)
|
||||
# We're assigning settings in _set_widget
|
||||
self.settings = webenginesettings.WebEngineSettings(settings=None)
|
||||
self._set_widget(widget)
|
||||
self._connect_signals()
|
||||
self.backend = usertypes.Backend.QtWebEngine
|
||||
self._child_event_filter = None
|
||||
self._saved_zoom = None
|
||||
self._reload_url = None
|
||||
self._scripts.init()
|
||||
|
||||
def _set_widget(self, widget):
|
||||
# pylint: disable=protected-access
|
||||
super()._set_widget(widget)
|
||||
self._permissions._widget = widget
|
||||
self._scripts._widget = widget
|
||||
scripts.insert(new_script)
|
||||
|
||||
def _install_event_filter(self):
|
||||
fp = self._widget.focusProxy()
|
||||
@@ -1004,7 +760,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
fp.installEventFilter(self._mouse_event_filter)
|
||||
self._child_event_filter = mouse.ChildEventFilter(
|
||||
eventfilter=self._mouse_event_filter, widget=self._widget,
|
||||
win_id=self.win_id, parent=self)
|
||||
parent=self)
|
||||
self._widget.installEventFilter(self._child_event_filter)
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -1024,9 +780,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
url: The QUrl to open.
|
||||
predict: If set to False, predicted_navigation is not emitted.
|
||||
"""
|
||||
if sip.isdeleted(self._widget):
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3896
|
||||
return
|
||||
self._saved_zoom = self.zoom.factor()
|
||||
self._openurl_prepare(url, predict=predict)
|
||||
self._widget.load(url)
|
||||
@@ -1182,6 +935,18 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
|
||||
self._show_error_page(url, "Authentication required")
|
||||
|
||||
@pyqtSlot('QWebEngineFullScreenRequest')
|
||||
def _on_fullscreen_requested(self, request):
|
||||
request.accept()
|
||||
on = request.toggleOn()
|
||||
|
||||
self.data.fullscreen = on
|
||||
self.fullscreen_requested.emit(on)
|
||||
if on:
|
||||
notification = miscwidgets.FullscreenNotification(self)
|
||||
notification.show()
|
||||
notification.set_timeout(3000)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_load_started(self):
|
||||
"""Clear search when a new load is started if needed."""
|
||||
@@ -1272,55 +1037,16 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
# the old icon is still displayed.
|
||||
self.icon_changed.emit(QIcon())
|
||||
|
||||
@pyqtSlot(certificateerror.CertificateErrorWrapper)
|
||||
def _on_ssl_errors(self, error):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
url = error.url()
|
||||
log.webview.debug("Certificate error: {}".format(error))
|
||||
|
||||
if error.is_overridable():
|
||||
error.ignore = shared.ignore_certificate_errors(
|
||||
url, [error], abort_on=[self.shutting_down, self.load_started])
|
||||
else:
|
||||
log.webview.error("Non-overridable certificate error: "
|
||||
"{}".format(error))
|
||||
|
||||
log.webview.debug("ignore {}, URL {}, requested {}".format(
|
||||
error.ignore, url, self.url(requested=True)))
|
||||
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207
|
||||
# We can't really know when to show an error page, as the error might
|
||||
# have happened when loading some resource.
|
||||
# However, self.url() is not available yet and the requested URL
|
||||
# might not match the URL we get from the error - so we just apply a
|
||||
# heuristic here.
|
||||
if (not qtutils.version_check('5.9') and
|
||||
not error.ignore and
|
||||
url.matches(self.url(requested=True), QUrl.RemoveScheme)):
|
||||
self._show_error_page(url, str(error))
|
||||
|
||||
@pyqtSlot(QUrl)
|
||||
def _on_predicted_navigation(self, url):
|
||||
"""If we know we're going to visit an URL soon, change the settings.
|
||||
|
||||
This is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
||||
"""
|
||||
"""If we know we're going to visit an URL soon, change the settings."""
|
||||
super()._on_predicted_navigation(url)
|
||||
if not qtutils.version_check('5.11.1', compiled=False):
|
||||
self.settings.update_for_url(url)
|
||||
self.settings.update_for_url(url)
|
||||
|
||||
@pyqtSlot(usertypes.NavigationRequest)
|
||||
def _on_navigation_request(self, navigation):
|
||||
super()._on_navigation_request(navigation)
|
||||
|
||||
if navigation.url == QUrl('qute://print'):
|
||||
command_dispatcher = objreg.get('command-dispatcher',
|
||||
scope='window',
|
||||
window=self.win_id)
|
||||
command_dispatcher.printpage()
|
||||
navigation.accepted = False
|
||||
|
||||
if not navigation.accepted or not navigation.is_main_frame:
|
||||
return
|
||||
|
||||
@@ -1328,8 +1054,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
'content.plugins',
|
||||
'content.javascript.enabled',
|
||||
'content.javascript.can_access_clipboard',
|
||||
'content.javascript.can_access_clipboard',
|
||||
'content.print_element_backgrounds',
|
||||
'input.spatial_navigation',
|
||||
'input.spatial_navigation',
|
||||
}
|
||||
assert settings_needing_reload.issubset(configdata.DATA)
|
||||
|
||||
@@ -1338,16 +1066,14 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
# On Qt < 5.11, we don't don't need a reload when type == link_clicked.
|
||||
# On Qt 5.11.0, we always need a reload.
|
||||
# On Qt > 5.11.0, we never need a reload:
|
||||
# https://codereview.qt-project.org/#/c/229525/1
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
||||
if qtutils.version_check('5.11.1', compiled=False):
|
||||
reload_needed = False
|
||||
elif not qtutils.version_check('5.11.0', exact=True, compiled=False):
|
||||
# TODO on Qt > 5.11.0, we hopefully never need a reload:
|
||||
# https://codereview.qt-project.org/#/c/229525/1
|
||||
if not qtutils.version_check('5.11.0', exact=True, compiled=False):
|
||||
if navigation.navigation_type == navigation.Type.link_clicked:
|
||||
reload_needed = False
|
||||
|
||||
if reload_needed:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
|
||||
self._reload_url = navigation.url
|
||||
|
||||
def _connect_signals(self):
|
||||
@@ -1362,6 +1088,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
page.authenticationRequired.connect(self._on_authentication_required)
|
||||
page.proxyAuthenticationRequired.connect(
|
||||
self._on_proxy_authentication_required)
|
||||
page.fullScreenRequested.connect(self._on_fullscreen_requested)
|
||||
page.contentsSizeChanged.connect(self.contents_size_changed)
|
||||
page.navigation_request.connect(self._on_navigation_request)
|
||||
|
||||
@@ -1386,10 +1113,5 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
|
||||
self.predicted_navigation.connect(self._on_predicted_navigation)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.audio._connect_signals()
|
||||
self._permissions.connect_signals()
|
||||
self._scripts.connect_signals()
|
||||
|
||||
def event_target(self):
|
||||
return self._widget.render_widget()
|
||||
|
||||
@@ -19,17 +19,20 @@
|
||||
|
||||
"""The main browser widget for QtWebEngine."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
|
||||
import functools
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
|
||||
QWebEngineScript)
|
||||
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import webenginesettings, certificateerror
|
||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, debug, usertypes, objreg, qtutils
|
||||
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
@@ -65,10 +68,9 @@ class WebEngineView(QWebEngineView):
|
||||
However, it sometimes isn't, so we use this as a WORKAROUND for
|
||||
https://bugreports.qt.io/browse/QTBUG-68727
|
||||
"""
|
||||
if 'lost-focusproxy' not in objreg.get('args').debug_flags:
|
||||
proxy = self.focusProxy()
|
||||
if proxy is not None:
|
||||
return proxy
|
||||
proxy = self.focusProxy()
|
||||
if proxy is not None:
|
||||
return proxy
|
||||
|
||||
# We don't want e.g. a QMenu.
|
||||
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
|
||||
@@ -151,22 +153,23 @@ class WebEnginePage(QWebEnginePage):
|
||||
|
||||
Signals:
|
||||
certificate_error: Emitted on certificate errors.
|
||||
Needs to be directly connected to a slot setting the
|
||||
'ignore' attribute.
|
||||
shutting_down: Emitted when the page is shutting down.
|
||||
navigation_request: Emitted on acceptNavigationRequest.
|
||||
"""
|
||||
|
||||
certificate_error = pyqtSignal(certificateerror.CertificateErrorWrapper)
|
||||
certificate_error = pyqtSignal()
|
||||
shutting_down = pyqtSignal()
|
||||
navigation_request = pyqtSignal(usertypes.NavigationRequest)
|
||||
|
||||
def __init__(self, *, theme_color, profile, parent=None):
|
||||
super().__init__(profile, parent)
|
||||
self._is_shutting_down = False
|
||||
self.featurePermissionRequested.connect(
|
||||
self._on_feature_permission_requested)
|
||||
self._theme_color = theme_color
|
||||
self._set_bg_color()
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
self.urlChanged.connect(self._inject_userjs)
|
||||
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
@@ -175,15 +178,97 @@ class WebEnginePage(QWebEnginePage):
|
||||
col = self._theme_color
|
||||
self.setBackgroundColor(col)
|
||||
|
||||
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
|
||||
def _on_feature_permission_requested(self, url, feature):
|
||||
"""Ask the user for approval for geolocation/media/etc.."""
|
||||
options = {
|
||||
QWebEnginePage.Geolocation: 'content.geolocation',
|
||||
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
|
||||
}
|
||||
messages = {
|
||||
QWebEnginePage.Geolocation: 'access your location',
|
||||
QWebEnginePage.MediaAudioCapture: 'record audio',
|
||||
QWebEnginePage.MediaVideoCapture: 'record video',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
||||
}
|
||||
assert options.keys() == messages.keys()
|
||||
|
||||
if feature not in options:
|
||||
log.webview.error("Unhandled feature permission {}".format(
|
||||
debug.qenum_key(QWebEnginePage, feature)))
|
||||
self.setFeaturePermission(url, feature,
|
||||
QWebEnginePage.PermissionDeniedByUser)
|
||||
return
|
||||
|
||||
yes_action = functools.partial(
|
||||
self.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionGrantedByUser)
|
||||
no_action = functools.partial(
|
||||
self.setFeaturePermission, url, feature,
|
||||
QWebEnginePage.PermissionDeniedByUser)
|
||||
|
||||
question = shared.feature_permission(
|
||||
url=url, option=options[feature], msg=messages[feature],
|
||||
yes_action=yes_action, no_action=no_action,
|
||||
abort_on=[self.shutting_down, self.loadStarted])
|
||||
|
||||
if question is not None:
|
||||
self.featurePermissionRequestCanceled.connect(
|
||||
functools.partial(self._on_feature_permission_cancelled,
|
||||
question, url, feature))
|
||||
|
||||
def _on_feature_permission_cancelled(self, question, url, feature,
|
||||
cancelled_url, cancelled_feature):
|
||||
"""Slot invoked when a feature permission request was cancelled.
|
||||
|
||||
To be used with functools.partial.
|
||||
"""
|
||||
if url == cancelled_url and feature == cancelled_feature:
|
||||
try:
|
||||
question.abort()
|
||||
except RuntimeError:
|
||||
# The question could already be deleted, e.g. because it was
|
||||
# aborted after a loadStarted signal.
|
||||
pass
|
||||
|
||||
def shutdown(self):
|
||||
self._is_shutting_down = True
|
||||
self.shutting_down.emit()
|
||||
|
||||
def certificateError(self, error):
|
||||
"""Handle certificate errors coming from Qt."""
|
||||
self.certificate_error.emit()
|
||||
url = error.url()
|
||||
error = certificateerror.CertificateErrorWrapper(error)
|
||||
self.certificate_error.emit(error)
|
||||
return error.ignore
|
||||
log.webview.debug("Certificate error: {}".format(error))
|
||||
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html', title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error=str(error))
|
||||
|
||||
if error.is_overridable():
|
||||
ignore = shared.ignore_certificate_errors(
|
||||
url, [error], abort_on=[self.loadStarted, self.shutting_down])
|
||||
else:
|
||||
log.webview.error("Non-overridable certificate error: "
|
||||
"{}".format(error))
|
||||
ignore = False
|
||||
|
||||
# We can't really know when to show an error page, as the error might
|
||||
# have happened when loading some resource.
|
||||
# However, self.url() is not available yet and self.requestedUrl()
|
||||
# might not match the URL we get from the error - so we just apply a
|
||||
# heuristic here.
|
||||
# See https://bugreports.qt.io/browse/QTBUG-56207
|
||||
log.webview.debug("ignore {}, URL {}, requested {}".format(
|
||||
ignore, url, self.requestedUrl()))
|
||||
if not ignore and url.matches(self.requestedUrl(), QUrl.RemoveScheme):
|
||||
self.setHtml(error_page)
|
||||
|
||||
return ignore
|
||||
|
||||
def javaScriptConfirm(self, url, js_msg):
|
||||
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
||||
@@ -261,3 +346,43 @@ class WebEnginePage(QWebEnginePage):
|
||||
is_main_frame=is_main_frame)
|
||||
self.navigation_request.emit(navigation)
|
||||
return navigation.accepted
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def _inject_userjs(self, url):
|
||||
"""Inject userscripts registered for `url` into the current page."""
|
||||
if qtutils.version_check('5.8'):
|
||||
# Handled in webenginetab with the builtin Greasemonkey
|
||||
# support.
|
||||
return
|
||||
|
||||
# Using QWebEnginePage.scripts() to hold the user scripts means
|
||||
# we don't have to worry ourselves about where to inject the
|
||||
# page but also means scripts hang around for the tab lifecycle.
|
||||
# So clear them here.
|
||||
scripts = self.scripts()
|
||||
for script in scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug("Removing script: {}"
|
||||
.format(script.name()))
|
||||
removed = scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
|
||||
def _add_script(script, injection_point):
|
||||
new_script = QWebEngineScript()
|
||||
new_script.setInjectionPoint(injection_point)
|
||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName("GM-{}".format(script.name))
|
||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||
log.greasemonkey.debug("Adding script: {}"
|
||||
.format(new_script.name()))
|
||||
scripts.insert(new_script)
|
||||
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
matching_scripts = greasemonkey.scripts_for(url)
|
||||
for script in matching_scripts.start:
|
||||
_add_script(script, QWebEngineScript.DocumentCreation)
|
||||
for script in matching_scripts.end:
|
||||
_add_script(script, QWebEngineScript.DocumentReady)
|
||||
for script in matching_scripts.idle:
|
||||
_add_script(script, QWebEngineScript.Deferred)
|
||||
|
||||
@@ -28,8 +28,7 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (message, log, usertypes, utils, objreg,
|
||||
urlutils, debug)
|
||||
from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webkit import certificateerror
|
||||
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
|
||||
@@ -148,7 +147,6 @@ class NetworkManager(QNetworkAccessManager):
|
||||
super().__init__(parent)
|
||||
log.init.debug("NetworkManager init done")
|
||||
self.adopted_downloads = 0
|
||||
self._args = objreg.get('args')
|
||||
self._win_id = win_id
|
||||
self._tab_id = tab_id
|
||||
self._private = private
|
||||
@@ -380,7 +378,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
result.setParent(self)
|
||||
return result
|
||||
|
||||
for header, value in shared.custom_headers(url=req.url()):
|
||||
for header, value in shared.custom_headers():
|
||||
req.setRawHeader(header, value)
|
||||
|
||||
host_blocker = objreg.get('host-blocker')
|
||||
@@ -408,13 +406,5 @@ class NetworkManager(QNetworkAccessManager):
|
||||
# the webpage shutdown here.
|
||||
current_url = QUrl()
|
||||
|
||||
if 'log-requests' in self._args.debug_flags:
|
||||
operation = debug.qenum_key(QNetworkAccessManager, op)
|
||||
operation = operation.replace('Operation', '').upper()
|
||||
log.webview.debug("{} {}, first-party {}".format(
|
||||
operation,
|
||||
req.url().toDisplayString(),
|
||||
current_url.toDisplayString()))
|
||||
|
||||
self.set_referer(req, current_url)
|
||||
return super().createRequest(op, req, outgoing_data)
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
|
||||
"""Customized QWebInspector for QtWebKit."""
|
||||
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
|
||||
from PyQt5.QtWebKitWidgets import QWebInspector
|
||||
|
||||
from qutebrowser.browser import inspector
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
class WebKitInspector(inspector.AbstractWebInspector):
|
||||
@@ -35,6 +36,9 @@ class WebKitInspector(inspector.AbstractWebInspector):
|
||||
self._set_widget(qwebinspector)
|
||||
|
||||
def inspect(self, page):
|
||||
settings = QWebSettings.globalSettings()
|
||||
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
|
||||
if not config.val.content.developer_extras:
|
||||
raise inspector.WebInspectorError(
|
||||
"Please enable content.developer_extras before using the "
|
||||
"webinspector!")
|
||||
self._widget.setPage(page)
|
||||
self.show()
|
||||
|
||||
@@ -30,7 +30,6 @@ from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
|
||||
from qutebrowser.config import config, websettings
|
||||
from qutebrowser.config.websettings import AttributeInfo as Attr
|
||||
from qutebrowser.utils import standarddir, urlutils
|
||||
from qutebrowser.browser import shared
|
||||
|
||||
@@ -45,48 +44,50 @@ class WebKitSettings(websettings.AbstractSettings):
|
||||
|
||||
_ATTRIBUTES = {
|
||||
'content.images':
|
||||
Attr(QWebSettings.AutoLoadImages),
|
||||
[QWebSettings.AutoLoadImages],
|
||||
'content.javascript.enabled':
|
||||
Attr(QWebSettings.JavascriptEnabled),
|
||||
[QWebSettings.JavascriptEnabled],
|
||||
'content.javascript.can_open_tabs_automatically':
|
||||
Attr(QWebSettings.JavascriptCanOpenWindows),
|
||||
[QWebSettings.JavascriptCanOpenWindows],
|
||||
'content.javascript.can_close_tabs':
|
||||
Attr(QWebSettings.JavascriptCanCloseWindows),
|
||||
[QWebSettings.JavascriptCanCloseWindows],
|
||||
'content.javascript.can_access_clipboard':
|
||||
Attr(QWebSettings.JavascriptCanAccessClipboard),
|
||||
[QWebSettings.JavascriptCanAccessClipboard],
|
||||
'content.plugins':
|
||||
Attr(QWebSettings.PluginsEnabled),
|
||||
[QWebSettings.PluginsEnabled],
|
||||
'content.webgl':
|
||||
Attr(QWebSettings.WebGLEnabled),
|
||||
[QWebSettings.WebGLEnabled],
|
||||
'content.hyperlink_auditing':
|
||||
Attr(QWebSettings.HyperlinkAuditingEnabled),
|
||||
[QWebSettings.HyperlinkAuditingEnabled],
|
||||
'content.local_content_can_access_remote_urls':
|
||||
Attr(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
[QWebSettings.LocalContentCanAccessRemoteUrls],
|
||||
'content.local_content_can_access_file_urls':
|
||||
Attr(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
[QWebSettings.LocalContentCanAccessFileUrls],
|
||||
'content.dns_prefetch':
|
||||
Attr(QWebSettings.DnsPrefetchEnabled),
|
||||
[QWebSettings.DnsPrefetchEnabled],
|
||||
'content.frame_flattening':
|
||||
Attr(QWebSettings.FrameFlatteningEnabled),
|
||||
[QWebSettings.FrameFlatteningEnabled],
|
||||
'content.cache.appcache':
|
||||
Attr(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
[QWebSettings.OfflineWebApplicationCacheEnabled],
|
||||
'content.local_storage':
|
||||
Attr(QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
[QWebSettings.LocalStorageEnabled,
|
||||
QWebSettings.OfflineStorageDatabaseEnabled],
|
||||
'content.developer_extras':
|
||||
[QWebSettings.DeveloperExtrasEnabled],
|
||||
'content.print_element_backgrounds':
|
||||
Attr(QWebSettings.PrintElementBackgrounds),
|
||||
[QWebSettings.PrintElementBackgrounds],
|
||||
'content.xss_auditing':
|
||||
Attr(QWebSettings.XSSAuditingEnabled),
|
||||
[QWebSettings.XSSAuditingEnabled],
|
||||
|
||||
'input.spatial_navigation':
|
||||
Attr(QWebSettings.SpatialNavigationEnabled),
|
||||
[QWebSettings.SpatialNavigationEnabled],
|
||||
'input.links_included_in_focus_chain':
|
||||
Attr(QWebSettings.LinksIncludedInFocusChain),
|
||||
[QWebSettings.LinksIncludedInFocusChain],
|
||||
|
||||
'zoom.text_only':
|
||||
Attr(QWebSettings.ZoomTextOnly),
|
||||
[QWebSettings.ZoomTextOnly],
|
||||
'scrolling.smooth':
|
||||
Attr(QWebSettings.ScrollAnimatorEnabled),
|
||||
[QWebSettings.ScrollAnimatorEnabled],
|
||||
}
|
||||
|
||||
_FONT_SIZES = {
|
||||
|
||||
@@ -23,6 +23,11 @@ import re
|
||||
import functools
|
||||
import xml.etree.ElementTree
|
||||
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
|
||||
QSize)
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
@@ -33,8 +38,7 @@ from PyQt5.QtPrintSupport import QPrinter
|
||||
from qutebrowser.browser import browsertab, shared
|
||||
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
|
||||
webkitsettings)
|
||||
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
|
||||
from qutebrowser.qt import sip
|
||||
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
|
||||
|
||||
|
||||
class WebKitAction(browsertab.AbstractAction):
|
||||
@@ -51,8 +55,28 @@ class WebKitAction(browsertab.AbstractAction):
|
||||
"""Save the current page."""
|
||||
raise browsertab.UnsupportedOperationError
|
||||
|
||||
def show_source(self, pygments=False):
|
||||
self._show_source_pygments()
|
||||
def show_source(self):
|
||||
|
||||
def show_source_cb(source):
|
||||
"""Show source as soon as it's ready."""
|
||||
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
|
||||
# pylint: disable=no-member
|
||||
lexer = pygments.lexers.HtmlLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table')
|
||||
# pylint: enable=no-member
|
||||
highlighted = pygments.highlight(source, lexer, formatter)
|
||||
|
||||
tb = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._tab.win_id)
|
||||
new_tab = tb.tabopen(background=False, related=True)
|
||||
# The original URL becomes the path of a view-source: URL
|
||||
# (without a host), but query/fragment should stay.
|
||||
url = QUrl('view-source:' + urlstr)
|
||||
new_tab.set_html(highlighted, url)
|
||||
|
||||
urlstr = self._tab.url().toString(QUrl.RemoveUserInfo)
|
||||
self._tab.dump_async(show_source_cb)
|
||||
|
||||
|
||||
class WebKitPrinting(browsertab.AbstractPrinting):
|
||||
@@ -353,22 +377,11 @@ class WebKitCaret(browsertab.AbstractCaret):
|
||||
QWebSettings.JavascriptEnabled):
|
||||
if tab:
|
||||
self._tab.data.override_target = usertypes.ClickTarget.tab
|
||||
self._tab.run_js_async("""
|
||||
const aElm = document.activeElement;
|
||||
if (window.getSelection().anchorNode) {
|
||||
window.getSelection().anchorNode.parentNode.click();
|
||||
} else if (aElm && aElm !== document.body) {
|
||||
aElm.click();
|
||||
}
|
||||
""")
|
||||
self._tab.run_js_async(
|
||||
'window.getSelection().anchorNode.parentNode.click()')
|
||||
else:
|
||||
selection = self._widget.selectedHtml()
|
||||
if not selection:
|
||||
# Getting here may mean we crashed, but we can't do anything
|
||||
# about that until this commit is released:
|
||||
# https://github.com/annulen/webkit/commit/0e75f3272d149bc64899c161f150eb341a2417af
|
||||
# TODO find a way to check if something is focused
|
||||
self._follow_enter(tab)
|
||||
return
|
||||
try:
|
||||
selected_element = xml.etree.ElementTree.fromstring(
|
||||
@@ -627,20 +640,6 @@ class WebKitElements(browsertab.AbstractElements):
|
||||
callback(elem)
|
||||
|
||||
|
||||
class WebKitAudio(browsertab.AbstractAudio):
|
||||
|
||||
"""Dummy handling of audio status for QtWebKit."""
|
||||
|
||||
def set_muted(self, muted: bool):
|
||||
raise browsertab.WebTabError('Muting is not supported on QtWebKit!')
|
||||
|
||||
def is_muted(self):
|
||||
return False
|
||||
|
||||
def is_recently_audible(self):
|
||||
return False
|
||||
|
||||
|
||||
class WebKitTab(browsertab.AbstractTab):
|
||||
|
||||
"""A QtWebKit tab in the browser."""
|
||||
@@ -661,7 +660,6 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
self.printing = WebKitPrinting()
|
||||
self.elements = WebKitElements(tab=self)
|
||||
self.action = WebKitAction(tab=self)
|
||||
self.audio = WebKitAudio(parent=self)
|
||||
# We're assigning settings in _set_widget
|
||||
self.settings = webkitsettings.WebKitSettings(settings=None)
|
||||
self._set_widget(widget)
|
||||
@@ -808,10 +806,6 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
if navigation.is_main_frame:
|
||||
self.settings.update_for_url(navigation.url)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
def _connect_signals(self):
|
||||
view = self._widget
|
||||
page = view.page()
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import html
|
||||
import functools
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
@@ -34,7 +35,6 @@ from qutebrowser.browser import pdfjs, shared
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.utils import message, usertypes, log, jinja, objreg
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
class BrowserPage(QWebPage):
|
||||
@@ -212,8 +212,7 @@ class BrowserPage(QWebPage):
|
||||
page = pdfjs.generate_pdfjs_page(reply.url())
|
||||
except pdfjs.PDFJSNotFound:
|
||||
page = jinja.render('no_pdfjs.html',
|
||||
url=reply.url().toDisplayString(),
|
||||
title="PDF.js not found")
|
||||
url=reply.url().toDisplayString())
|
||||
self.mainFrame().setContent(page.encode('utf-8'), 'text/html',
|
||||
reply.url())
|
||||
reply.deleteLater()
|
||||
@@ -416,7 +415,7 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def userAgentForUrl(self, url):
|
||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||
ua = config.instance.get('content.headers.user_agent', url=url)
|
||||
ua = config.val.content.headers.user_agent
|
||||
if ua is None:
|
||||
return super().userAgentForUrl(url)
|
||||
else:
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
"""The main browser widgets."""
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt, QUrl
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtWidgets import QStyleFactory
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
@@ -78,6 +78,10 @@ class WebView(QWebView):
|
||||
|
||||
self.setPage(page)
|
||||
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=win_id)
|
||||
mode_manager.entered.connect(self.on_mode_entered)
|
||||
mode_manager.left.connect(self.on_mode_left)
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -126,6 +130,32 @@ class WebView(QWebView):
|
||||
"""
|
||||
self.load(url)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(self, mode):
|
||||
"""Ignore attempts to focus the widget if in any status-input mode.
|
||||
|
||||
FIXME:qtwebengine
|
||||
For QtWebEngine, doing the same has no effect, so we do it in here.
|
||||
"""
|
||||
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno]:
|
||||
log.webview.debug("Ignoring focus because mode {} was "
|
||||
"entered.".format(mode))
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
"""Restore focus policy if status-input modes were left.
|
||||
|
||||
FIXME:qtwebengine
|
||||
For QtWebEngine, doing the same has no effect, so we do it in here.
|
||||
"""
|
||||
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno]:
|
||||
log.webview.debug("Restoring focus policy because mode {} was "
|
||||
"left.".format(mode))
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
|
||||
def createWindow(self, wintype):
|
||||
"""Called by Qt when a page wants to create a new window.
|
||||
|
||||
|
||||
@@ -123,7 +123,6 @@ class Command:
|
||||
self.pos_args = []
|
||||
self.desc = None
|
||||
self.flags_with_args = []
|
||||
self._has_vararg = False
|
||||
|
||||
# This is checked by future @cmdutils.argument calls so they fail
|
||||
# (as they'd be silently ignored otherwise)
|
||||
@@ -171,8 +170,6 @@ class Command:
|
||||
|
||||
def get_pos_arg_info(self, pos):
|
||||
"""Get an ArgInfo tuple for the given positional parameter."""
|
||||
if pos >= len(self.pos_args) and self._has_vararg:
|
||||
pos = len(self.pos_args) - 1
|
||||
name = self.pos_args[pos][0]
|
||||
return self._qute_args.get(name, ArgInfo())
|
||||
|
||||
@@ -236,8 +233,6 @@ class Command:
|
||||
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
||||
param.name, typ, callsig))
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
||||
self._has_vararg = True
|
||||
return signature.parameters.values()
|
||||
|
||||
def _param_to_argparse_kwargs(self, param, is_bool):
|
||||
|
||||
@@ -49,7 +49,7 @@ class Completer(QObject):
|
||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||
updates.
|
||||
_last_text: The old command text so we avoid double completion updates.
|
||||
_last_before_cursor: The prior value of before_cursor.
|
||||
_last_completion_func: The completion function used for the last text.
|
||||
"""
|
||||
|
||||
def __init__(self, *, cmd, win_id, parent=None):
|
||||
@@ -62,7 +62,7 @@ class Completer(QObject):
|
||||
self._timer.timeout.connect(self._update_completion)
|
||||
self._last_cursor_pos = -1
|
||||
self._last_text = None
|
||||
self._last_before_cursor = None
|
||||
self._last_completion_func = None
|
||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -228,7 +228,7 @@ class Completer(QObject):
|
||||
# FIXME complete searches
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/32
|
||||
completion.set_model(None)
|
||||
self._last_before_cursor = None
|
||||
self._last_completion_func = None
|
||||
return
|
||||
|
||||
before_cursor, pattern, after_cursor = self._partition()
|
||||
@@ -242,11 +242,11 @@ class Completer(QObject):
|
||||
if func is None:
|
||||
log.completion.debug('Clearing completion')
|
||||
completion.set_model(None)
|
||||
self._last_before_cursor = None
|
||||
self._last_completion_func = None
|
||||
return
|
||||
|
||||
if before_cursor != self._last_before_cursor:
|
||||
self._last_before_cursor = before_cursor
|
||||
if func != self._last_completion_func:
|
||||
self._last_completion_func = func
|
||||
args = (x for x in before_cursor[1:] if not x.startswith('-'))
|
||||
with debug.log_time(log.completion, 'Starting {} completion'
|
||||
.format(func.__name__)):
|
||||
|
||||
@@ -47,12 +47,12 @@ def customized_option(*, info):
|
||||
return model
|
||||
|
||||
|
||||
def value(optname, *values, info):
|
||||
def value(optname, *_values, info):
|
||||
"""A CompletionModel filled with setting values.
|
||||
|
||||
Args:
|
||||
optname: The name of the config option this model shows.
|
||||
values: The values already provided on the command line.
|
||||
_values: The values already provided on the command line.
|
||||
info: A CompletionInfo instance.
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
@@ -64,18 +64,13 @@ def value(optname, *values, info):
|
||||
|
||||
opt = info.config.get_opt(optname)
|
||||
default = opt.typ.to_str(opt.default)
|
||||
cur_def = []
|
||||
if current not in values:
|
||||
cur_def.append((current, "Current value"))
|
||||
if default not in values:
|
||||
cur_def.append((default, "Default value"))
|
||||
if cur_def:
|
||||
cur_cat = listcategory.ListCategory("Current/Default", cur_def)
|
||||
model.add_category(cur_cat)
|
||||
cur_cat = listcategory.ListCategory(
|
||||
"Current/Default",
|
||||
[(current, "Current value"), (default, "Default value")])
|
||||
model.add_category(cur_cat)
|
||||
|
||||
vals = opt.typ.complete() or []
|
||||
vals = [x for x in vals if x[0] not in values]
|
||||
if vals:
|
||||
vals = opt.typ.complete()
|
||||
if vals is not None:
|
||||
model.add_category(listcategory.ListCategory("Completions", vals))
|
||||
return model
|
||||
|
||||
|
||||
@@ -276,8 +276,7 @@ class Config(QObject):
|
||||
"""Set the given option to the given value."""
|
||||
if not isinstance(objects.backend, objects.NoBackend):
|
||||
if objects.backend not in opt.backends:
|
||||
raise configexc.BackendError(opt.name, objects.backend,
|
||||
opt.raw_backends)
|
||||
raise configexc.BackendError(opt.name, objects.backend)
|
||||
|
||||
opt.typ.to_py(value) # for validation
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ def _parse_yaml_type(name, node):
|
||||
|
||||
try:
|
||||
typ = getattr(configtypes, type_name)
|
||||
except AttributeError:
|
||||
except AttributeError as e:
|
||||
raise AttributeError("Did not find type {} for {}".format(
|
||||
type_name, name))
|
||||
|
||||
@@ -149,9 +149,6 @@ def _parse_yaml_backends_dict(name, node):
|
||||
False: False,
|
||||
'Qt 5.8': qtutils.version_check('5.8'),
|
||||
'Qt 5.9': qtutils.version_check('5.9'),
|
||||
'Qt 5.9.2': qtutils.version_check('5.9.2'),
|
||||
'Qt 5.10': qtutils.version_check('5.10'),
|
||||
'Qt 5.11': qtutils.version_check('5.11'),
|
||||
}
|
||||
for key in sorted(node.keys()):
|
||||
if conditionals[node[key]]:
|
||||
|
||||
@@ -150,24 +150,14 @@ force_software_rendering:
|
||||
renamed: qt.force_software_rendering
|
||||
|
||||
qt.force_software_rendering:
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- software-opengl: Tell LibGL to use a software implementation of GL
|
||||
(`LIBGL_ALWAYS_SOFTWARE` / `QT_XCB_FORCE_SOFTWARE_OPENGL`)
|
||||
- qt-quick: Tell Qt Quick to use a software renderer instead of OpenGL.
|
||||
(`QT_QUICK_BACKEND=software`)
|
||||
- chromium: Tell Chromium to disable GPU support and use Skia software
|
||||
rendering instead. (`--disable-gpu`)
|
||||
- none: Don't force software rendering.
|
||||
default: none
|
||||
type: Bool
|
||||
default: false
|
||||
backend: QtWebEngine
|
||||
restart: true
|
||||
desc: >-
|
||||
Force software rendering for QtWebEngine.
|
||||
|
||||
This is needed for QtWebEngine to work with Nouveau drivers and can be
|
||||
useful in other scenarios related to graphic issues.
|
||||
This is needed for QtWebEngine to work with Nouveau drivers.
|
||||
|
||||
qt.force_platform:
|
||||
type:
|
||||
@@ -214,17 +204,6 @@ auto_save.session:
|
||||
|
||||
## content
|
||||
|
||||
content.autoplay:
|
||||
default: true
|
||||
type: Bool
|
||||
backend:
|
||||
QtWebEngine: Qt 5.10
|
||||
QtWebKit: false
|
||||
desc: >-
|
||||
Automatically start playing `<video>` elements.
|
||||
|
||||
Note this option needs a restart with QtWebEngine on Qt < 5.11.
|
||||
|
||||
content.cache.size:
|
||||
default: null
|
||||
type:
|
||||
@@ -237,16 +216,6 @@ content.cache.size:
|
||||
|
||||
With QtWebEngine, the maximum supported value is 2147483647 (~2 GB).
|
||||
|
||||
content.canvas_reading:
|
||||
default: true
|
||||
type: Bool
|
||||
backend: QtWebEngine
|
||||
restart: true
|
||||
desc: >-
|
||||
Allow websites to read canvas elements.
|
||||
|
||||
Note this is needed for some websites to work properly.
|
||||
|
||||
# Defaults from QWebSettings::QWebSettings() in
|
||||
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
|
||||
|
||||
@@ -281,17 +250,14 @@ content.cache.appcache:
|
||||
|
||||
content.cookies.accept:
|
||||
default: no-3rdparty
|
||||
backend:
|
||||
QtWebKit: true
|
||||
QtWebEngine: Qt 5.11
|
||||
backend: QtWebKit
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
- all: "Accept all cookies."
|
||||
- no-3rdparty: "Accept cookies from the same origin only."
|
||||
- no-unknown-3rdparty: "Accept cookies from the same origin only, unless
|
||||
a cookie is already set for the domain. On QtWebEngine, this is the
|
||||
same as no-3rdparty."
|
||||
a cookie is already set for the domain."
|
||||
- never: "Don't accept cookies at all."
|
||||
desc: Which cookies to accept.
|
||||
|
||||
@@ -318,18 +284,16 @@ content.windowed_fullscreen:
|
||||
desc: >-
|
||||
Limit fullscreen to the browser window (does not expand to fill the screen).
|
||||
|
||||
content.desktop_capture:
|
||||
type: BoolAsk
|
||||
default: ask
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
Allow websites to share screen content.
|
||||
|
||||
On Qt < 5.10, a dialog box is always displayed, even if this is set to
|
||||
"true".
|
||||
|
||||
content.developer_extras:
|
||||
deleted: true
|
||||
type: Bool
|
||||
default: false
|
||||
backend: QtWebKit
|
||||
desc: >-
|
||||
Enable extra tools for Web developers.
|
||||
|
||||
This needs to be enabled for `:inspector` to work and also adds an
|
||||
_Inspect_ entry to the context menu. For QtWebEngine, see
|
||||
`--enable-webengine-inspector` in `qutebrowser --help` instead.
|
||||
|
||||
content.dns_prefetch:
|
||||
default: true
|
||||
@@ -351,19 +315,14 @@ content.frame_flattening:
|
||||
content.geolocation:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
desc: Allow websites to request geolocations.
|
||||
|
||||
content.headers.accept_language:
|
||||
type:
|
||||
name: String
|
||||
none_ok: true
|
||||
supports_pattern: true
|
||||
default: en-US,en
|
||||
desc: >-
|
||||
Value to send in the `Accept-Language` header.
|
||||
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
desc: Value to send in the `Accept-Language` header.
|
||||
|
||||
content.headers.custom:
|
||||
default: {}
|
||||
@@ -376,7 +335,6 @@ content.headers.custom:
|
||||
name: String
|
||||
encoding: ascii
|
||||
none_ok: true
|
||||
supports_pattern: true
|
||||
desc: Custom headers for qutebrowser HTTP requests.
|
||||
|
||||
content.headers.do_not_track:
|
||||
@@ -384,7 +342,6 @@ content.headers.do_not_track:
|
||||
name: Bool
|
||||
none_ok: true
|
||||
default: true
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
Value to send in the `DNT` header.
|
||||
|
||||
@@ -459,11 +416,7 @@ content.headers.user_agent:
|
||||
Gecko"
|
||||
- IE 11.0 for Desktop Win7 64-bit
|
||||
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
User agent to send. Unset to send the default.
|
||||
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
desc: User agent to send. Unset to send the default.
|
||||
|
||||
content.host_blocking.enabled:
|
||||
default: true
|
||||
@@ -606,7 +559,6 @@ content.local_storage:
|
||||
content.media_capture:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend: QtWebEngine
|
||||
desc: Allow websites to record audio/video.
|
||||
|
||||
@@ -623,7 +575,6 @@ content.netrc_file:
|
||||
content.notifications:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend: QtWebKit
|
||||
desc: Allow websites to show notifications.
|
||||
|
||||
@@ -637,16 +588,6 @@ content.pdfjs:
|
||||
Note that the files can still be downloaded by clicking the download button
|
||||
in the pdf.js viewer.
|
||||
|
||||
content.persistent_storage:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend:
|
||||
QtWebKit: false
|
||||
QtWebEngine: Qt 5.11
|
||||
desc: Allow websites to request persistent storage quota via
|
||||
`navigator.webkitPersistentStorage.requestQuota`.
|
||||
|
||||
content.plugins:
|
||||
default: false
|
||||
type: Bool
|
||||
@@ -684,20 +625,9 @@ content.proxy_dns_requests:
|
||||
backend: QtWebKit
|
||||
desc: Send DNS requests over the configured proxy.
|
||||
|
||||
content.register_protocol_handler:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend:
|
||||
QtWebKit: false
|
||||
QtWebEngine: Qt 5.11
|
||||
desc: Allow websites to register protocol handlers via
|
||||
`navigator.registerProtocolHandler`.
|
||||
|
||||
content.ssl_strict:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
desc: Validate SSL handshakes.
|
||||
|
||||
content.user_stylesheets:
|
||||
@@ -714,19 +644,6 @@ content.webgl:
|
||||
supports_pattern: true
|
||||
desc: Enable WebGL.
|
||||
|
||||
content.webrtc_public_interfaces_only:
|
||||
default: false
|
||||
type: Bool
|
||||
backend:
|
||||
QtWebKit: false
|
||||
QtWebEngine: Qt 5.9.2
|
||||
desc: >-
|
||||
Only expose public interfaces via WebRTC.
|
||||
|
||||
On Qt 5.9, this option requires a restart.
|
||||
On Qt 5.10, this option doesn't work at all because of a Qt bug.
|
||||
On Qt >= 5.11, no restart is required.
|
||||
|
||||
content.xss_auditing:
|
||||
type: Bool
|
||||
default: false
|
||||
@@ -1044,11 +961,6 @@ hints.uppercase:
|
||||
|
||||
## input
|
||||
|
||||
input.escape_quits_reporter:
|
||||
type: Bool
|
||||
default: True
|
||||
desc: Allow Escape to quit the crash reporter.
|
||||
|
||||
input.forward_unbound_keys:
|
||||
default: auto
|
||||
type:
|
||||
@@ -1432,7 +1344,7 @@ tabs.title.alignment:
|
||||
desc: Alignment of the text inside of tabs.
|
||||
|
||||
tabs.title.format:
|
||||
default: '{audio}{index}: {title}'
|
||||
default: '{index}: {title}'
|
||||
type:
|
||||
name: FormatString
|
||||
fields:
|
||||
@@ -1447,7 +1359,6 @@ tabs.title.format:
|
||||
- private
|
||||
- current_url
|
||||
- protocol
|
||||
- audio
|
||||
none_ok: true
|
||||
desc: |
|
||||
Format to use for the tab title.
|
||||
@@ -1465,7 +1376,6 @@ tabs.title.format:
|
||||
* `{private}`: Indicates when private mode is enabled.
|
||||
* `{current_url}`: URL of the current web page.
|
||||
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||
* `{audio}`: Indicator for audio/mute status.
|
||||
|
||||
tabs.title.format_pinned:
|
||||
default: '{index}'
|
||||
@@ -1483,7 +1393,6 @@ tabs.title.format_pinned:
|
||||
- private
|
||||
- current_url
|
||||
- protocol
|
||||
- audio
|
||||
none_ok: true
|
||||
desc: Format to use for the tab title for pinned tabs. The same placeholders
|
||||
like for `tabs.title.format` are defined.
|
||||
@@ -1651,7 +1560,6 @@ window.title_format:
|
||||
- private
|
||||
- current_url
|
||||
- protocol
|
||||
- audio
|
||||
default: '{perc}{title}{title_sep}qutebrowser'
|
||||
desc: |
|
||||
Format to use for the window title. The same placeholders like for
|
||||
@@ -2363,7 +2271,6 @@ bindings.default:
|
||||
;R: hint --rapid links window
|
||||
;d: hint links download
|
||||
;t: hint inputs
|
||||
gi: hint inputs --first
|
||||
h: scroll left
|
||||
j: scroll down
|
||||
k: scroll up
|
||||
@@ -2422,7 +2329,6 @@ bindings.default:
|
||||
gf: view-source
|
||||
gt: set-cmd-text -s :buffer
|
||||
<Ctrl-Tab>: tab-focus last
|
||||
<Ctrl-Shift-Tab>: nop
|
||||
<Ctrl-^>: tab-focus last
|
||||
<Ctrl-V>: enter-mode passthrough
|
||||
<Ctrl-Q>: quit
|
||||
@@ -2455,7 +2361,6 @@ bindings.default:
|
||||
<Ctrl-Return>: follow-selected -t
|
||||
.: repeat-command
|
||||
<Ctrl-p>: tab-pin
|
||||
<Alt-m>: tab-mute
|
||||
q: record-macro
|
||||
"@": run-macro
|
||||
tsh: config-cycle -p -t -u *://{url:host}/* content.javascript.enabled ;; reload
|
||||
|
||||
@@ -44,15 +44,9 @@ class BackendError(Error):
|
||||
|
||||
"""Raised when this setting is unavailable with the current backend."""
|
||||
|
||||
def __init__(self, name, backend, raw_backends):
|
||||
if raw_backends is None or not raw_backends[backend.name]:
|
||||
msg = ("The {} setting is not available with the {} backend!"
|
||||
.format(name, backend.name))
|
||||
else:
|
||||
msg = ("The {} setting needs {} with the {} backend!"
|
||||
.format(name, raw_backends[backend.name], backend.name))
|
||||
|
||||
super().__init__(msg)
|
||||
def __init__(self, name, backend):
|
||||
super().__init__("The {} setting is not available with the {} "
|
||||
"backend!".format(name, backend.name))
|
||||
|
||||
|
||||
class NoPatternError(Error):
|
||||
|
||||
@@ -233,14 +233,6 @@ class YamlConfig(QObject):
|
||||
if errors:
|
||||
raise configexc.ConfigFileErrors('autoconfig.yml', errors)
|
||||
|
||||
def _migrate_bool(self, settings, name, true_value, false_value):
|
||||
"""Migrate a boolean in the settings."""
|
||||
if name in settings:
|
||||
for scope, val in settings[name].items():
|
||||
if isinstance(val, bool):
|
||||
settings[name][scope] = true_value if val else false_value
|
||||
self._mark_changed()
|
||||
|
||||
def _handle_migrations(self, settings):
|
||||
"""Migrate older configs to the newest format."""
|
||||
# Simple renamed/deleted options
|
||||
@@ -276,9 +268,14 @@ class YamlConfig(QObject):
|
||||
del settings['bindings.default']
|
||||
self._mark_changed()
|
||||
|
||||
self._migrate_bool(settings, 'tabs.favicons.show', 'always', 'never')
|
||||
self._migrate_bool(settings, 'qt.force_software_rendering',
|
||||
'software-opengl', 'none')
|
||||
# Option to show favicons only for pinned tabs changed the type of
|
||||
# tabs.favicons.show from Bool to String
|
||||
name = 'tabs.favicons.show'
|
||||
if name in settings:
|
||||
for scope, val in settings[name].items():
|
||||
if isinstance(val, bool):
|
||||
settings[name][scope] = 'always' if val else 'never'
|
||||
self._mark_changed()
|
||||
|
||||
return settings
|
||||
|
||||
@@ -576,7 +573,7 @@ def read_autoconfig():
|
||||
"""Read the autoconfig.yml file."""
|
||||
try:
|
||||
config.instance.read_yaml()
|
||||
except configexc.ConfigFileErrors:
|
||||
except configexc.ConfigFileErrors as e:
|
||||
raise # caught in outer block
|
||||
except configexc.Error as e:
|
||||
desc = configexc.ConfigErrorDesc("Error", e)
|
||||
|
||||
@@ -83,12 +83,9 @@ def early_init(args):
|
||||
|
||||
def _init_envvars():
|
||||
"""Initialize environment variables which need to be set early."""
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
software_rendering = config.val.qt.force_software_rendering
|
||||
if software_rendering == 'software-opengl':
|
||||
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
|
||||
elif software_rendering == 'qt-quick':
|
||||
os.environ['QT_QUICK_BACKEND'] = 'software'
|
||||
if (objects.backend == usertypes.Backend.QtWebEngine and
|
||||
config.val.qt.force_software_rendering):
|
||||
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
|
||||
|
||||
if config.val.qt.force_platform is not None:
|
||||
os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform
|
||||
@@ -166,25 +163,11 @@ def qt_args(namespace):
|
||||
|
||||
argv += ['--' + arg for arg in config.val.qt.args]
|
||||
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
if not qtutils.version_check('5.11', compiled=False):
|
||||
# WORKAROUND equivalent to
|
||||
# https://codereview.qt-project.org/#/c/217932/
|
||||
# Needed for Qt < 5.9.5 and < 5.10.1
|
||||
argv.append('--disable-shared-workers')
|
||||
|
||||
if config.val.qt.force_software_rendering == 'chromium':
|
||||
argv.append('--disable-gpu')
|
||||
|
||||
if not config.val.content.canvas_reading:
|
||||
argv.append('--disable-reading-from-canvas')
|
||||
|
||||
if not qtutils.version_check('5.11'):
|
||||
# On Qt 5.11, we can control this via QWebEngineSettings
|
||||
if not config.val.content.autoplay:
|
||||
argv.append('--autoplay-policy=user-gesture-required')
|
||||
if config.val.content.webrtc_public_interfaces_only:
|
||||
argv.append('--force-webrtc-ip-handling-policy='
|
||||
'default_public_interface_only')
|
||||
if (objects.backend == usertypes.Backend.QtWebEngine and
|
||||
not qtutils.version_check('5.11', compiled=False)):
|
||||
# WORKAROUND equivalent to
|
||||
# https://codereview.qt-project.org/#/c/217932/
|
||||
# Needed for Qt < 5.9.5 and < 5.10.1
|
||||
argv.append('--disable-shared-workers')
|
||||
|
||||
return argv
|
||||
|
||||
@@ -1409,7 +1409,7 @@ class SearchEngineUrl(BaseType):
|
||||
|
||||
try:
|
||||
value.format("")
|
||||
except (KeyError, IndexError):
|
||||
except (KeyError, IndexError) as e:
|
||||
raise configexc.ValidationError(
|
||||
value, "may not contain {...} (use {{ and }} for literal {/})")
|
||||
except ValueError as e:
|
||||
|
||||
@@ -28,18 +28,6 @@ from qutebrowser.misc import objects
|
||||
UNSET = object()
|
||||
|
||||
|
||||
class AttributeInfo:
|
||||
|
||||
"""Info about a settings attribute."""
|
||||
|
||||
def __init__(self, *attributes, converter=None):
|
||||
self.attributes = attributes
|
||||
if converter is None:
|
||||
self.converter = lambda val: val
|
||||
else:
|
||||
self.converter = converter
|
||||
|
||||
|
||||
class AbstractSettings:
|
||||
|
||||
"""Abstract base class for settings set via QWeb(Engine)Settings."""
|
||||
@@ -62,13 +50,12 @@ class AbstractSettings:
|
||||
"""
|
||||
old_value = self.test_attribute(name)
|
||||
|
||||
info = self._ATTRIBUTES[name]
|
||||
for attribute in info.attributes:
|
||||
for attribute in self._ATTRIBUTES[name]:
|
||||
if value is configutils.UNSET:
|
||||
self._settings.resetAttribute(attribute)
|
||||
new_value = self.test_attribute(name)
|
||||
else:
|
||||
self._settings.setAttribute(attribute, info.converter(value))
|
||||
self._settings.setAttribute(attribute, value)
|
||||
new_value = value
|
||||
|
||||
return old_value != new_value
|
||||
@@ -79,8 +66,7 @@ class AbstractSettings:
|
||||
If the setting resolves to a list of attributes, only the first
|
||||
attribute is tested.
|
||||
"""
|
||||
info = self._ATTRIBUTES[name]
|
||||
return self._settings.testAttribute(info.attributes[0])
|
||||
return self._settings.testAttribute(self._ATTRIBUTES[name][0])
|
||||
|
||||
def set_font_size(self, name, value):
|
||||
"""Set the given QWebSettings/QWebEngineSettings font size.
|
||||
|
||||
@@ -56,5 +56,3 @@ rules:
|
||||
no-bitwise: "off"
|
||||
no-ternary: "off"
|
||||
max-lines: "off"
|
||||
multiline-ternary: ["error", "always-multiline"]
|
||||
max-lines-per-function: "off"
|
||||
|
||||
@@ -780,18 +780,6 @@ window._qutebrowser.caret = (function() {
|
||||
*/
|
||||
CaretBrowsing.blinkFlag = true;
|
||||
|
||||
/**
|
||||
* Whether we're running on Windows.
|
||||
* @type {boolean}
|
||||
*/
|
||||
CaretBrowsing.isWindows = null;
|
||||
|
||||
/**
|
||||
* Whether we're running on on old Qt 5.7.1.
|
||||
* @type {boolean}
|
||||
*/
|
||||
CaretBrowsing.isOldQt = null;
|
||||
|
||||
/**
|
||||
* Check if a node is a control that normally allows the user to interact
|
||||
* with it using arrow keys. We won't override the arrow keys when such a
|
||||
@@ -868,24 +856,12 @@ window._qutebrowser.caret = (function() {
|
||||
};
|
||||
|
||||
CaretBrowsing.injectCaretStyles = function() {
|
||||
const prefix = CaretBrowsing.isOldQt ? "-webkit-" : "";
|
||||
const style = `
|
||||
.CaretBrowsing_Caret {
|
||||
position: absolute;
|
||||
z-index: 2147483647;
|
||||
min-height: 1em;
|
||||
min-width: 0.2em;
|
||||
animation: blink 1s step-end infinite;
|
||||
--inherited-color: inherit;
|
||||
background-color: var(--inherited-color, #000);
|
||||
color: var(--inherited-color, #000);
|
||||
mix-blend-mode: difference;
|
||||
${prefix}filter: invert(85%);
|
||||
}
|
||||
@keyframes blink {
|
||||
50% { visibility: hidden; }
|
||||
}
|
||||
`;
|
||||
const style = ".CaretBrowsing_Caret {" +
|
||||
" position: absolute;" +
|
||||
" z-index: 2147483647;" +
|
||||
" min-height: 10px;" +
|
||||
" background-color: #000;" +
|
||||
"}";
|
||||
const node = document.createElement("style");
|
||||
node.innerHTML = style;
|
||||
document.body.appendChild(node);
|
||||
@@ -1182,8 +1158,6 @@ window._qutebrowser.caret = (function() {
|
||||
CaretBrowsing.updateCaretOrSelection(true);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
CaretBrowsing.stopAnimation();
|
||||
};
|
||||
|
||||
CaretBrowsing.moveToBlock = function(paragraph, boundary) {
|
||||
@@ -1202,8 +1176,6 @@ window._qutebrowser.caret = (function() {
|
||||
window.setTimeout(() => {
|
||||
CaretBrowsing.updateCaretOrSelection(true);
|
||||
}, 0);
|
||||
|
||||
CaretBrowsing.stopAnimation();
|
||||
};
|
||||
|
||||
CaretBrowsing.toggle = function(value) {
|
||||
@@ -1270,17 +1242,6 @@ window._qutebrowser.caret = (function() {
|
||||
CaretBrowsing.updateIsCaretVisible();
|
||||
};
|
||||
|
||||
CaretBrowsing.startAnimation = function() {
|
||||
CaretBrowsing.caretElement.style.animationIterationCount = "infinite";
|
||||
};
|
||||
|
||||
CaretBrowsing.stopAnimation = function() {
|
||||
CaretBrowsing.caretElement.style.animationIterationCount = 0;
|
||||
window.setTimeout(() => {
|
||||
CaretBrowsing.startAnimation();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
CaretBrowsing.init = function() {
|
||||
CaretBrowsing.isWindowFocused = document.hasFocus();
|
||||
|
||||
@@ -1318,9 +1279,8 @@ window._qutebrowser.caret = (function() {
|
||||
return CaretBrowsing.selectionEnabled;
|
||||
};
|
||||
|
||||
funcs.setPlatform = (platform, qtVersion) => {
|
||||
funcs.setPlatform = (platform) => {
|
||||
CaretBrowsing.isWindows = platform.startsWith("win");
|
||||
CaretBrowsing.isOldQt = qtVersion === "5.7.1";
|
||||
};
|
||||
|
||||
funcs.disableCaret = () => {
|
||||
|
||||
@@ -145,59 +145,9 @@
|
||||
}
|
||||
};
|
||||
|
||||
{% if use_proxy %}
|
||||
/*
|
||||
* Try to give userscripts an environment that they expect. Which
|
||||
* seems to be that the global window object should look the same as
|
||||
* the page's one and that if a script writes to an attribute of
|
||||
* window it should be able to access that variable in the global
|
||||
* scope.
|
||||
* Use a Proxy to stop scripts from actually changing the global
|
||||
* window (that's what unsafeWindow is for).
|
||||
* Use the "with" statement to make the proxy provide what looks
|
||||
* like global scope.
|
||||
*
|
||||
* There are other Proxy functions that we may need to override.
|
||||
* set, get and has are definitely required.
|
||||
*/
|
||||
const unsafeWindow = window;
|
||||
const qute_gm_window_shadow = {}; // stores local changes to window
|
||||
const qute_gm_windowProxyHandler = {
|
||||
get: function(target, prop) {
|
||||
if (prop in qute_gm_window_shadow)
|
||||
return qute_gm_window_shadow[prop];
|
||||
if (prop in target) {
|
||||
if (typeof target[prop] === 'function' && typeof target[prop].prototype == 'undefined')
|
||||
// Getting TypeError: Illegal Execution when callers try to execute
|
||||
// eg addEventListener from here because they were returned
|
||||
// unbound
|
||||
return target[prop].bind(target);
|
||||
return target[prop];
|
||||
}
|
||||
},
|
||||
set: function(target, prop, val) {
|
||||
return qute_gm_window_shadow[prop] = val;
|
||||
},
|
||||
has: function(target, key) {
|
||||
return key in qute_gm_window_shadow || key in target;
|
||||
}
|
||||
};
|
||||
const qute_gm_window_proxy = new Proxy(
|
||||
unsafeWindow, qute_gm_windowProxyHandler);
|
||||
const unsafeWindow = window;
|
||||
|
||||
with (qute_gm_window_proxy) {
|
||||
// We can't return `this` or `qute_gm_window_proxy` from
|
||||
// `qute_gm_window_proxy.get('window')` because the Proxy implementation
|
||||
// does typechecking on read-only things. So we have to shadow `window`
|
||||
// more conventionally here.
|
||||
const window = qute_gm_window_proxy;
|
||||
// ====== The actual user script source ====== //
|
||||
// ====== The actual user script source ====== //
|
||||
{{ scriptSource }}
|
||||
// ====== End User Script ====== //
|
||||
};
|
||||
{% else %}
|
||||
// ====== The actual user script source ====== //
|
||||
{{ scriptSource }}
|
||||
// ====== End User Script ====== //
|
||||
{% endif %}
|
||||
// ====== End User Script ====== //
|
||||
})();
|
||||
|
||||
@@ -114,7 +114,7 @@ window.loadHistory = (function() {
|
||||
title.className = "title";
|
||||
const link = document.createElement("a");
|
||||
link.href = itemUrl;
|
||||
link.innerHTML = itemTitle; // Properly escaped in qutescheme.py
|
||||
link.innerHTML = itemTitle;
|
||||
const host = document.createElement("span");
|
||||
host.className = "hostname";
|
||||
host.innerHTML = link.hostname;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright 2018 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* this is a hack based on the QupZilla solution, https://github.com/QupZilla/qupzilla/commit/d3f0d766fb052dc504de2426d42f235d96b5eb60
|
||||
*
|
||||
* We go to a qute://print which triggers the print, then we cancel the request.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
window.print = function() {
|
||||
window.location = "qute://print";
|
||||
};
|
||||
@@ -243,7 +243,6 @@ window._qutebrowser.webelem = (function() {
|
||||
};
|
||||
|
||||
// Runs a function in a frame until the result is not null, then return
|
||||
// If no frame succeds, return null
|
||||
function run_frames(func) {
|
||||
for (let i = 0; i < window.frames.length; ++i) {
|
||||
const frame = window.frames[i];
|
||||
@@ -329,10 +328,8 @@ window._qutebrowser.webelem = (function() {
|
||||
return serialize_elem(elem);
|
||||
};
|
||||
|
||||
// Function for returning a selection or focus to python (so we can click
|
||||
// it). If nothing is selected but there is something focused, returns
|
||||
// "focused"
|
||||
funcs.find_selected_focused_link = () => {
|
||||
// Function for returning a selection to python (so we can click it)
|
||||
funcs.find_selected_link = () => {
|
||||
const elem = window.getSelection().baseNode;
|
||||
if (elem) {
|
||||
return serialize_elem(elem.parentNode);
|
||||
@@ -345,11 +342,7 @@ window._qutebrowser.webelem = (function() {
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
if (serialized_frame_elem) {
|
||||
return serialized_frame_elem;
|
||||
}
|
||||
return funcs.find_focused() && "focused";
|
||||
return serialized_frame_elem;
|
||||
};
|
||||
|
||||
funcs.set_value = (id, value) => {
|
||||
|
||||
@@ -138,37 +138,6 @@ def _key_to_string(key):
|
||||
'Dead_Hook': 'Hook',
|
||||
'Dead_Horn': 'Horn',
|
||||
|
||||
'Dead_Stroke': '̵',
|
||||
'Dead_Abovecomma': '̓',
|
||||
'Dead_Abovereversedcomma': '̔',
|
||||
'Dead_Doublegrave': '̏',
|
||||
'Dead_Belowring': '̥',
|
||||
'Dead_Belowmacron': '̱',
|
||||
'Dead_Belowcircumflex': '̭',
|
||||
'Dead_Belowtilde': '̰',
|
||||
'Dead_Belowbreve': '̮',
|
||||
'Dead_Belowdiaeresis': '̤',
|
||||
'Dead_Invertedbreve': '̑',
|
||||
'Dead_Belowcomma': '̦',
|
||||
'Dead_Currency': '¤',
|
||||
'Dead_a': 'a',
|
||||
'Dead_A': 'A',
|
||||
'Dead_e': 'e',
|
||||
'Dead_E': 'E',
|
||||
'Dead_i': 'i',
|
||||
'Dead_I': 'I',
|
||||
'Dead_o': 'o',
|
||||
'Dead_O': 'O',
|
||||
'Dead_u': 'u',
|
||||
'Dead_U': 'U',
|
||||
'Dead_Small_Schwa': 'ə',
|
||||
'Dead_Capital_Schwa': 'Ə',
|
||||
'Dead_Greek': 'Greek',
|
||||
'Dead_Lowline': '̲',
|
||||
'Dead_Aboveverticalline': '̍',
|
||||
'Dead_Belowverticalline': '\u0329',
|
||||
'Dead_Longsolidusoverlay': '̸',
|
||||
|
||||
'Memo': 'Memo',
|
||||
'ToDoList': 'To Do List',
|
||||
'Calendar': 'Calendar',
|
||||
|
||||
@@ -30,9 +30,6 @@ from qutebrowser.config import config
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
|
||||
INPUT_MODES = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough]
|
||||
PROMPT_MODES = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class KeyEvent:
|
||||
@@ -124,7 +121,6 @@ class ModeManager(QObject):
|
||||
Attributes:
|
||||
mode: The mode we're currently in.
|
||||
_win_id: The window ID of this ModeManager
|
||||
_prev_mode: Mode before a prompt popped up
|
||||
_parsers: A dictionary of modes and their keyparsers.
|
||||
_forward_unbound_keys: If we should forward unbound keys.
|
||||
_releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was
|
||||
@@ -148,7 +144,6 @@ class ModeManager(QObject):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._parsers = {}
|
||||
self._prev_mode = usertypes.KeyMode.normal
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self._releaseevents_to_pass = set()
|
||||
|
||||
@@ -241,17 +236,13 @@ class ModeManager(QObject):
|
||||
"""
|
||||
if not isinstance(mode, usertypes.KeyMode):
|
||||
raise TypeError("Mode {} is no KeyMode member!".format(mode))
|
||||
|
||||
if mode == usertypes.KeyMode.normal:
|
||||
self.leave(self.mode, reason='enter normal: {}'.format(reason))
|
||||
return
|
||||
|
||||
log.modes.debug("Entering mode {}{}".format(
|
||||
mode, '' if reason is None else ' (reason: {})'.format(reason)))
|
||||
if mode not in self._parsers:
|
||||
raise ValueError("No keyparser for mode {}".format(mode))
|
||||
if self.mode == mode or (self.mode in PROMPT_MODES and
|
||||
mode in PROMPT_MODES):
|
||||
prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno)
|
||||
if self.mode == mode or (self.mode in prompt_modes and
|
||||
mode in prompt_modes):
|
||||
log.modes.debug("Ignoring request as we're in mode {} "
|
||||
"already.".format(self.mode))
|
||||
return
|
||||
@@ -263,12 +254,6 @@ class ModeManager(QObject):
|
||||
return
|
||||
log.modes.debug("Overriding mode {}.".format(self.mode))
|
||||
self.left.emit(self.mode, mode, self._win_id)
|
||||
|
||||
if mode in PROMPT_MODES and self.mode in INPUT_MODES:
|
||||
self._prev_mode = self.mode
|
||||
else:
|
||||
self._prev_mode = usertypes.KeyMode.normal
|
||||
|
||||
self.mode = mode
|
||||
self.entered.emit(mode, self._win_id)
|
||||
|
||||
@@ -316,9 +301,6 @@ class ModeManager(QObject):
|
||||
self.clear_keychain()
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self.left.emit(mode, self.mode, self._win_id)
|
||||
if mode in PROMPT_MODES:
|
||||
self.enter(self._prev_mode,
|
||||
reason='restore mode before {}'.format(mode.name))
|
||||
|
||||
@cmdutils.register(instance='mode-manager', name='leave-mode',
|
||||
not_modes=[usertypes.KeyMode.normal], scope='window')
|
||||
|
||||
@@ -492,7 +492,6 @@ class MainWindow(QWidget):
|
||||
self.tabbed_browser.cur_fullscreen_requested.connect(status.maybe_hide)
|
||||
|
||||
# command input / completion
|
||||
mode_manager.entered.connect(self.tabbed_browser.on_mode_entered)
|
||||
mode_manager.left.connect(self.tabbed_browser.on_mode_left)
|
||||
cmd.clear_completion_selection.connect(
|
||||
completion_obj.on_clear_completion_selection)
|
||||
|
||||
@@ -24,6 +24,7 @@ import html
|
||||
import collections
|
||||
|
||||
import attr
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex,
|
||||
QItemSelectionModel, QObject, QEventLoop)
|
||||
from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit,
|
||||
@@ -35,7 +36,6 @@ from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
prompt_queue = None
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import functools
|
||||
|
||||
import attr
|
||||
from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication
|
||||
from PyQt5.QtWidgets import QSizePolicy, QWidget
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl
|
||||
from PyQt5.QtGui import QIcon
|
||||
|
||||
@@ -238,10 +238,6 @@ class TabbedBrowser(QWidget):
|
||||
functools.partial(self.on_window_close_requested, tab))
|
||||
tab.renderer_process_terminated.connect(
|
||||
functools.partial(self._on_renderer_process_terminated, tab))
|
||||
tab.audio.muted_changed.connect(
|
||||
functools.partial(self._on_audio_changed, tab))
|
||||
tab.audio.recently_audible_changed.connect(
|
||||
functools.partial(self._on_audio_changed, tab))
|
||||
tab.new_tab_requested.connect(self.tabopen)
|
||||
if not self.private:
|
||||
web_history = objreg.get('web-history')
|
||||
@@ -462,8 +458,6 @@ class TabbedBrowser(QWidget):
|
||||
"related {}, idx {}".format(
|
||||
url, background, related, idx))
|
||||
|
||||
prev_focus = QApplication.focusWidget()
|
||||
|
||||
if (config.val.tabs.tabs_are_windows and self.widget.count() > 0 and
|
||||
not ignore_tabs_are_windows):
|
||||
window = mainwindow.MainWindow(private=self.private)
|
||||
@@ -493,22 +487,12 @@ class TabbedBrowser(QWidget):
|
||||
tab.resize(self.widget.currentWidget().size())
|
||||
self.widget.tab_index_changed.emit(self.widget.currentIndex(),
|
||||
self.widget.count())
|
||||
# Refocus webview in case we lost it by spawning a bg tab
|
||||
self.widget.currentWidget().setFocus()
|
||||
else:
|
||||
self.widget.setCurrentWidget(tab)
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
|
||||
# Still seems to be needed with Qt 5.11.1
|
||||
tab.setFocus()
|
||||
|
||||
mode = modeman.instance(self._win_id).mode
|
||||
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno]:
|
||||
# If we were in a command prompt, restore old focus
|
||||
# The above commands need to be run to switch tabs
|
||||
if prev_focus is not None:
|
||||
prev_focus.setFocus()
|
||||
|
||||
tab.show()
|
||||
self.new_tab.emit(tab, idx)
|
||||
return tab
|
||||
@@ -645,32 +629,24 @@ class TabbedBrowser(QWidget):
|
||||
if config.val.tabs.tabs_are_windows:
|
||||
self.widget.window().setWindowIcon(icon)
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(self, mode):
|
||||
"""Save input mode when tabs.mode_on_change = restore."""
|
||||
if (config.val.tabs.mode_on_change == 'restore' and
|
||||
mode in modeman.INPUT_MODES):
|
||||
tab = self.widget.currentWidget()
|
||||
if tab is not None:
|
||||
tab.data.input_mode = mode
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
"""Give focus to current tab if command mode was left."""
|
||||
widget = self.widget.currentWidget()
|
||||
if widget is None:
|
||||
return
|
||||
if mode in [usertypes.KeyMode.command] + modeman.PROMPT_MODES:
|
||||
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno]:
|
||||
widget = self.widget.currentWidget()
|
||||
log.modes.debug("Left status-input mode, focusing {!r}".format(
|
||||
widget))
|
||||
if widget is None:
|
||||
return
|
||||
widget.setFocus()
|
||||
if config.val.tabs.mode_on_change == 'restore':
|
||||
widget.data.input_mode = usertypes.KeyMode.normal
|
||||
|
||||
@pyqtSlot(int)
|
||||
def on_current_changed(self, idx):
|
||||
"""Set last-focused-tab and leave hinting mode when focus changed."""
|
||||
mode_on_change = config.val.tabs.mode_on_change
|
||||
modes_to_save = [usertypes.KeyMode.insert,
|
||||
usertypes.KeyMode.passthrough]
|
||||
if idx == -1 or self.shutting_down:
|
||||
# closing the last tab (before quitting) or shutting down
|
||||
return
|
||||
@@ -679,28 +655,26 @@ class TabbedBrowser(QWidget):
|
||||
log.webview.debug("on_current_changed got called with invalid "
|
||||
"index {}".format(idx))
|
||||
return
|
||||
if self._now_focused is not None and mode_on_change == 'restore':
|
||||
current_mode = modeman.instance(self._win_id).mode
|
||||
if current_mode not in modes_to_save:
|
||||
current_mode = usertypes.KeyMode.normal
|
||||
self._now_focused.data.input_mode = current_mode
|
||||
|
||||
log.modes.debug("Current tab changed, focusing {!r}".format(tab))
|
||||
tab.setFocus()
|
||||
|
||||
modes_to_leave = [usertypes.KeyMode.hint, usertypes.KeyMode.caret]
|
||||
|
||||
mm_instance = modeman.instance(self._win_id)
|
||||
current_mode = mm_instance.mode
|
||||
log.modes.debug("Mode before tab change: {} (mode_on_change = {})"
|
||||
.format(current_mode.name, mode_on_change))
|
||||
if mode_on_change == 'normal':
|
||||
modes_to_leave += modeman.INPUT_MODES
|
||||
if mode_on_change != 'persist':
|
||||
modes_to_leave += modes_to_save
|
||||
for mode in modes_to_leave:
|
||||
modeman.leave(self._win_id, mode, 'tab changed', maybe=True)
|
||||
if (mode_on_change == 'restore' and
|
||||
current_mode not in modeman.PROMPT_MODES):
|
||||
modeman.enter(self._win_id, tab.data.input_mode, 'restore')
|
||||
if mode_on_change == 'restore':
|
||||
modeman.enter(self._win_id, tab.data.input_mode,
|
||||
'restore input mode for tab')
|
||||
if self._now_focused is not None:
|
||||
objreg.register('last-focused-tab', self._now_focused, update=True,
|
||||
scope='window', window=self._win_id)
|
||||
log.modes.debug("Mode after tab change: {} (mode_on_change = {})"
|
||||
.format(current_mode.name, mode_on_change))
|
||||
self._now_focused = tab
|
||||
self.current_tab_changed.emit(tab)
|
||||
QTimer.singleShot(0, self._update_window_title)
|
||||
@@ -760,17 +734,6 @@ class TabbedBrowser(QWidget):
|
||||
self._update_window_title('scroll_pos')
|
||||
self.widget.update_tab_title(idx, 'scroll_pos')
|
||||
|
||||
def _on_audio_changed(self, tab, _muted):
|
||||
"""Update audio field in tab when mute or recentlyAudible changed."""
|
||||
try:
|
||||
idx = self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
self.widget.update_tab_title(idx, 'audio')
|
||||
if idx == self.widget.currentIndex():
|
||||
self._update_window_title('audio')
|
||||
|
||||
def _on_renderer_process_terminated(self, tab, status, code):
|
||||
"""Show an error when a renderer process terminated."""
|
||||
if status == browsertab.TerminationStatus.normal:
|
||||
|
||||
@@ -33,7 +33,6 @@ from PyQt5.QtGui import QIcon, QPalette, QColor
|
||||
from qutebrowser.utils import qtutils, objreg, utils, usertypes, log
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.browser import browsertab
|
||||
|
||||
|
||||
PixelMetrics = enum.IntEnum('PixelMetrics', ['icon_padding'],
|
||||
@@ -173,16 +172,6 @@ class TabWidget(QTabWidget):
|
||||
fields['perc_raw'] = tab.progress()
|
||||
fields['backend'] = objects.backend.name
|
||||
fields['private'] = ' [Private Mode] ' if tab.private else ''
|
||||
try:
|
||||
if tab.audio.is_muted():
|
||||
fields['audio'] = '[M] '
|
||||
elif tab.audio.is_recently_audible():
|
||||
fields['audio'] = '[A] '
|
||||
else:
|
||||
fields['audio'] = ''
|
||||
except browsertab.WebTabError:
|
||||
# Muting is only implemented with QtWebEngine
|
||||
fields['audio'] = ''
|
||||
|
||||
if tab.load_status() == usertypes.LoadStatus.loading:
|
||||
fields['perc'] = '[{}%] '.format(tab.progress())
|
||||
|
||||
@@ -185,7 +185,7 @@ def _handle_nouveau_graphics():
|
||||
return
|
||||
|
||||
button = _Button("Force software rendering", 'qt.force_software_rendering',
|
||||
'chromium')
|
||||
True)
|
||||
_show_dialog(
|
||||
backend=usertypes.Backend.QtWebEngine,
|
||||
because="you're using Nouveau graphics",
|
||||
@@ -194,9 +194,9 @@ def _handle_nouveau_graphics():
|
||||
"<p>This allows you to use the newer QtWebEngine backend (based "
|
||||
"on Chromium) but could have noticeable performance impact "
|
||||
"(depending on your hardware). "
|
||||
"This sets the <i>qt.force_software_rendering = 'chromium'</i> "
|
||||
"option (if you have a <i>config.py</i> file, you'll need to set "
|
||||
"this manually).</p>",
|
||||
"This sets the <i>qt.force_software_rendering = True</i> option "
|
||||
"(if you have a <i>config.py</i> file, you'll need to set this "
|
||||
"manually).</p>",
|
||||
buttons=[button],
|
||||
)
|
||||
|
||||
@@ -213,41 +213,33 @@ def _handle_wayland():
|
||||
if platform not in ['wayland', 'wayland-egl']:
|
||||
return
|
||||
|
||||
has_qt511 = qtutils.version_check('5.11', compiled=False)
|
||||
if has_qt511 and config.val.qt.force_software_rendering == 'chromium':
|
||||
return
|
||||
|
||||
buttons = []
|
||||
text = "<p>You can work around this in one of the following ways:</p>"
|
||||
|
||||
if 'DISPLAY' in os.environ:
|
||||
# XWayland is available, but QT_QPA_PLATFORM=wayland is set
|
||||
buttons.append(_Button("Force XWayland", 'qt.force_platform', 'xcb'))
|
||||
text += ("<p><b>Force Qt to use XWayland</b></p>"
|
||||
button = _Button("Force XWayland", 'qt.force_platform', 'xcb')
|
||||
_show_dialog(
|
||||
backend=usertypes.Backend.QtWebEngine,
|
||||
because="you're using Wayland",
|
||||
text="<p>There are two ways to fix this:</p>"
|
||||
"<p><b>Force Qt to use XWayland</b></p>"
|
||||
"<p>This allows you to use the newer QtWebEngine backend "
|
||||
"(based on Chromium). "
|
||||
"This sets the <i>qt.force_platform = 'xcb'</i> option "
|
||||
"(if you have a <i>config.py</i> file, you'll need to set "
|
||||
"this manually).</p>")
|
||||
"this manually).</p>",
|
||||
buttons=[button],
|
||||
)
|
||||
else:
|
||||
text.append("<p><b>Set up XWayland</b></p>"
|
||||
"<p>This allows you to use the newer QtWebEngine backend "
|
||||
"(based on Chromium). ")
|
||||
|
||||
if has_qt511:
|
||||
buttons.append(_Button("Force software rendering",
|
||||
'qt.force_software_rendering',
|
||||
'chromium'))
|
||||
text += ("<p><b>Forcing software rendering</b></p>"
|
||||
# XWayland is unavailable
|
||||
_show_dialog(
|
||||
backend=usertypes.Backend.QtWebEngine,
|
||||
because="you're using Wayland without XWayland",
|
||||
text="<p>There are two ways to fix this:</p>"
|
||||
"<p><b>Set up XWayland</b></p>"
|
||||
"<p>This allows you to use the newer QtWebEngine backend "
|
||||
"(based on Chromium) but could have noticeable performance "
|
||||
"impact (depending on your hardware). This sets the "
|
||||
"<i>qt.force_software_rendering = 'chromium'</i> option "
|
||||
"(if you have a <i>config.py</i> file, you'll need to set "
|
||||
"this manually).</p>")
|
||||
"(based on Chromium). "
|
||||
)
|
||||
|
||||
_show_dialog(backend=usertypes.Backend.QtWebEngine,
|
||||
because="you're using Wayland", text=text, buttons=buttons)
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
@attr.s
|
||||
|
||||
@@ -158,11 +158,6 @@ class _CrashDialog(QDialog):
|
||||
self._init_info_text()
|
||||
self._init_buttons()
|
||||
|
||||
def keyPressEvent(self, e):
|
||||
"""Prevent closing :report dialogs when pressing <Escape>."""
|
||||
if config.val.input.escape_quits_reporter or e.key() != Qt.Key_Escape:
|
||||
super().keyPressEvent(e)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self)
|
||||
|
||||
@@ -298,7 +293,7 @@ class _CrashDialog(QDialog):
|
||||
self._paste_text = '\n\n'.join(lines)
|
||||
try:
|
||||
user = getpass.getuser()
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
log.misc.exception("Error while getting user")
|
||||
user = 'unknown'
|
||||
try:
|
||||
|
||||
@@ -36,6 +36,7 @@ except ImportError:
|
||||
import attr
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject,
|
||||
QSocketNotifier, QTimer, QUrl)
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.misc import earlyinit, crashdialog, ipc
|
||||
@@ -205,6 +206,7 @@ class CrashHandler(QObject):
|
||||
gracefully.
|
||||
"""
|
||||
exc = (exctype, excvalue, tb)
|
||||
qapp = QApplication.instance()
|
||||
|
||||
if not self._quitter.quit_status['crash']:
|
||||
log.misc.error("ARGH, there was an exception while the crash "
|
||||
@@ -221,7 +223,14 @@ class CrashHandler(QObject):
|
||||
|
||||
if is_ignored_exception or 'pdb-postmortem' in self._args.debug_flags:
|
||||
# pdb exit, KeyboardInterrupt, ...
|
||||
sys.exit(usertypes.Exit.exception)
|
||||
status = 0 if is_ignored_exception else 2
|
||||
try:
|
||||
self._quitter.shutdown(status)
|
||||
return
|
||||
except Exception:
|
||||
log.init.exception("Error while shutting down")
|
||||
qapp.quit()
|
||||
return
|
||||
|
||||
self._quitter.quit_status['crash'] = False
|
||||
info = self._get_exception_info()
|
||||
|
||||
@@ -246,7 +246,7 @@ def configure_pyqt():
|
||||
from PyQt5.QtCore import pyqtRemoveInputHook
|
||||
pyqtRemoveInputHook()
|
||||
|
||||
from qutebrowser.qt import sip
|
||||
import sip
|
||||
try:
|
||||
# Added in sip 4.19.4
|
||||
sip.enableoverflowchecking(True)
|
||||
|
||||
@@ -498,7 +498,7 @@ def send_or_listen(args):
|
||||
server = IPCServer(socketname)
|
||||
server.listen()
|
||||
return server
|
||||
except AddressInUseError:
|
||||
except AddressInUseError as e:
|
||||
# This could be a race condition...
|
||||
log.init.debug("Got AddressInUseError, trying again.")
|
||||
time.sleep(0.5)
|
||||
|
||||
@@ -24,6 +24,7 @@ import os.path
|
||||
import itertools
|
||||
import urllib
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
import yaml
|
||||
@@ -34,7 +35,6 @@ from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.config import config, configfiles
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
default = object() # Sentinel value
|
||||
|
||||
@@ -29,6 +29,7 @@ try:
|
||||
except ImportError:
|
||||
hunter = None
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrl
|
||||
# so it's available for :debug-pyeval
|
||||
from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import
|
||||
@@ -39,7 +40,6 @@ from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.misc import consolewidget
|
||||
from qutebrowser.utils.version import pastebin_version
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
|
||||
@@ -189,10 +189,10 @@ def debug_cache_stats():
|
||||
tabbed_browser_info = tab_bar._minimum_tab_size_hint_helper.cache_info()
|
||||
# pylint: enable=protected-access
|
||||
|
||||
log.misc.info('is_valid_prefix: {}'.format(prefix_info))
|
||||
log.misc.info('_render_stylesheet: {}'.format(render_stylesheet_info))
|
||||
log.misc.info('history: {}'.format(history_info))
|
||||
log.misc.info('tab width cache: {}'.format(tabbed_browser_info))
|
||||
log.misc.debug('is_valid_prefix: {}'.format(prefix_info))
|
||||
log.misc.debug('_render_stylesheet: {}'.format(render_stylesheet_info))
|
||||
log.misc.debug('history: {}'.format(history_info))
|
||||
log.misc.debug('tab width cache: {}'.format(tabbed_browser_info))
|
||||
|
||||
|
||||
@cmdutils.register(debug=True)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2018 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/>.
|
||||
|
||||
"""Wrappers around Qt/PyQt code."""
|
||||
|
||||
# pylint: disable=unused-import
|
||||
# PyQt 5.11 comes with a bundled sip,
|
||||
# for older PyQt versions it's a separate module.
|
||||
try:
|
||||
from PyQt5 import sip
|
||||
except ImportError:
|
||||
import sip
|
||||
@@ -61,8 +61,7 @@ def get_argparser():
|
||||
"""Get the argparse parser."""
|
||||
parser = argparse.ArgumentParser(prog='qutebrowser',
|
||||
description=qutebrowser.__description__)
|
||||
parser.add_argument('-B', '--basedir', help="Base directory for all "
|
||||
"storage.")
|
||||
parser.add_argument('--basedir', help="Base directory for all storage.")
|
||||
parser.add_argument('-V', '--version', help="Show version and quit.",
|
||||
action='store_true')
|
||||
parser.add_argument('-s', '--set', help="Set a temporary setting for "
|
||||
@@ -86,9 +85,7 @@ def get_argparser():
|
||||
"that this is a SECURITY RISK and you should not "
|
||||
"visit untrusted websites with the inspector turned "
|
||||
"on. See https://bugreports.qt.io/browse/QTBUG-50725 "
|
||||
"for more details. This is not needed anymore since "
|
||||
"Qt 5.11 where the inspector is always enabled and "
|
||||
"secure.")
|
||||
"for more details.")
|
||||
|
||||
parser.add_argument('--json-args', help=argparse.SUPPRESS)
|
||||
parser.add_argument('--temp-basedir-restarted', help=argparse.SUPPRESS)
|
||||
@@ -105,7 +102,7 @@ def get_argparser():
|
||||
help="How many lines of the debug log to keep in RAM "
|
||||
"(-1: unlimited).",
|
||||
default=2000, type=int)
|
||||
debug.add_argument('-d', '--debug', help="Turn on debugging options.",
|
||||
debug.add_argument('--debug', help="Turn on debugging options.",
|
||||
action='store_true')
|
||||
debug.add_argument('--json-logging', action='store_true', help="Output log"
|
||||
" lines in JSON format (one object per line).")
|
||||
@@ -115,8 +112,8 @@ def get_argparser():
|
||||
action='store_true')
|
||||
debug.add_argument('--nowindow', action='store_true', help="Don't show "
|
||||
"the main window.")
|
||||
debug.add_argument('-T', '--temp-basedir', action='store_true', help="Use "
|
||||
"a temporary basedir.")
|
||||
debug.add_argument('--temp-basedir', action='store_true', help="Use a "
|
||||
"temporary basedir.")
|
||||
debug.add_argument('--no-err-windows', action='store_true', help="Don't "
|
||||
"show any error windows (used for tests/smoke.py).")
|
||||
debug.add_argument('--qt-arg', help="Pass an argument with a value to Qt. "
|
||||
@@ -126,9 +123,9 @@ def get_argparser():
|
||||
action='append')
|
||||
debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.",
|
||||
nargs=1, action='append')
|
||||
debug.add_argument('-D', '--debug-flag', type=debug_flag_error,
|
||||
default=[], help="Pass name of debugging feature to be"
|
||||
" turned on.", action='append', dest='debug_flags')
|
||||
debug.add_argument('--debug-flag', type=debug_flag_error, default=[],
|
||||
help="Pass name of debugging feature to be turned on.",
|
||||
action='append', dest='debug_flags')
|
||||
parser.add_argument('command', nargs='*', help="Commands to execute on "
|
||||
"startup.", metavar=':command')
|
||||
# URLs will actually be in command
|
||||
@@ -148,7 +145,7 @@ def logfilter_error(logfilter):
|
||||
Args:
|
||||
logfilter: A comma separated list of logger names.
|
||||
"""
|
||||
if set(logfilter.lstrip('!').split(',')).issubset(log.LOGGER_NAMES):
|
||||
if set(logfilter.split(',')).issubset(log.LOGGER_NAMES):
|
||||
return logfilter
|
||||
else:
|
||||
raise argparse.ArgumentTypeError(
|
||||
@@ -162,12 +159,9 @@ def debug_flag_error(flag):
|
||||
Available flags:
|
||||
debug-exit: Turn on debugging of late exit.
|
||||
pdb-postmortem: Drop into pdb on exceptions.
|
||||
no-sql-history: Don't store history items.
|
||||
no-scroll-filtering: Process all scrolling updates.
|
||||
log-requests: Log all network requests.
|
||||
"""
|
||||
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history',
|
||||
'no-scroll-filtering', 'log-requests', 'lost-focusproxy']
|
||||
'no-scroll-filtering']
|
||||
|
||||
if flag in valid_flags:
|
||||
return flag
|
||||
|
||||
@@ -182,16 +182,9 @@ def init_log(args):
|
||||
root = logging.getLogger()
|
||||
global console_filter
|
||||
if console is not None:
|
||||
if not args.logfilter:
|
||||
negate = False
|
||||
names = None
|
||||
elif args.logfilter.startswith('!'):
|
||||
negate = True
|
||||
names = args.logfilter[1:].split(',')
|
||||
else:
|
||||
negate = False
|
||||
names = args.logfilter.split(',')
|
||||
console_filter = LogFilter(names, negate)
|
||||
console_filter = LogFilter(None)
|
||||
if args.logfilter is not None:
|
||||
console_filter.names = args.logfilter.split(',')
|
||||
console.addFilter(console_filter)
|
||||
root.addHandler(console)
|
||||
if ram is not None:
|
||||
@@ -209,11 +202,6 @@ def _init_py_warnings():
|
||||
"""Initialize Python warning handling."""
|
||||
warnings.simplefilter('default')
|
||||
warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning)
|
||||
# This happens in many qutebrowser dependencies...
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning,
|
||||
message="Using or importing the ABCs from "
|
||||
"'collections' instead of from 'collections.abc' "
|
||||
"is deprecated, and in 3.8 it will stop working")
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@@ -510,14 +498,12 @@ class LogFilter(logging.Filter):
|
||||
comma-separated list instead.
|
||||
|
||||
Attributes:
|
||||
names: A list of record names to filter.
|
||||
negated: Whether names is a list of records to log or to suppress.
|
||||
_names: A list of names that should be logged.
|
||||
"""
|
||||
|
||||
def __init__(self, names, negate=False):
|
||||
def __init__(self, names):
|
||||
super().__init__()
|
||||
self.names = names
|
||||
self.negated = negate
|
||||
|
||||
def filter(self, record):
|
||||
"""Determine if the specified record is to be logged."""
|
||||
@@ -528,12 +514,12 @@ class LogFilter(logging.Filter):
|
||||
return True
|
||||
for name in self.names:
|
||||
if record.name == name:
|
||||
return not self.negated
|
||||
return True
|
||||
elif not record.name.startswith(name):
|
||||
continue
|
||||
elif record.name[len(name)] == '.':
|
||||
return not self.negated
|
||||
return self.negated
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class RAMHandler(logging.Handler):
|
||||
|
||||
@@ -510,13 +510,11 @@ def sanitize_filename(name, replacement='_'):
|
||||
|
||||
def set_clipboard(data, selection=False):
|
||||
"""Set the clipboard to some given data."""
|
||||
global fake_clipboard
|
||||
if selection and not supports_selection():
|
||||
raise SelectionUnsupportedError
|
||||
if log_clipboard:
|
||||
what = 'primary selection' if selection else 'clipboard'
|
||||
log.misc.debug("Setting fake {}: {}".format(what, json.dumps(data)))
|
||||
fake_clipboard = data
|
||||
else:
|
||||
mode = QClipboard.Selection if selection else QClipboard.Clipboard
|
||||
QApplication.clipboard().setText(data, mode=mode)
|
||||
|
||||
@@ -310,7 +310,7 @@ def _chromium_version():
|
||||
"""Get the Chromium version for QtWebEngine.
|
||||
|
||||
This can also be checked by looking at this file with the right Qt tag:
|
||||
http://code.qt.io/cgit/qt/qtwebengine.git/tree/tools/scripts/version_resolver.py#n41
|
||||
https://github.com/qt/qtwebengine/blob/dev/tools/scripts/version_resolver.py#L41
|
||||
|
||||
Quick reference:
|
||||
Qt 5.7: Chromium 49
|
||||
@@ -318,7 +318,7 @@ def _chromium_version():
|
||||
Qt 5.9: Chromium 56
|
||||
Qt 5.10: Chromium 61
|
||||
Qt 5.11: Chromium 65
|
||||
Qt 5.12: Chromium 69 (? - current dev branch: 67)
|
||||
Qt 5.12: Chromium 69 (?)
|
||||
|
||||
Also see https://www.chromium.org/developers/calendar
|
||||
"""
|
||||
@@ -399,7 +399,6 @@ def version():
|
||||
lines += [
|
||||
'Frozen: {}'.format(hasattr(sys, 'frozen')),
|
||||
"Imported from {}".format(importpath),
|
||||
"Using Python from {}".format(sys.executable),
|
||||
"Qt library executable path: {}, data path: {}".format(
|
||||
QLibraryInfo.location(QLibraryInfo.LibraryExecutablesPath),
|
||||
QLibraryInfo.location(QLibraryInfo.DataPath)
|
||||
@@ -484,7 +483,6 @@ def pastebin_version(pbclient=None):
|
||||
|
||||
def _on_paste_version_success(url):
|
||||
global pastebin_url
|
||||
url = url.strip()
|
||||
_yank_url(url)
|
||||
pbclient.deleteLater()
|
||||
pastebin_url = url
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user