Merge branch 'master' into keys

This commit is contained in:
Florian Bruhin 2018-02-25 21:25:12 +01:00
commit 321d5c5d20
407 changed files with 7485 additions and 2245 deletions

View File

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

2
.github/CODEOWNERS vendored
View File

@ -8,3 +8,5 @@ tests/unit/completion/* @rcorre
tests/unit/misc/test_sql.py @rcorre tests/unit/misc/test_sql.py @rcorre
qutebrowser/config/configdata.yml @mschilli87 qutebrowser/config/configdata.yml @mschilli87
qutebrowser/javascript/caret.js @artur-shaik

17
.gitignore vendored
View File

@ -25,18 +25,19 @@ __pycache__
/.tox /.tox
/testresults.html /testresults.html
/.cache /.cache
/.pytest_cache
/.testmondata /.testmondata
/.hypothesis /.hypothesis
/.mypy_cache /.mypy_cache
/prof /prof
/venv /venv
TODO TODO
/scripts/testbrowser_cpp/webkit/Makefile /scripts/testbrowser/cpp/webkit/Makefile
/scripts/testbrowser_cpp/webkit/main.o /scripts/testbrowser/cpp/webkit/main.o
/scripts/testbrowser_cpp/webkit/testbrowser /scripts/testbrowser/cpp/webkit/testbrowser
/scripts/testbrowser_cpp/webkit/.qmake.stash /scripts/testbrowser/cpp/webkit/.qmake.stash
/scripts/testbrowser_cpp/webengine/Makefile /scripts/testbrowser/cpp/webengine/Makefile
/scripts/testbrowser_cpp/webengine/main.o /scripts/testbrowser/cpp/webengine/main.o
/scripts/testbrowser_cpp/webengine/testbrowser /scripts/testbrowser/cpp/webengine/testbrowser
/scripts/testbrowser_cpp/webengine/.qmake.stash /scripts/testbrowser/cpp/webengine/.qmake.stash
/scripts/dev/pylint_checkers/qute_pylint.egg-info /scripts/dev/pylint_checkers/qute_pylint.egg-info

View File

@ -21,6 +21,17 @@ matrix:
env: TESTENV=py35-pyqt59 env: TESTENV=py35-pyqt59
- os: linux - os: linux
env: TESTENV=py36-pyqt59-cov env: TESTENV=py36-pyqt59-cov
- os: linux
env: TESTENV=py36-pyqt510
# We need a newer Xvfb as a WORKAROUND for:
# https://bugreports.qt.io/browse/QTBUG-64928
sudo: required
addons:
apt:
sources:
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
packages:
- xvfb
- os: osx - os: osx
env: TESTENV=py36 OSX=sierra env: TESTENV=py36 OSX=sierra
osx_image: xcode9.2 osx_image: xcode9.2

View File

@ -8,7 +8,7 @@ graft icons
graft doc/img graft doc/img
graft misc/apparmor graft misc/apparmor
graft misc/userscripts graft misc/userscripts
recursive-include scripts *.py *.sh recursive-include scripts *.py *.sh *.js
include qutebrowser/utils/testfile include qutebrowser/utils/testfile
include qutebrowser/git-commit-id include qutebrowser/git-commit-id
include LICENSE doc/* README.asciidoc include LICENSE doc/* README.asciidoc
@ -23,7 +23,7 @@ include qutebrowser/config/configdata.yml
prune www prune www
prune scripts/dev prune scripts/dev
prune scripts/testbrowser_cpp prune scripts/testbrowser/cpp
prune .github prune .github
exclude scripts/asciidoc2html.py exclude scripts/asciidoc2html.py
exclude doc/notes exclude doc/notes

View File

@ -44,8 +44,8 @@ Documentation
In addition to the topics mentioned in this README, the following documents are In addition to the topics mentioned in this README, the following documents are
available: available:
* https://qutebrowser.org/img/cheatsheet-big.png[Key binding cheatsheet]: + * https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[Key binding cheatsheet]: +
image:https://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://qutebrowser.org/img/cheatsheet-big.png"] image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"]
* link:doc/quickstart.asciidoc[Quick start guide] * link:doc/quickstart.asciidoc[Quick start guide]
* https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings * https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings
* link:doc/faq.asciidoc[Frequently asked questions] * link:doc/faq.asciidoc[Frequently asked questions]
@ -114,7 +114,7 @@ The following software and libraries are required to run qutebrowser:
* http://fdik.org/pyPEG/[pyPEG2] * http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2] * http://jinja.pocoo.org/[jinja2]
* http://pygments.org/[pygments] * http://pygments.org/[pygments]
* http://pyyaml.org/wiki/PyYAML[PyYAML] * https://github.com/yaml/pyyaml[PyYAML]
* http://www.attrs.org/[attrs] * http://www.attrs.org/[attrs]
The following libraries are optional: The following libraries are optional:

View File

@ -13,47 +13,75 @@ Thanks a lot to the following people who contributed to it:
Gold sponsors Gold sponsors
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
TODO - Iggy
- zwitschi
- 2x Anonymous
Silver sponsors Silver sponsors
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
TODO - https://benary.org[benaryorg]
- https://scratchbook.ch[Claude]
- Martin Tournoij
- http://supported.elsensohn.ch[Thomas Elsensohn]
- Christian Helbling
- Gavin Troy
- Chris King-Parra
- Tim Das Mool Wegener
Other sponsors Other sponsors
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
TODO: people with t-shirts or higher pledge levels
- 7scan - 7scan
- AMD1212
- Alex
- Alex Suykov - Alex Suykov
- Alexey Zhikhartsev - Alexey Zhikhartsev
- Allan Nordhøy - Allan Nordhøy
- Anirudh Sanjeev - Anirudh Sanjeev
- Anssi Puustinen - Anssi Puustinen
- Anton Grensjö
- Aristaeus
- Armin Fisslthaler
- Ashley Hauck
- Benedikt Steindorf - Benedikt Steindorf
- Bernardo Kuri - Bernardo Kuri
- Blaise Duszynski - Blaise Duszynski
- Bostan - Bostan
- Bruno Oliveira - Bruno Oliveira
- BunnyApocalypse
- Christian Kellermann
- Colin Jacobs - Colin Jacobs
- Daniel Andersson - Daniel Andersson
- Daniel Nelson
- Daniel P. Schmidt
- Daniel Salby
- Danilo - Danilo
- David Beley - David Beley
- David Hollings - David Hollings
- David Keijser
- David Parrish - David Parrish
- Derin Yarsuvat - Derin Yarsuvat
- Dmytro Kostiuchenko - Dmytro Kostiuchenko
- Eero Kari
- Epictek
- Eric
- Faure Hu
- Ferus
- Frederik Thorøe - Frederik Thorøe
- G4v4g4i - G4v4g4i
- Granitosaurus
- Gyula Teleki - Gyula Teleki
- H - H
- Heinz Bruhin
- Hosaka - Hosaka
- Ihor Radchenko
- Iordanis Grigoriou - Iordanis Grigoriou
- Isaac Sandaljian - Isaac Sandaljian
- Jakub Podeszwik - Jakub Podeszwik
- Jamie Anderson - Jamie Anderson
- Jasper Woudenberg - Jasper Woudenberg
- Jay Kamat
- Jens Højgaard - Jens Højgaard
- Johannes - Johannes
- John Baber-Lucero - John Baber-Lucero
@ -61,9 +89,11 @@ TODO: people with t-shirts or higher pledge levels
- Kenichiro Ito - Kenichiro Ito
- Kenny Low - Kenny Low
- Lars Ivar Igesund - Lars Ivar Igesund
- Leulas
- Lucas Aride Moulin - Lucas Aride Moulin
- Ludovic Chabant - Ludovic Chabant
- Lukas Gierth - Lukas Gierth
- Magnus Lindström
- Marulkan - Marulkan
- Matthew Chun-Lum - Matthew Chun-Lum
- Matthew Cronen - Matthew Cronen
@ -80,7 +110,10 @@ TODO: people with t-shirts or higher pledge levels
- Peter Rice - Peter Rice
- Philipp Middendorf - Philipp Middendorf
- Pkill9 - Pkill9
- PluMGMK
- Prescott - Prescott
- ProXicT
- Ram-Z
- Robotichead - Robotichead
- Roshless - Roshless
- Ryan Ellis - Ryan Ellis
@ -90,35 +123,53 @@ TODO: people with t-shirts or higher pledge levels
- Sean Herman - Sean Herman
- Sebastian Frysztak - Sebastian Frysztak
- Shelby Cruver - Shelby Cruver
- Simon Désaulniers
- SirCmpwn - SirCmpwn
- Soham Pal - Soham Pal
- Stephan Jauernick
- Stewart Webb - Stewart Webb
- Sven Reinecke - Sven Reinecke
- Timothée Floure
- Tom Bass - Tom Bass
- Tom Kirchner
- Tomas Slusny - Tomas Slusny
- Tomasz Kramkowski - Tomasz Kramkowski
- Tommy Thomas - Tommy Thomas
- Tuscan
- Ulrich Pötter
- Vasilij Schneidermann - Vasilij Schneidermann
- Vlaaaaaaad - Vlaaaaaaad
- XTaran
- Z2h-A6n
- ayekat
- beanieuptop - beanieuptop
- cee
- craftyguy
- demure - demure
- dlangevi
- epon
- evenorbert - evenorbert
- fishss - fishss
- gsnewmark - gsnewmark
- guillermohs9 - guillermohs9
- hernani
- hubcaps - hubcaps
- jnphilipp
- lobachevsky - lobachevsky
- neodarz - neodarz
- nihlaeth - nihlaeth
- notbenh - notbenh
- nyctea
- ongy
- patrick suwanvithaya - patrick suwanvithaya
- pyratebeard - pyratebeard
- p≡p foundation
- randm_dave - randm_dave
- sabreman - sabreman
- toml - toml
- vimja - vimja
- wiz - wiz
- 44 Anonymous - 48 Anonymous
2016 2016
---- ----

View File

@ -15,54 +15,168 @@ breaking changes (such as renamed commands) can happen in minor releases.
// `Fixed` for any bug fixes. // `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities. // `Security` to invite users to upgrade in case of vulnerabilities.
v1.1.0 (unreleased) v1.2.0 (unreleased)
------------------- -------------------
Added Added
~~~~~ ~~~~~
- Initial implementation of per-domain settings:
* `:set` and `:config-cycle` now have a `-u`/`--pattern` argument taking a
https://developer.chrome.com/extensions/match_patterns[URL match pattern]
for supported settings.
* `config.set` in `config.py` now takes a third argument which is the pattern.
* New `with config.pattern('...') as p:` context manager for `config.py` to
use the shorthand syntax with a pattern.
* New `tsh` keybinding to toggle scripts for the current host. With a capital
`S`, the toggle is saved. With a capital `H`, subdomains are included.
* New `tsu` keybinding to toggle scripts for the current URL. With a capital
`S`, the toggle is saved.
- QtWebEngine: Caret/visual mode is now supported.
- QtWebEngine: Authentication via ~/.netrc is now supported.
- A new `qute://bindings` page, opened by `:bind`, shows all keybindings.
- `:session-load` has a new `--delete` flag which deletes the
session after loading it.
- QtWebEngine: Retrying downloads is now supported with Qt 5.10 or newer.
- QtWebEngine: Hinting and other features inside same-origin frames is now
supported.
- New `cycle-inputs.js` script in `scripts/` which can be used with `:jseval -f`
to cycle through inputs.
- New `--no-last` flag for `:tab-focus` to not focus the last tab when focusing
the currently focused one.
- New `--edit` flag for `:view-source` to open the source in an external editor.
- New `statusbar.widgets` setting to configure which widgets should be shown in
which order in the statusbar.
- New `:prompt-yank` command (bound to `Alt-y` by default) to yank URLs
referenced in prompts.
- The `hostblock_blame` script which was removed in v1.0 was updated for the new
config and re-added.
- New `qute://tabs` page (opened via `:buffer`) which lists all tabs.
Changed
~~~~~~~
- The `hist_importer.py` script now only imports URL schemes qutebrowser can
handle.
- Deleting a prefix (`:`, `/` or `?`) via backspace now leaves command mode.
- Angular 1 elements now get hints assigned.
- `:tab-only` with pinned tabs now still closes unpinned tabs.
- GreaseMonkey `@include` and `@exclude` now support
regex matches. With QtWebEngine and Qt 5.8 and newer, Qt handles the matching,
but similar functionality was added in Qt 5.11.
- The sqlite history now uses write-ahead logging which should be
a performance and stability improvement.
- The `url.incdec_segments` option now also can take `port` as possible segment.
- QtWebEngine: `:view-source` now uses Chromium's `view-source:` scheme.
- Tabs now show their full title as tooltip.
- When an editor is spawned with `:open-editor` and `:config-edit`, the changes
are now applied as soon as the file is saved in the editor.
- When there are multiple unknown keys in a autoconfig.yml, they now all get
reported in one error.
- New `tabs.mode_on_change` setting which replaces
`tabs.persist_mode_on_change`. It can now be set to `restore` which remembers
input modes (input/passthrough) per tab.
- More performance improvements when opening/closing many tabs.
- The `:version` page now has a button to pastebin the information.
- Replacements like `{url}` can now be replaced as `{{url}}`.
Fixed
~~~~~
- QtWebEngine: Improved fullscreen handling with Qt 5.10.
- QtWebEngine: Hinting and scrolling now works properly on special
`view-source:` pages.
- QtWebEngine: Scroll positions are now restored correctly from sessions.
- QtWebEngine: Crash with Qt 5.10.1 when using :undo on some tabs.
- QtWebEngine: `:follow-selected` should now work in more cases with Qt > 5.10.
- QtWebKit: `:view-source` now displays a valid URL.
- URLs containing ampersands and other special chars are now shown
correctly when filtering them in the completion.
- `:bookmark-add "" foo` can now be used to save the current URL with a custom
title.
- `:spawn -o` now waits until the process has finished before trying to show the
output. Previously, it incorrectly showed the previous output immediately.
- QtWebEngine: Qt download objects are now cleaned up properly when a download
is removed.
- Suspended pages now should always load the correct page when being un-suspended.
- Compatibility with Python 3.7
- Exception types are now shown properly with `:config-source` and `:config-edit`.
- When using `:bookmark-add --toggle`, bookmarks are now saved properly.
Removed
~~~~~~~
- `QUTE_SELECTED_HTML` is now not set for userscripts anymore except when called
via hints.
- The `qutebrowser_viewsource` userscript has been removed as `:view-source
--edit` can now be used.
- The `tabs.persist_mode_on_change` setting has been removed and replaced by
`tabs.mode_on_change`.
v1.1.1
------
Fixed
~~~~~
- The Makefile now actually works.
- Fixed crashes with Qt 5.10 when closing a tab before it finished loading.
v1.1.0
------
Added
~~~~~
- Initial support for Greasemonkey scripts. There are still some rough edges, - Initial support for Greasemonkey scripts. There are still some rough edges,
but many scripts should already work. but many scripts should already work.
- There's now a `misc/Makefile` file in releases, which should help - There's now a `misc/Makefile` file in releases, which should help
distributions which package qutebrowser, as they can run something like distributions which package qutebrowser, as they can run something like
`make -f misc/Makefile DESTDIR="$pkgdir" install` now. `make -f misc/Makefile DESTDIR="$pkgdir" install` now.
- New `{current_url}` field for `window.title_format` and `tabs.title.format`. - New fields for `window.title_format` and `tabs.title.format`:
- New `colors.statusbar.passthrough.fg`/`.bg` settings. * `{current_url}`
- New `completion.delay` and `completion.min_chars` settings to update the * `{protocol}`
completion less often. - New settings:
- New `completion.use_best_match` setting to automatically use the best-matching * `colors.statusbar.passthrough.fg`/`.bg`
command in the completion. * `completion.delay` and `completion.min_chars` to update the completion less
- New `:tab-give` and `:tab-take` commands, to give tabs to another window, or often.
take them from another window. * `completion.use_best_match` to automatically use the best-matching
command in the completion.
* `keyhint.radius` to configure the edge rounding for the key hint widget.
* `qt.highdpi` to turn on Qt's High-DPI scaling.
* `tabs.pinned.shrink` (`true` by default) to make it possible
for pinned tabs and normal tabs to have the same size.
* `content.windowed_fullscreen` to show e.g. a fullscreened video in the
window without fullscreening that window.
* `tabs.persist_mode_on_change` to keep the current mode when
switching tabs.
* `session.lazy_restore` which allows to not load pages immediately
when restoring a session.
- New commands:
* `:tab-give` and `:tab-take`, to give tabs to another window, or take them
from another window.
* `:completion-item-yank` (bound to `<Ctrl-C>`) to yank the current
completion item text.
* `:edit-command` to edit the commandline in an editor.
* `search.incremental` for incremental text search.
- New flags for existing commands:
* `-o` flag for `:spawn` to show stdout/stderr in a new tab.
* `--rapid` flag for `:command-accept` (bound to `Ctrl-Enter` by default),
which allows executing a command in the completion without closing it.
* `--private` and `--related` flags for `:edit-url`, which have the
same effect they have with `:open`.
* `--history` for `:completion-item-focus` which causes it to go
through the command history when no text was entered. The default bindings for
cursor keys in the completion changed to use that, so that they can be used
again to navigate through completion items when a text was entered.
* `--file` for `:debug-pyeval` which makes it take a filename instead of a
line of code.
- New `config.source(...)` method for `config.py` to source another file. - New `config.source(...)` method for `config.py` to source another file.
- New `keyhint.radius` option to configure the edge rounding for the key hint
widget.
- `:edit-url` now handles the `--private` and `--related` flags, which have the
same effect they have with `:open`.
- New `{line}` and `{column}` replacements for `editor.command` to position the - New `{line}` and `{column}` replacements for `editor.command` to position the
cursor correctly. cursor correctly.
- New `qute-pass` userscript as alternative to `password_fill` which allows - New `qute-pass` userscript as alternative to `password_fill` which allows
selecting accounts via rofi or any other dmenu-compatile application. selecting accounts via rofi or any other dmenu-compatile application.
- New `qt.highdpi` setting to turn on Qt's High-DPI scaling.
- New `:completion-item-yank` command (bound to `<Ctrl-C>`) to yank the current
completion item text.
- New `tabs.pinned.shrink` setting to (`true` by default) to make it possible
for pinned tabs and normal tabs to have the same size.
- New `content.windowed_fullscreen` setting to show e.g. a fullscreened video in
the window without fullscreening that window.
- New `:edit-command` command to edit the commandline in an editor.
- New `tabs.persist_mode_on_change` setting to keep the current mode when
switching tabs.
- New `session.lazy_restore` setting which allows to not load pages immediately
when restoring a session.
- New `hist_importer.py` script to import history from Firefox/Chromium. - New `hist_importer.py` script to import history from Firefox/Chromium.
- New `{protocol}` replacement for `tabs.title.format` and friends.
- New `-o` flag for `:spawn` to show stdout/stderr in a new tab.
- Support for incremental search, with a new `search.incremental` setting.
- New `--rapid` flag for `:command-accept` (bound to `Ctrl-Enter` by default),
which allows executing a command in the completion without closing it.
- The `colors.completion.fg` setting can now be a list, allowing to specify
different colors for the three completion columns.
Changed Changed
~~~~~~~ ~~~~~~~
@ -73,13 +187,15 @@ Changed
* `tabs.indicator_padding` -> `tabs.indicator.padding` * `tabs.indicator_padding` -> `tabs.indicator.padding`
* `session_default_name` -> `session.default_name` * `session_default_name` -> `session.default_name`
* `ignore_case` -> `search.ignore_case` * `ignore_case` -> `search.ignore_case`
- Much improved user stylesheet handling for QtWebEngine which reduces
flickering and updates immediately after setting a stylesheet.
- High-DPI favicons are now used when available. - High-DPI favicons are now used when available.
- The `asciidoc2html.py` script now uses Pygments (which is already a dependency - The `asciidoc2html.py` script now uses Pygments (which is already a dependency
of qutebrowser) instead of `source-highlight` for syntax highlighting. of qutebrowser) instead of `source-highlight` for syntax highlighting.
- The `:buffer` command now doesn't require quoting anymore, similar to `:open`. - The `:buffer` command now doesn't require quoting anymore, similar to `:open`.
- The `importer.py` script was largely rewritten and now also supports importing - The `importer.py` script was largely rewritten and now also supports importing
from Firefox' `places.sqlite` file and Chrome/Chromium profiles. from Firefox' `places.sqlite` file and Chrome/Chromium profiles.
- Various internal refactorings to use Python 3.5 and ECMAscript 6 features - Various internal refactorings to use Python 3.5 and ECMAscript 6 features.
- If the `window.hide_wayland_decoration` setting is False, but - If the `window.hide_wayland_decoration` setting is False, but
`QT_WAYLAND_DISABLE_WINDOWDECORATION` is set in the environment, `QT_WAYLAND_DISABLE_WINDOWDECORATION` is set in the environment,
the decorations are still hidden. the decorations are still hidden.
@ -91,17 +207,9 @@ Changed
- The `qute://version` page now also shows the uptime of qutebrowser. - The `qute://version` page now also shows the uptime of qutebrowser.
- qutebrowser now prompts to create a non-existing directory when starting a - qutebrowser now prompts to create a non-existing directory when starting a
download. download.
- Much improved user stylesheet handling which reduces flickering - `:jseval --file` now searches relative paths in a `js/` subdir in
and updates immediately after setting a stylesheet. qutebrowser's data dir, e.g. `~/.local/share/qutebrowser/js`.
- `:completion-item-focus` now has a `--history` flag which causes it to go - The current/default bindings are now shown in the ``:bind` completion.
through the command history when no text was entered. The default bindings for
cursor keys in the completion changed to use that, so that they can be used
again to navigate through completion items when a text was entered.
- `:debug-pyeval` now has a `--file` argument so it takes a filename instead of
a line of code.
- `:jseval --file` now searches relative paths in a js/ subdir in qutebrowser's
data dir, e.g. `~/.local/share/qutebrowser/js`.
- The current/default bindings are now shown in the :bind completion.
- Empty categories are now hidden in the `:open` completion. - Empty categories are now hidden in the `:open` completion.
- Search terms for URLs and titles can now be mixed when filtering the - Search terms for URLs and titles can now be mixed when filtering the
completion. completion.
@ -110,30 +218,35 @@ Changed
order, and mixed matches on URL/tite are possible. order, and mixed matches on URL/tite are possible.
- The system's default encoding (rather than UTF-8) is now used to decode - The system's default encoding (rather than UTF-8) is now used to decode
subprocess output. subprocess output.
- qutebrowser now ensures it's focused again after an external editor is closed.
- The `colors.completion.fg` setting can now be a list, allowing to specify
different colors for the three completion columns.
Fixed Fixed
~~~~~ ~~~~~
- More consistent sizing for favicons with vertical tabs. - More consistent sizing for favicons with vertical tabs.
- Using `:home` on pinned tabs is now prevented. - Using `:home` on pinned tabs is now prevented.
- Fix crash with unknown file types loaded via qute://help. - Fix crash with unknown file types loaded via `qute://help`.
- Scrolling performance improvements. - Scrolling performance improvements.
- Sites like `qute://help` now redirect to `qute://help/` to make sure links - Sites like `qute://help` now redirect to `qute://help/` to make sure links
work properly. work properly.
- Fixes for the size calculation of pinned tabs in the tab bar. - Fixes for the size calculation of pinned tabs in the tab bar.
- Worked around a crash with PyQt 5.9.1 compiled against Qt < 5.9.1 when using - Worked around a crash with PyQt 5.9.1 compiled against Qt < 5.9.1 when using
:yank or qute:// URLs. `:yank` or `qute://` URLs.
- Fixed crash when opening `qute://help/img` - Fixed crash when opening `qute://help/img`.
- Fixed `gU` (`:navigate up`) on `qute://help` and webservers not handling `..` - Fixed `gU` (`:navigate up`) on `qute://help` and webservers not handling `..`
in a URL. in a URL.
- Using e.g. `-s backend webkit` to set the backend now works correctly. - Using e.g. `-s backend webkit` to set the backend now works correctly.
- Fixed crash when closing the tab an external editor was opened in. - Fixed crash when closing the tab an external editor was opened in.
- When using `:search-next` before a search is finished, no warning about no - When using `:search-next` before a search is finished, no warning about no
results being found is shown anymore. results being found is shown anymore.
- Fix :click-element with an ID containing non-alphanumeric characters. - Fix `:click-element` with an ID containing non-alphanumeric characters.
- Fix crash when a subprocess outputs data which is not decodable as UTF-8. - Fix crash when a subprocess outputs data which is not decodable as UTF-8.
- Fix crash when closing a tab immediately after hinting. - Fix crash when closing a tab immediately after hinting.
- Worked around issues in Qt 5.10 with loading progress never being finished. - Worked around issues in Qt 5.10 with loading progress never being finished.
- Fixed a crash when writing a flag before a command (e.g. `:-w open `).
- Fixed a crash when clicking certain form elements with QtWebEngine.
Deprecated Deprecated
~~~~~~~~~~ ~~~~~~~~~~
@ -1088,7 +1201,7 @@ Added
- New `:fake-key` command to send a fake keypress to a website or to - New `:fake-key` command to send a fake keypress to a website or to
qutebrowser. qutebrowser.
- New `--mhtml` argument for `:download` to download a page including all - New `--mhtml` argument for `:download` to download a page including all
ressources as MHTML file. resources as MHTML file.
- New option `tabs -> title-alignment` to change the alignment of tab titles. - New option `tabs -> title-alignment` to change the alignment of tab titles.
Changed Changed
@ -1288,7 +1401,7 @@ Added
- New argument `--no-err-windows` to suppress all error windows. - New argument `--no-err-windows` to suppress all error windows.
- New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom). - New arguments `--top-navigate` and `--bottom-navigate` (`-t`/`-b`) for `:scroll-page` to specify a navigation action (e.g. automatically go to the next page when arriving at the bottom).
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is. - New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
- New flag `-v`/`--verbose` for `:spawn` to print informations when the process started/exited successfully. - New flag `-v`/`--verbose` for `:spawn` to print information when the process started/exited successfully.
- Many new color settings (foreground setting for every background setting). - Many new color settings (foreground setting for every background setting).
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar. - New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one. - New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one.

View File

@ -44,8 +44,8 @@ be easy to solve]
If you prefer C++ or Javascript to Python, see the relevant issues which involve If you prefer C++ or Javascript to Python, see the relevant issues which involve
work in those languages: work in those languages:
* https://github.com/qutebrowser/qutebrowser/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3Ac%2B%2B[C++] (mostly work on Qt, the library behind qutebrowser) * https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3A%22language%3A+c%2B%2B%22[C++] (mostly work on Qt, the library behind qutebrowser)
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Ajavascript[JavaScript] * https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3A%22language%3A+javascript%22[JavaScript]
There are also some things to do if you don't want to write code: There are also some things to do if you don't want to write code:
@ -375,7 +375,7 @@ The following logging levels are available for every logger:
|error |There was an issue and some kind of operation was abandoned. |error |There was an issue and some kind of operation was abandoned.
|warning |There was an issue but the operation can continue running. |warning |There was an issue but the operation can continue running.
|info |General informational messages. |info |General informational messages.
|debug |Verbose debugging informations. |debug |Verbose debugging information.
|======================================================================= |=======================================================================
[[commands]] [[commands]]

View File

@ -216,6 +216,29 @@ And then re-emerging qtwebengine with: +
emerge -1 qtwebengine emerge -1 qtwebengine
Unable to view DRM content (Netflix, Spotify, etc.).::
You will need to install `widevine` and set `qt.args` to point to it.
Qt 5.9 currently only supports widevine up to Chrome version 61.
+
On Arch, simply install `qt5-webengine-widevine` from the AUR and run:
+
----
:set qt.args '["ppapi-widevine-path=/usr/lib/qt/plugins/ppapi/libwidevinecdmadapter.so"]'
:restart
----
+
For other distributions, download the chromium tarball and widevine-cdm zip from
https://aur.archlinux.org/packages/qt5-webengine-widevine/[the AUR page],
extract `libwidevinecdmadapter.so` and `libwidevinecdm.so` files, respectively,
and move them to the `ppapi` plugin directory in your Qt library directory (create it if it does not exist).
+
Lastly, set your `qt.args` to point to that directory and restart qutebrowser:
+
----
:set qt.args '["ppapi-widevine-path=/usr/lib64/qt5/plugins/ppapi/libwidevinecdmadapter.so"]'
:restart
----
My issue is not listed.:: My issue is not listed.::
If you experience any segfaults or crashes, you can report the issue in If you experience any segfaults or crashes, you can report the issue in
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or

View File

@ -1,6 +1,7 @@
// DO NOT EDIT THIS FILE DIRECTLY! // DO NOT EDIT THIS FILE DIRECTLY!
// It is autogenerated by running: // It is autogenerated by running:
// $ python3 scripts/dev/src2asciidoc.py // $ python3 scripts/dev/src2asciidoc.py
// vim: readonly:
= Commands = Commands
@ -145,14 +146,15 @@ How many pages to go back.
[[bind]] [[bind]]
=== bind === bind
Syntax: +:bind [*--mode* 'mode'] [*--default*] 'key' ['command']+ Syntax: +:bind [*--mode* 'mode'] [*--default*] ['key'] ['command']+
Bind a key to a command. Bind a key to a command.
If no command is given, show the current binding for the given key. Using :bind without any arguments opens a page showing all keybindings.
==== positional arguments ==== positional arguments
* +'key'+: The keychain or special key (inside `<...>`) to bind. * +'key'+: The keychain or special key (inside `<...>`) to bind.
* +'command'+: The command to execute, with optional args, or not given to print the current binding. * +'command'+: The command to execute, with optional args.
==== optional arguments ==== optional arguments
* +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the * +*-m*+, +*--mode*+: A comma-separated list of modes to bind the key in (default: `normal`). See `:help bindings.commands` for the
@ -174,7 +176,8 @@ Save the current page as a bookmark, or a specific url.
If no url and title are provided, then save the current page as a bookmark. If a url and title have been provided, then save the given url as a bookmark with the provided title. You can view all saved bookmarks on the link:qute://bookmarks[bookmarks page]. If no url and title are provided, then save the current page as a bookmark. If a url and title have been provided, then save the given url as a bookmark with the provided title. You can view all saved bookmarks on the link:qute://bookmarks[bookmarks page].
==== positional arguments ==== positional arguments
* +'url'+: url to save as a bookmark. If None, use url of current page. * +'url'+: url to save as a bookmark. If not given, use url of current page.
* +'title'+: title of the new bookmark. * +'title'+: title of the new bookmark.
==== optional arguments ==== optional arguments
@ -218,7 +221,7 @@ Syntax: +:buffer ['index']+
Select tab by index or url/title best match. Select tab by index or url/title best match.
Focuses window if necessary when index is given. If both index and count are given, use count. Focuses window if necessary when index is given. If both index and count are given, use count. With neither index nor count given, open the qute://tabs page.
==== positional arguments ==== positional arguments
* +'index'+: The [win_id/]index of the tab to focus. Or a substring in which case the closest match will be focused. * +'index'+: The [win_id/]index of the tab to focus. Or a substring in which case the closest match will be focused.
@ -271,7 +274,8 @@ Set all settings back to their default.
[[config-cycle]] [[config-cycle]]
=== config-cycle === config-cycle
Syntax: +:config-cycle [*--temp*] [*--print*] 'option' ['values' ['values' ...]]+ Syntax: +:config-cycle [*--pattern* 'pattern'] [*--temp*] [*--print*]
'option' ['values' ['values' ...]]+
Cycle an option between multiple values. Cycle an option between multiple values.
@ -280,6 +284,7 @@ Cycle an option between multiple values.
* +'values'+: The values to cycle through. * +'values'+: The values to cycle through.
==== optional arguments ==== optional arguments
* +*-u*+, +*--pattern*+: The URL pattern to use.
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed. * +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
* +*-p*+, +*--print*+: Print the value after setting. * +*-p*+, +*--print*+: Print the value after setting.
@ -741,7 +746,13 @@ This tries to automatically click on typical _Previous Page_ or _Next Page_ link
- `next`: Open a _next_ link. - `next`: Open a _next_ link.
- `up`: Go up a level in the current URL. - `up`: Go up a level in the current URL.
- `increment`: Increment the last number in the URL. - `increment`: Increment the last number in the URL.
Uses the
link:settings.html#url.incdec_segments[url.incdec_segments]
config option.
- `decrement`: Decrement the last number in the URL. - `decrement`: Decrement the last number in the URL.
Uses the
link:settings.html#url.incdec_segments[url.incdec_segments]
config option.
@ -1066,7 +1077,7 @@ Delete a session.
[[session-load]] [[session-load]]
=== session-load === session-load
Syntax: +:session-load [*--clear*] [*--temp*] [*--force*] 'name'+ Syntax: +:session-load [*--clear*] [*--temp*] [*--force*] [*--delete*] 'name'+
Load a session. Load a session.
@ -1078,6 +1089,7 @@ Load a session.
* +*-t*+, +*--temp*+: Don't set the current session for :session-save. * +*-t*+, +*--temp*+: Don't set the current session for :session-save.
* +*-f*+, +*--force*+: Force loading internal sessions (starting with an underline). * +*-f*+, +*--force*+: Force loading internal sessions (starting with an underline).
* +*-d*+, +*--delete*+: Delete the saved session once it has loaded.
[[session-save]] [[session-save]]
=== session-save === session-save
@ -1100,11 +1112,11 @@ Save a session.
[[set]] [[set]]
=== set === set
Syntax: +:set [*--temp*] [*--print*] ['option'] ['value']+ Syntax: +:set [*--temp*] [*--print*] [*--pattern* 'pattern'] ['option'] ['value']+
Set an option. Set an option.
If the option name ends with '?', the value of the option is shown instead. If the option name ends with '?', the value of the option is shown instead. Using :set without any arguments opens a page where settings can be changed interactively.
==== positional arguments ==== positional arguments
* +'option'+: The name of the option. * +'option'+: The name of the option.
@ -1113,6 +1125,7 @@ If the option name ends with '?', the value of the option is shown instead.
==== optional arguments ==== optional arguments
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed. * +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
* +*-p*+, +*--print*+: Print the value after setting. * +*-p*+, +*--print*+: Print the value after setting.
* +*-u*+, +*--pattern*+: The URL pattern to use.
[[set-cmd-text]] [[set-cmd-text]]
=== set-cmd-text === set-cmd-text
@ -1202,7 +1215,7 @@ The tab index to close
[[tab-focus]] [[tab-focus]]
=== tab-focus === tab-focus
Syntax: +:tab-focus ['index']+ Syntax: +:tab-focus [*--no-last*] ['index']+
Select the tab given as argument/[count]. Select the tab given as argument/[count].
@ -1214,6 +1227,9 @@ If neither count nor index are given, it behaves like tab-next. If both are give
last tab. last tab.
==== optional arguments
* +*-n*+, +*--no-last*+: Whether to avoid focusing last tab if already focused.
==== count ==== count
The tab index to focus, starting with 1. The tab index to focus, starting with 1.
@ -1228,6 +1244,9 @@ If no win_id is given, the tab will get detached into a new window.
==== positional arguments ==== positional arguments
* +'win-id'+: The window ID of the window to give the current tab to. * +'win-id'+: The window ID of the window to give the current tab to.
==== count
Overrides win_id (index starts at 1 for win_id=0).
[[tab-move]] [[tab-move]]
=== tab-move === tab-move
Syntax: +:tab-move ['index']+ Syntax: +:tab-move ['index']+
@ -1309,12 +1328,22 @@ Re-open the last closed tab or tabs.
[[version]] [[version]]
=== version === version
Syntax: +:version [*--paste*]+
Show version information. Show version information.
==== optional arguments
* +*-p*+, +*--paste*+: Paste to pastebin.
[[view-source]] [[view-source]]
=== view-source === view-source
Syntax: +:view-source [*--edit*]+
Show the source of the current page in a new tab. 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.
[[window-only]] [[window-only]]
=== window-only === window-only
Close all windows except for the current one. Close all windows except for the current one.
@ -1402,6 +1431,7 @@ How many steps to zoom out.
|<<prompt-accept,prompt-accept>>|Accept the current prompt. |<<prompt-accept,prompt-accept>>|Accept the current prompt.
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item. |<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
|<<prompt-open-download,prompt-open-download>>|Immediately open a download. |<<prompt-open-download,prompt-open-download>>|Immediately open a download.
|<<prompt-yank,prompt-yank>>|Yank URL to clipboard or primary selection.
|<<rl-backward-char,rl-backward-char>>|Move back a character. |<<rl-backward-char,rl-backward-char>>|Move back a character.
|<<rl-backward-delete-char,rl-backward-delete-char>>|Delete the character before the cursor. |<<rl-backward-delete-char,rl-backward-delete-char>>|Delete the character before the cursor.
|<<rl-backward-kill-word,rl-backward-kill-word>>|Remove chars from the cursor to the beginning of the word. |<<rl-backward-kill-word,rl-backward-kill-word>>|Remove chars from the cursor to the beginning of the word.
@ -1607,6 +1637,15 @@ If no specific command is given, this will use the system's default application
==== note ==== note
* This command does not split arguments after the last argument and handles quotes literally. * This command does not split arguments after the last argument and handles quotes literally.
[[prompt-yank]]
=== prompt-yank
Syntax: +:prompt-yank [*--sel*]+
Yank URL to clipboard or primary selection.
==== optional arguments
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
[[rl-backward-char]] [[rl-backward-char]]
=== rl-backward-char === rl-backward-char
Move back a character. Move back a character.

View File

@ -63,6 +63,10 @@ customizable.
Using the link:commands.html#set[`:set`] command and command completion, you Using the link:commands.html#set[`:set`] command and command completion, you
can quickly set settings interactively, for example `:set tabs.position left`. can quickly set settings interactively, for example `:set tabs.position left`.
Some settings are also customizable for a given
https://developer.chrome.com/apps/match_patterns[URL pattern] by doing e.g.
`:set --pattern=*://example.com/ content.images false`.
To get more help about a setting, use e.g. `:help tabs.position`. To get more help about a setting, use e.g. `:help tabs.position`.
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
@ -147,7 +151,6 @@ prefix to preserve backslashes) or a Python regex object:
If you want to read a setting, you can use the `c` object to do so as well: If you want to read a setting, you can use the `c` object to do so as well:
`c.colors.tabs.even.bg = c.colors.tabs.odd.bg`. `c.colors.tabs.even.bg = c.colors.tabs.odd.bg`.
Using strings for setting names Using strings for setting names
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -171,6 +174,26 @@ To read a setting, use the `config.get` method:
color = config.get('colors.completion.fg') color = config.get('colors.completion.fg')
---- ----
Per-domain settings
~~~~~~~~~~~~~~~~~~~
Using `config.set`, some settings are also customizable for a given
https://developer.chrome.com/apps/match_patterns[URL pattern]:
[source,python]
----
config.set('content.images', False, '*://example.com/')
----
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
similar to `c.` which is scoped to the given domain:
[source,python]
----
with config.pattern('*://example.com/') as p:
p.content.images = False
----
Binding keys Binding keys
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -254,7 +277,7 @@ Getting the config directory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you need to get the qutebrowser config directory, you can do so by reading If you need to get the qutebrowser config directory, you can do so by reading
`config.configdir`. Similarily, you can get the qutebrowser data directory via `config.configdir`. Similarly, you can get the qutebrowser data directory via
`config.datadir`. `config.datadir`.
This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path` This gives you a https://docs.python.org/3/library/pathlib.html[`pathlib.Path`
@ -366,6 +389,8 @@ You can use something like this to read colors from an `~/.Xresources` file:
[source,python] [source,python]
---- ----
import subprocess
def read_xresources(prefix): def read_xresources(prefix):
props = {} props = {}
x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE) x = subprocess.run(['xrdb', '-query'], stdout=subprocess.PIPE)
@ -376,7 +401,7 @@ def read_xresources(prefix):
return props return props
xresources = read_xresources('*') xresources = read_xresources('*')
c.colors.statusbar.normal.bg = xresources['*background'] c.colors.statusbar.normal.bg = xresources['*.background']
---- ----
Avoiding flake8 errors Avoiding flake8 errors

View File

@ -1,6 +1,7 @@
// DO NOT EDIT THIS FILE DIRECTLY! // DO NOT EDIT THIS FILE DIRECTLY!
// It is autogenerated by running: // It is autogenerated by running:
// $ python3 scripts/dev/src2asciidoc.py // $ python3 scripts/dev/src2asciidoc.py
// vim: readonly:
= Setting reference = Setting reference
@ -229,6 +230,7 @@
|<<statusbar.hide,statusbar.hide>>|Hide the statusbar unless a message is shown. |<<statusbar.hide,statusbar.hide>>|Hide the statusbar unless a message is shown.
|<<statusbar.padding,statusbar.padding>>|Padding (in pixels) for the statusbar. |<<statusbar.padding,statusbar.padding>>|Padding (in pixels) for the statusbar.
|<<statusbar.position,statusbar.position>>|Position of the status bar. |<<statusbar.position,statusbar.position>>|Position of the status bar.
|<<statusbar.widgets,statusbar.widgets>>|List of widgets displayed in the statusbar.
|<<tabs.background,tabs.background>>|Open new tabs (middleclick/ctrl+click) in the background. |<<tabs.background,tabs.background>>|Open new tabs (middleclick/ctrl+click) in the background.
|<<tabs.close_mouse_button,tabs.close_mouse_button>>|Mouse button with which to close tabs. |<<tabs.close_mouse_button,tabs.close_mouse_button>>|Mouse button with which to close tabs.
|<<tabs.close_mouse_button_on_bar,tabs.close_mouse_button_on_bar>>|How to behave when the close mouse button is pressed on the tab bar. |<<tabs.close_mouse_button_on_bar,tabs.close_mouse_button_on_bar>>|How to behave when the close mouse button is pressed on the tab bar.
@ -237,11 +239,11 @@
|<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators. |<<tabs.indicator.padding,tabs.indicator.padding>>|Padding (in pixels) for tab indicators.
|<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable). |<<tabs.indicator.width,tabs.indicator.width>>|Width (in pixels) of the progress indicator (0 to disable).
|<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed. |<<tabs.last_close,tabs.last_close>>|How to behave when the last tab is closed.
|<<tabs.mode_on_change,tabs.mode_on_change>>|When switching tabs, what input mode is applied.
|<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel. |<<tabs.mousewheel_switching,tabs.mousewheel_switching>>|Switch between tabs using the mouse wheel.
|<<tabs.new_position.related,tabs.new_position.related>>|Position of new tabs opened from another tab. |<<tabs.new_position.related,tabs.new_position.related>>|Position of new tabs opened from another tab.
|<<tabs.new_position.unrelated,tabs.new_position.unrelated>>|Position of new tabs which aren't opened from another tab. |<<tabs.new_position.unrelated,tabs.new_position.unrelated>>|Position of new tabs which aren't opened from another tab.
|<<tabs.padding,tabs.padding>>|Padding (in pixels) around text for tabs. |<<tabs.padding,tabs.padding>>|Padding (in pixels) around text for tabs.
|<<tabs.persist_mode_on_change,tabs.persist_mode_on_change>>|Stay in insert/passthrough mode when switching tabs.
|<<tabs.pinned.shrink,tabs.pinned.shrink>>|Shrink pinned tabs down to their contents. |<<tabs.pinned.shrink,tabs.pinned.shrink>>|Shrink pinned tabs down to their contents.
|<<tabs.position,tabs.position>>|Position of the tab bar. |<<tabs.position,tabs.position>>|Position of the tab bar.
|<<tabs.select_on_remove,tabs.select_on_remove>>|Which tab to select when the focused tab is removed. |<<tabs.select_on_remove,tabs.select_on_remove>>|Which tab to select when the focused tab is removed.
@ -580,8 +582,14 @@ Default:
* +pass:[sk]+: +pass:[set-cmd-text -s :bind]+ * +pass:[sk]+: +pass:[set-cmd-text -s :bind]+
* +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+ * +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+
* +pass:[ss]+: +pass:[set-cmd-text -s :set]+ * +pass:[ss]+: +pass:[set-cmd-text -s :set]+
* +pass:[tSH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tSh]+: +pass:[config-cycle -p -u *://{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tSu]+: +pass:[config-cycle -p -u {url} content.javascript.enabled ;; reload]+
* +pass:[th]+: +pass:[back -t]+ * +pass:[th]+: +pass:[back -t]+
* +pass:[tl]+: +pass:[forward -t]+ * +pass:[tl]+: +pass:[forward -t]+
* +pass:[tsH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tsh]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tsu]+: +pass:[config-cycle -p -t -u {url} content.javascript.enabled ;; reload]+
* +pass:[u]+: +pass:[undo]+ * +pass:[u]+: +pass:[undo]+
* +pass:[v]+: +pass:[enter-mode caret]+ * +pass:[v]+: +pass:[enter-mode caret]+
* +pass:[wB]+: +pass:[set-cmd-text -s :bookmark-load -w]+ * +pass:[wB]+: +pass:[set-cmd-text -s :bookmark-load -w]+
@ -615,6 +623,8 @@ Default:
* +pass:[&lt;Alt-Backspace&gt;]+: +pass:[rl-backward-kill-word]+ * +pass:[&lt;Alt-Backspace&gt;]+: +pass:[rl-backward-kill-word]+
* +pass:[&lt;Alt-D&gt;]+: +pass:[rl-kill-word]+ * +pass:[&lt;Alt-D&gt;]+: +pass:[rl-kill-word]+
* +pass:[&lt;Alt-F&gt;]+: +pass:[rl-forward-word]+ * +pass:[&lt;Alt-F&gt;]+: +pass:[rl-forward-word]+
* +pass:[&lt;Alt-Shift-Y&gt;]+: +pass:[prompt-yank --sel]+
* +pass:[&lt;Alt-Y&gt;]+: +pass:[prompt-yank]+
* +pass:[&lt;Ctrl-?&gt;]+: +pass:[rl-delete-char]+ * +pass:[&lt;Ctrl-?&gt;]+: +pass:[rl-delete-char]+
* +pass:[&lt;Ctrl-A&gt;]+: +pass:[rl-beginning-of-line]+ * +pass:[&lt;Ctrl-A&gt;]+: +pass:[rl-beginning-of-line]+
* +pass:[&lt;Ctrl-B&gt;]+: +pass:[rl-backward-char]+ * +pass:[&lt;Ctrl-B&gt;]+: +pass:[rl-backward-char]+
@ -1443,6 +1453,8 @@ Default:
Enable support for the HTML 5 web application cache feature. Enable support for the HTML 5 web application cache feature.
An application cache acts like an HTTP cache in some sense. For documents that use the application cache via JavaScript, the loader engine will first ask the application cache for the contents, before hitting the network. An application cache acts like an HTTP cache in some sense. For documents that use the application cache via JavaScript, the loader engine will first ask the application cache for the contents, before hitting the network.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1520,6 +1532,8 @@ This setting is only available with the QtWebKit backend.
=== content.dns_prefetch === content.dns_prefetch
Try to pre-fetch DNS entries to speed up browsing. Try to pre-fetch DNS entries to speed up browsing.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1531,6 +1545,8 @@ This setting is only available with the QtWebKit backend.
Expand each subframe to its contents. Expand each subframe to its contents.
This will flatten all the frames to become one scrollable page. This will flatten all the frames to become one scrollable page.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -1579,7 +1595,7 @@ Default: +pass:[true]+
[[content.headers.referer]] [[content.headers.referer]]
=== content.headers.referer === content.headers.referer
When to send the Referer header. When to send the Referer header.
The Referer header tells websites from which website you were coming from when visting them. The Referer header tells websites from which website you were coming from when visiting them.
Type: <<types,String>> Type: <<types,String>>
@ -1647,6 +1663,8 @@ Default:
=== content.hyperlink_auditing === content.hyperlink_auditing
Enable hyperlink auditing (`<a ping>`). Enable hyperlink auditing (`<a ping>`).
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -1655,6 +1673,8 @@ Default: +pass:[false]+
=== content.images === content.images
Load images automatically in web pages. Load images automatically in web pages.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1672,6 +1692,8 @@ Default: +pass:[true]+
Allow JavaScript to read from or write to the clipboard. Allow JavaScript to read from or write to the clipboard.
With QtWebEngine, writing the clipboard as response to a user interaction is always allowed. With QtWebEngine, writing the clipboard as response to a user interaction is always allowed.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -1680,6 +1702,8 @@ Default: +pass:[false]+
=== content.javascript.can_close_tabs === content.javascript.can_close_tabs
Allow JavaScript to close tabs. Allow JavaScript to close tabs.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -1690,6 +1714,8 @@ This setting is only available with the QtWebKit backend.
=== content.javascript.can_open_tabs_automatically === content.javascript.can_open_tabs_automatically
Allow JavaScript to open new tabs without user interaction. Allow JavaScript to open new tabs without user interaction.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -1698,6 +1724,8 @@ Default: +pass:[false]+
=== content.javascript.enabled === content.javascript.enabled
Enable JavaScript. Enable JavaScript.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1737,6 +1765,8 @@ Default: +pass:[true]+
=== content.local_content_can_access_file_urls === content.local_content_can_access_file_urls
Allow locally loaded documents to access other local URLs. Allow locally loaded documents to access other local URLs.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1745,6 +1775,8 @@ Default: +pass:[true]+
=== content.local_content_can_access_remote_urls === content.local_content_can_access_remote_urls
Allow locally loaded documents to access remote URLs. Allow locally loaded documents to access remote URLs.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -1753,6 +1785,8 @@ Default: +pass:[false]+
=== content.local_storage === content.local_storage
Enable support for HTML 5 local storage and Web SQL. Enable support for HTML 5 local storage and Web SQL.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1813,6 +1847,8 @@ This setting is only available with the QtWebKit backend.
=== content.plugins === content.plugins
Enable plugins in Web pages. Enable plugins in Web pages.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -1821,6 +1857,8 @@ Default: +pass:[false]+
=== content.print_element_backgrounds === content.print_element_backgrounds
Draw the background color and images also when the page is printed. Draw the background color and images also when the page is printed.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1885,6 +1923,8 @@ Default: empty
=== content.webgl === content.webgl
Enable WebGL. Enable WebGL.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -1902,6 +1942,8 @@ Default: +pass:[false]+
Monitor load requests for cross-site scripting attempts. Monitor load requests for cross-site scripting attempts.
Suspicious scripts will be blocked and reported in the inspector's JavaScript console. Enabling this feature might have an impact on performance. Suspicious scripts will be blocked and reported in the inspector's JavaScript console. Enabling this feature might have an impact on performance.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -2375,6 +2417,8 @@ Default: +pass:[false]+
=== input.links_included_in_focus_chain === input.links_included_in_focus_chain
Include hyperlinks in the keyboard focus chain when tabbing. Include hyperlinks in the keyboard focus chain when tabbing.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[true]+ Default: +pass:[true]+
@ -2402,6 +2446,8 @@ Default: +pass:[false]+
Enable spatial navigation. Enable spatial navigation.
Spatial navigation consists in the ability to navigate between focusable elements in a Web page, such as hyperlinks and form controls, by using Left, Right, Up and Down arrow keys. For example, if the user presses the Right key, heuristics determine whether there is an element he might be trying to reach towards the right and which element he probably wants. Spatial navigation consists in the ability to navigate between focusable elements in a Web page, such as hyperlinks and form controls, by using Left, Right, Up and Down arrow keys. For example, if the user presses the Right key, heuristics determine whether there is an element he might be trying to reach towards the right and which element he probably wants.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -2546,6 +2592,8 @@ Default: +pass:[false]+
Enable smooth scrolling for web pages. Enable smooth scrolling for web pages.
Note smooth scrolling does not work with the `:scroll-px` command. Note smooth scrolling does not work with the `:scroll-px` command.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+
@ -2682,6 +2730,31 @@ Valid values:
Default: +pass:[bottom]+ Default: +pass:[bottom]+
[[statusbar.widgets]]
=== statusbar.widgets
List of widgets displayed in the statusbar.
Type: <<types,List of String>>
Valid values:
* +url+: Current page URL.
* +scroll+: Percentage of the current page position like `10%`.
* +scroll_raw+: Raw percentage of the current page position like `10`.
* +history+: Display an arrow when possible to go back/forward in history.
* +tabs+: Current active tab, e.g. `2`.
* +keypress+: Display pressed keys when composing a vi command.
* +progress+: Progress bar for the current page loading.
Default:
- +pass:[keypress]+
- +pass:[url]+
- +pass:[scroll]+
- +pass:[history]+
- +pass:[tabs]+
- +pass:[progress]+
[[tabs.background]] [[tabs.background]]
=== tabs.background === tabs.background
Open new tabs (middleclick/ctrl+click) in the background. Open new tabs (middleclick/ctrl+click) in the background.
@ -2773,6 +2846,20 @@ Valid values:
Default: +pass:[ignore]+ Default: +pass:[ignore]+
[[tabs.mode_on_change]]
=== tabs.mode_on_change
When switching tabs, what input mode is applied.
Type: <<types,String>>
Valid values:
* +persist+: Retain the current mode.
* +restore+: Restore previously saved mode.
* +normal+: Always revert to normal mode.
Default: +pass:[normal]+
[[tabs.mousewheel_switching]] [[tabs.mousewheel_switching]]
=== tabs.mousewheel_switching === tabs.mousewheel_switching
Switch between tabs using the mouse wheel. Switch between tabs using the mouse wheel.
@ -2824,14 +2911,6 @@ Default:
- +pass:[right]+: +pass:[5]+ - +pass:[right]+: +pass:[5]+
- +pass:[top]+: +pass:[0]+ - +pass:[top]+: +pass:[0]+
[[tabs.persist_mode_on_change]]
=== tabs.persist_mode_on_change
Stay in insert/passthrough mode when switching tabs.
Type: <<types,Bool>>
Default: +pass:[false]+
[[tabs.pinned.shrink]] [[tabs.pinned.shrink]]
=== tabs.pinned.shrink === tabs.pinned.shrink
Shrink pinned tabs down to their contents. Shrink pinned tabs down to their contents.
@ -2993,6 +3072,7 @@ Type: <<types,FlagList>>
Valid values: Valid values:
* +host+ * +host+
* +port+
* +path+ * +path+
* +query+ * +query+
* +anchor+ * +anchor+
@ -3101,6 +3181,8 @@ Default: +pass:[512]+
=== zoom.text_only === zoom.text_only
Apply the zoom factor on a frame only to the text or to all content. Apply the zoom factor on a frame only to the text or to all content.
This setting supports URL patterns.
Type: <<types,Bool>> Type: <<types,Bool>>
Default: +pass:[false]+ Default: +pass:[false]+

View File

@ -277,6 +277,11 @@ PS C:\> Install-Package qutebrowser
---- ----
C:\> choco install qutebrowser C:\> choco install qutebrowser
---- ----
* Scoop's client
----
C:\> scoop bucket add extras
C:\> scoop install qutebrowser
----
Manual install Manual install
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -37,7 +37,7 @@ is available in the repositories:
Archlinux Archlinux
^^^^^^^^^ ^^^^^^^^^
For Archlinux, no debug informations are provided. You can either compile Qt For Archlinux, no debug information is provided. You can either compile Qt
yourself (which will take a few hours even on a modern machine) or use yourself (which will take a few hours even on a modern machine) or use
debugging symbols compiled/packaged by me (x86_64 only). debugging symbols compiled/packaged by me (x86_64 only).

View File

@ -45,8 +45,6 @@ In `command` mode:
- `QUTE_URL`: The current URL. - `QUTE_URL`: The current URL.
- `QUTE_TITLE`: The title of the current page. - `QUTE_TITLE`: The title of the current page.
- `QUTE_SELECTED_TEXT`: The text currently selected on the page. - `QUTE_SELECTED_TEXT`: The text currently selected on the page.
- `QUTE_SELECTED_HTML` The HTML currently selected on the page (not supported
with QtWebEngine).
In `hints` mode: In `hints` mode:

View File

@ -21,5 +21,5 @@ install: doc/qutebrowser.1.html
$(wildcard misc/userscripts/*) $(wildcard misc/userscripts/*)
install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/scripts/" \ install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/scripts/" \
$(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \ $(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \
scripts/testbrowser_cpp scripts/asciidoc2html.py scripts/setupcommon.py \ scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \
scripts/link_pyqt.py,$(wildcard scripts/*)) scripts/link_pyqt.py,$(wildcard scripts/*))

View File

@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
certifi==2017.11.5 certifi==2018.1.18
chardet==3.0.4 chardet==3.0.4
codecov==2.0.10 codecov==2.0.15
coverage==4.4.2 coverage==4.5.1
idna==2.6 idna==2.6
requests==2.18.4 requests==2.18.4
urllib3==1.22 urllib3==1.22

View File

@ -1,23 +1,23 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==17.3.0 attrs==17.4.0
flake8==3.5.0 flake8==3.5.0
flake8-bugbear==17.12.0 flake8-bugbear==18.2.0
flake8-builtins==1.0.post0 flake8-builtins==1.0.post0
flake8-comprehensions==1.4.1 flake8-comprehensions==1.4.1
flake8-copyright==0.2.0 flake8-copyright==0.2.0
flake8-debugger==3.0.0 flake8-debugger==3.1.0
flake8-deprecated==1.3 flake8-deprecated==1.3
flake8-docstrings==1.1.0 flake8-docstrings==1.3.0
flake8-future-import==0.4.3 flake8-future-import==0.4.4
flake8-mock==0.3 flake8-mock==0.3
flake8-per-file-ignores==0.4 flake8-per-file-ignores==0.4
flake8-polyfill==1.0.1 flake8-polyfill==1.0.2
flake8-string-format==0.2.3 flake8-string-format==0.2.3
flake8-tidy-imports==1.1.0 flake8-tidy-imports==1.1.0
flake8-tuple==0.2.13 flake8-tuple==0.2.13
mccabe==0.6.1 mccabe==0.6.1
pep8-naming==0.4.1 pep8-naming==0.5.0
pycodestyle==2.3.1 pycodestyle==2.3.1
pydocstyle==2.1.1 pydocstyle==2.1.1
pyflakes==1.6.0 pyflakes==1.6.0

View File

@ -3,6 +3,6 @@
appdirs==1.4.3 appdirs==1.4.3
packaging==16.8 packaging==16.8
pyparsing==2.2.0 pyparsing==2.2.0
setuptools==38.2.5 setuptools==38.5.1
six==1.11.0 six==1.11.0
wheel==0.30.0 wheel==0.30.0

View File

@ -1,11 +1,11 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
-e git+https://github.com/PyCQA/astroid.git#egg=astroid -e git+https://github.com/PyCQA/astroid.git#egg=astroid
certifi==2017.11.5 certifi==2018.1.18
chardet==3.0.4 chardet==3.0.4
github3.py==0.9.6 github3.py==0.9.6
idna==2.6 idna==2.6
isort==4.2.15 isort==4.3.4
lazy-object-proxy==1.3.1 lazy-object-proxy==1.3.1
mccabe==0.6.1 mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint -e git+https://github.com/PyCQA/pylint.git#egg=pylint

View File

@ -1,14 +1,14 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==1.6.0 astroid==1.6.1
certifi==2017.11.5 certifi==2018.1.18
chardet==3.0.4 chardet==3.0.4
github3.py==0.9.6 github3.py==0.9.6
idna==2.6 idna==2.6
isort==4.2.15 isort==4.3.4
lazy-object-proxy==1.3.1 lazy-object-proxy==1.3.1
mccabe==0.6.1 mccabe==0.6.1
pylint==1.8.1 pylint==1.8.2
./scripts/dev/pylint_checkers ./scripts/dev/pylint_checkers
requests==2.18.4 requests==2.18.4
six==1.11.0 six==1.11.0

View File

@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.9.2 PyQt5==5.10
sip==4.19.6 sip==4.19.7

View File

@ -1,39 +1,40 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==17.3.0 attrs==17.4.0
beautifulsoup4==4.6.0 beautifulsoup4==4.6.0
cheroot==6.0.0 cheroot==6.0.0
click==6.7 click==6.7
# colorama==0.3.9 # colorama==0.3.9
coverage==4.4.2 coverage==4.5.1
EasyProcess==0.2.3 EasyProcess==0.2.3
fields==5.0.0 fields==5.0.0
Flask==0.12.2 Flask==0.12.2
glob2==0.6 glob2==0.6
hunter==2.0.2 hunter==2.0.2
hypothesis==3.44.4 hypothesis==3.45.2
itsdangerous==0.24 itsdangerous==0.24
# Jinja2==2.10 # Jinja2==2.10
Mako==1.0.7 Mako==1.0.7
# MarkupSafe==1.0 # MarkupSafe==1.0
more-itertools==4.1.0
parse==1.8.2 parse==1.8.2
parse-type==0.4.2 parse-type==0.4.2
pluggy==0.6.0 pluggy==0.6.0
py==1.5.2 py==1.5.2
py-cpuinfo==3.3.0 py-cpuinfo==3.3.0
pytest==3.3.1 pytest==3.4.0
pytest-bdd==2.19.0 pytest-bdd==2.20.0
pytest-benchmark==3.1.1 pytest-benchmark==3.1.1
pytest-cov==2.5.1 pytest-cov==2.5.1
pytest-faulthandler==1.3.1 pytest-faulthandler==1.4.1
pytest-instafail==0.3.0 pytest-instafail==0.3.0
pytest-mock==1.6.3 pytest-mock==1.7.0
pytest-qt==2.3.0 pytest-qt==2.3.1
pytest-repeat==0.4.1 pytest-repeat==0.4.1
pytest-rerunfailures==4.0 pytest-rerunfailures==4.0
pytest-travis-fold==1.3.0 pytest-travis-fold==1.3.0
pytest-xvfb==1.0.0 pytest-xvfb==1.1.0
PyVirtualDisplay==0.2.1 PyVirtualDisplay==0.2.1
six==1.11.0 six==1.11.0
vulture==0.26 vulture==0.26
Werkzeug==0.13 Werkzeug==0.14.1

View File

@ -1,33 +0,0 @@
#!/usr/bin/env bash
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
#
# This script fetches the unprocessed HTML source for a page and opens it in vim.
# :bind gf spawn --userscript qutebrowser_viewsource
#
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
#
path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html)
curl "$QUTE_URL" > "$path"
urxvt -e vim "$path"
rm "$path"

View File

@ -13,7 +13,11 @@
from __future__ import absolute_import from __future__ import absolute_import
import codecs, os import codecs, os
tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html') tmpfile = os.path.join(
os.environ.get('QUTE_DATA_DIR',
os.path.expanduser('~/.local/share/qutebrowser')),
'userscripts/readability.html')
if not os.path.exists(os.path.dirname(tmpfile)): if not os.path.exists(os.path.dirname(tmpfile)):
os.makedirs(os.path.dirname(tmpfile)) os.makedirs(os.path.dirname(tmpfile))

52
misc/userscripts/tor_identity Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2018 jnphilipp <mail@jnphilipp.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# Change your tor identity.
#
# Set a hotkey to launch this script, then:
# :bind ti spawn --userscript tor_identity PASSWORD
#
# Use the hotkey to change your tor identity, press 'ti' to change it.
# https://stem.torproject.org/faq.html#how-do-i-request-a-new-identity-from-tor
#
import os
import sys
try:
from stem import Signal
from stem.control import Controller
except ImportError:
if os.getenv('QUTE_FIFO'):
with open(os.environ['QUTE_FIFO'], 'w') as f:
f.write('message-error "Failed to import stem."')
else:
print('Failed to import stem.')
password = sys.argv[1]
with Controller.from_port(port=9051) as controller:
controller.authenticate(password)
controller.signal(Signal.NEWNYM)
if os.getenv('QUTE_FIFO'):
with open(os.environ['QUTE_FIFO'], 'w') as f:
f.write('message-info "Tor identity changed."')
else:
print('Tor identity changed.')

View File

@ -1,4 +1,5 @@
[pytest] [pytest]
log_level = NOTSET
addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median addopts = --strict -rfEw --faulthandler-timeout=90 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
testpaths = tests testpaths = tests
markers = markers =
@ -25,6 +26,7 @@ markers =
this: Used to mark tests during development this: Used to mark tests during development
no_invalid_lines: Don't fail on unparseable lines in end2end tests no_invalid_lines: Don't fail on unparseable lines in end2end tests
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478 issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
issue3572: Tests which are broken with QtWebEngine and Qt 5.10, https://github.com/qutebrowser/qutebrowser/issues/3572
fake_os: Fake utils.is_* to a fake operating system fake_os: Fake utils.is_* to a fake operating system
unicode_locale: Tests which need an unicode locale to work unicode_locale: Tests which need an unicode locale to work
qt_log_level_fail = WARNING qt_log_level_fail = WARNING

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -22,11 +22,11 @@
import os.path import os.path
__author__ = "Florian Bruhin" __author__ = "Florian Bruhin"
__copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)" __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
__license__ = "GPL" __license__ = "GPL"
__maintainer__ = __author__ __maintainer__ = __author__
__email__ = "mail@qutebrowser.org" __email__ = "mail@qutebrowser.org"
__version_info__ = (1, 0, 4) __version_info__ = (1, 1, 1)
__version__ = '.'.join(str(e) for e in __version_info__) __version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5." __description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -64,7 +64,7 @@ from qutebrowser.completion.models import miscmodels
from qutebrowser.commands import cmdutils, runners, cmdexc from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import config, websettings, configfiles, configinit from qutebrowser.config import config, websettings, configfiles, configinit
from qutebrowser.browser import (urlmarks, adblock, history, browsertab, from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
downloads, greasemonkey) qtnetworkdownloads, downloads, greasemonkey)
from qutebrowser.browser.network import proxy from qutebrowser.browser.network import proxy
from qutebrowser.browser.webkit import cookies, cache from qutebrowser.browser.webkit import cookies, cache
from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.browser.webkit.network import networkmanager
@ -491,6 +491,10 @@ def _init_modules(args, crash_handler):
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp) diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
objreg.register('cache', diskcache) objreg.register('cache', diskcache)
log.init.debug("Initializing downloads...")
download_manager = qtnetworkdownloads.DownloadManager(parent=qApp)
objreg.register('qtnetwork-download-manager', download_manager)
log.init.debug("Initializing Greasemonkey...") log.init.debug("Initializing Greasemonkey...")
greasemonkey.init() greasemonkey.init()
@ -872,10 +876,6 @@ class EventFilter(QObject):
super().__init__(parent) super().__init__(parent)
self._activated = True self._activated = True
self._handlers = { self._handlers = {
QEvent.MouseButtonDblClick: self._handle_mouse_event,
QEvent.MouseButtonPress: self._handle_mouse_event,
QEvent.MouseButtonRelease: self._handle_mouse_event,
QEvent.MouseMove: self._handle_mouse_event,
QEvent.KeyPress: self._handle_key_event, QEvent.KeyPress: self._handle_key_event,
QEvent.KeyRelease: self._handle_key_event, QEvent.KeyRelease: self._handle_key_event,
} }
@ -900,19 +900,6 @@ class EventFilter(QObject):
# No window available yet, or not a MainWindow # No window available yet, or not a MainWindow
return False return False
def _handle_mouse_event(self, _event):
"""Handle a mouse event.
Args:
_event: The QEvent which is about to be delivered.
Return:
True if the event should be filtered, False if it's passed through.
"""
# Mouse cursor shown (overrideCursor None) -> don't filter event
# Mouse cursor hidden (overrideCursor not None) -> filter event
return qApp.overrideCursor() is not None
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
"""Handle an event. """Handle an event.

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -176,8 +176,7 @@ class HostBlocker:
self._config_blocked_hosts) self._config_blocked_hosts)
self._blocked_hosts = set() self._blocked_hosts = set()
self._done_count = 0 self._done_count = 0
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager')
scope='window', window='last-focused')
for url in config.val.content.host_blocking.lists: for url in config.val.content.host_blocking.lists:
if url.scheme() == 'file': if url.scheme() == 'file':
filename = url.toLocalFile() filename = url.toLocalFile()

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -30,7 +30,8 @@ from PyQt5.QtWidgets import QWidget, QApplication
from qutebrowser.keyinput import modeman from qutebrowser.keyinput import modeman
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import utils, objreg, usertypes, log, qtutils from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
urlutils, message)
from qutebrowser.misc import miscwidgets, objects from qutebrowser.misc import miscwidgets, objects
from qutebrowser.browser import mouse, hints from qutebrowser.browser import mouse, hints
@ -94,19 +95,24 @@ class TabData:
keep_icon: Whether the (e.g. cloned) icon should not be cleared on page keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
load. load.
inspector: The QWebInspector used for this webview. inspector: The QWebInspector used for this webview.
viewing_source: Set if we're currently showing a source view. open_target: Where to open the next link.
Only used for QtWebKit.
override_target: Override for open_target for fake clicks (like hints). override_target: Override for open_target for fake clicks (like hints).
Only used for QtWebKit. Only used for QtWebKit.
pinned: Flag to pin the tab. pinned: Flag to pin the tab.
fullscreen: Whether the tab has a video shown fullscreen currently. fullscreen: Whether the tab has a video shown fullscreen currently.
netrc_used: Whether netrc authentication was performed.
input_mode: current input mode for the tab.
""" """
keep_icon = attr.ib(False) keep_icon = attr.ib(False)
viewing_source = attr.ib(False)
inspector = attr.ib(None) inspector = attr.ib(None)
open_target = attr.ib(usertypes.ClickTarget.normal)
override_target = attr.ib(None) override_target = attr.ib(None)
pinned = attr.ib(False) pinned = attr.ib(False)
fullscreen = attr.ib(False) fullscreen = attr.ib(False)
netrc_used = attr.ib(False)
input_mode = attr.ib(usertypes.KeyMode.normal)
class AbstractAction: class AbstractAction:
@ -121,8 +127,9 @@ class AbstractAction:
action_class = None action_class = None
action_base = None action_base = None
def __init__(self): def __init__(self, tab):
self._widget = None self._widget = None
self._tab = tab
def exit_fullscreen(self): def exit_fullscreen(self):
"""Exit the fullscreen mode.""" """Exit the fullscreen mode."""
@ -139,6 +146,10 @@ class AbstractAction:
raise WebTabError("{} is not a valid web action!".format(name)) raise WebTabError("{} is not a valid web action!".format(name))
self._widget.triggerPageAction(member) self._widget.triggerPageAction(member)
def show_source(self):
"""Show the source of the current page in a new tab."""
raise NotImplementedError
class AbstractPrinting: class AbstractPrinting:
@ -245,10 +256,10 @@ class AbstractZoom(QObject):
_default_zoom_changed: Whether the zoom was changed from the default. _default_zoom_changed: Whether the zoom was changed from the default.
""" """
def __init__(self, win_id, parent=None): def __init__(self, tab, parent=None):
super().__init__(parent) super().__init__(parent)
self._tab = tab
self._widget = None self._widget = None
self._win_id = win_id
self._default_zoom_changed = False self._default_zoom_changed = False
self._init_neighborlist() self._init_neighborlist()
config.instance.changed.connect(self._on_config_changed) config.instance.changed.connect(self._on_config_changed)
@ -324,10 +335,9 @@ class AbstractCaret(QObject):
"""Attribute of AbstractTab for caret browsing.""" """Attribute of AbstractTab for caret browsing."""
def __init__(self, win_id, tab, mode_manager, parent=None): def __init__(self, tab, mode_manager, parent=None):
super().__init__(parent) super().__init__(parent)
self._tab = tab self._tab = tab
self._win_id = win_id
self._widget = None self._widget = None
self.selection_enabled = False self.selection_enabled = False
mode_manager.entered.connect(self._on_mode_entered) mode_manager.entered.connect(self._on_mode_entered)
@ -336,7 +346,7 @@ class AbstractCaret(QObject):
def _on_mode_entered(self, mode): def _on_mode_entered(self, mode):
raise NotImplementedError raise NotImplementedError
def _on_mode_left(self): def _on_mode_left(self, mode):
raise NotImplementedError raise NotImplementedError
def move_to_next_line(self, count=1): def move_to_next_line(self, count=1):
@ -390,10 +400,7 @@ class AbstractCaret(QObject):
def drop_selection(self): def drop_selection(self):
raise NotImplementedError raise NotImplementedError
def has_selection(self): def selection(self, callback):
raise NotImplementedError
def selection(self, html=False):
raise NotImplementedError raise NotImplementedError
def follow_selected(self, *, tab=False): def follow_selected(self, *, tab=False):
@ -609,6 +616,7 @@ class AbstractTab(QWidget):
process terminated. process terminated.
arg 0: A TerminationStatus member. arg 0: A TerminationStatus member.
arg 1: The exit code. arg 1: The exit code.
predicted_navigation: Emitted before we tell Qt to open a URL.
""" """
window_close_requested = pyqtSignal() window_close_requested = pyqtSignal()
@ -626,6 +634,7 @@ class AbstractTab(QWidget):
add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title add_history_item = pyqtSignal(QUrl, QUrl, str) # url, requested url, title
fullscreen_requested = pyqtSignal(bool) fullscreen_requested = pyqtSignal(bool)
renderer_process_terminated = pyqtSignal(TerminationStatus, int) renderer_process_terminated = pyqtSignal(TerminationStatus, int)
predicted_navigation = pyqtSignal(QUrl)
def __init__(self, *, win_id, mode_manager, private, parent=None): def __init__(self, *, win_id, mode_manager, private, parent=None):
self.private = private self.private = private
@ -639,16 +648,6 @@ class AbstractTab(QWidget):
tab_registry[self.tab_id] = self tab_registry[self.tab_id] = self
objreg.register('tab', self, registry=self.registry) objreg.register('tab', self, registry=self.registry)
# self.history = AbstractHistory(self)
# self.scroller = AbstractScroller(self, parent=self)
# self.caret = AbstractCaret(win_id=win_id, tab=self,
# mode_manager=mode_manager, parent=self)
# self.zoom = AbstractZoom(win_id=win_id)
# self.search = AbstractSearch(parent=self)
# self.printing = AbstractPrinting()
# self.elements = AbstractElements(self)
# self.action = AbstractAction()
self.data = TabData() self.data = TabData()
self._layout = miscwidgets.WrapperLayout(self) self._layout = miscwidgets.WrapperLayout(self)
self._widget = None self._widget = None
@ -666,6 +665,9 @@ class AbstractTab(QWidget):
objreg.register('hintmanager', hintmanager, scope='tab', objreg.register('hintmanager', hintmanager, scope='tab',
window=self.win_id, tab=self.tab_id) window=self.win_id, tab=self.tab_id)
self.predicted_navigation.connect(
lambda url: self.title_changed.emit(url.toDisplayString()))
def _set_widget(self, widget): def _set_widget(self, widget):
# pylint: disable=protected-access # pylint: disable=protected-access
self._widget = widget self._widget = widget
@ -678,6 +680,7 @@ class AbstractTab(QWidget):
self.printing._widget = widget self.printing._widget = widget
self.action._widget = widget self.action._widget = widget
self.elements._widget = widget self.elements._widget = widget
self.settings._settings = widget.settings()
self._install_event_filter() self._install_event_filter()
self.zoom.set_default() self.zoom.set_default()
@ -723,10 +726,25 @@ class AbstractTab(QWidget):
def _on_load_started(self): def _on_load_started(self):
self._progress = 0 self._progress = 0
self._has_ssl_errors = False self._has_ssl_errors = False
self.data.viewing_source = False
self._set_load_status(usertypes.LoadStatus.loading) self._set_load_status(usertypes.LoadStatus.loading)
self.load_started.emit() self.load_started.emit()
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
"""Handle common acceptNavigationRequest code."""
log.webview.debug("navigation request: url {}, type {}, is_main_frame "
"{}".format(navigation.url.toDisplayString(),
navigation.navigation_type,
navigation.is_main_frame))
if (navigation.navigation_type == navigation.Type.link_clicked and
not navigation.url.isValid()):
msg = urlutils.get_errstring(navigation.url,
"Invalid link clicked")
message.error(msg)
self.data.open_target = usertypes.ClickTarget.normal
navigation.accepted = False
def handle_auto_insert_mode(self, ok): def handle_auto_insert_mode(self, ok):
"""Handle `input.insert_mode.auto_load` after loading finished.""" """Handle `input.insert_mode.auto_load` after loading finished."""
if not config.val.input.insert_mode.auto_load or not ok: if not config.val.input.insert_mode.auto_load or not ok:
@ -749,6 +767,10 @@ class AbstractTab(QWidget):
@pyqtSlot(bool) @pyqtSlot(bool)
def _on_load_finished(self, ok): def _on_load_finished(self, ok):
if sip.isdeleted(self._widget):
# https://github.com/qutebrowser/qutebrowser/issues/3498
return
sess_manager = objreg.get('session-manager') sess_manager = objreg.get('session-manager')
sess_manager.save_autosave() sess_manager.save_autosave()
@ -761,7 +783,9 @@ class AbstractTab(QWidget):
self._set_load_status(usertypes.LoadStatus.warn) self._set_load_status(usertypes.LoadStatus.warn)
else: else:
self._set_load_status(usertypes.LoadStatus.error) self._set_load_status(usertypes.LoadStatus.error)
self.load_finished.emit(ok) self.load_finished.emit(ok)
if not self.title(): if not self.title():
self.title_changed.emit(self.url().toDisplayString()) self.title_changed.emit(self.url().toDisplayString())
@ -792,7 +816,7 @@ class AbstractTab(QWidget):
def _openurl_prepare(self, url): def _openurl_prepare(self, url):
qtutils.ensure_valid(url) qtutils.ensure_valid(url)
self.title_changed.emit(url.toDisplayString()) self.predicted_navigation.emit(url)
def openurl(self, url): def openurl(self, url):
raise NotImplementedError raise NotImplementedError
@ -811,7 +835,7 @@ class AbstractTab(QWidget):
raise NotImplementedError raise NotImplementedError
def dump_async(self, callback, *, plain=False): def dump_async(self, callback, *, plain=False):
"""Dump the current page to a file ascync. """Dump the current page's html asynchronously.
The given callback will be called with the result when dumping is The given callback will be called with the result when dumping is
complete. complete.

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -26,12 +26,9 @@ import functools
import typing import typing
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery
from PyQt5.QtGui import QKeyEvent from PyQt5.QtGui import QKeyEvent
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
import pygments
import pygments.lexers
import pygments.formatters
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
from qutebrowser.config import config, configdata from qutebrowser.config import config, configdata
@ -39,7 +36,7 @@ from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
webelem, downloads) webelem, downloads)
from qutebrowser.keyinput import modeman from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils, from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils, debug, standarddir) objreg, utils, standarddir)
from qutebrowser.utils.usertypes import KeyMode from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import urlmodel, miscmodels from qutebrowser.completion.models import urlmodel, miscmodels
@ -536,14 +533,19 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('win_id', completion=miscmodels.window) @cmdutils.argument('win_id', completion=miscmodels.window)
def tab_give(self, win_id: int = None): @cmdutils.argument('count', count=True)
def tab_give(self, win_id: int = None, count=None):
"""Give the current tab to a new or existing window if win_id given. """Give the current tab to a new or existing window if win_id given.
If no win_id is given, the tab will get detached into a new window. If no win_id is given, the tab will get detached into a new window.
Args: Args:
win_id: The window ID of the window to give the current tab to. win_id: The window ID of the window to give the current tab to.
count: Overrides win_id (index starts at 1 for win_id=0).
""" """
if count is not None:
win_id = count - 1
if win_id == self._win_id: if win_id == self._win_id:
raise cmdexc.CommandError("Can't give a tab to the same window") raise cmdexc.CommandError("Can't give a tab to the same window")
@ -638,7 +640,13 @@ class CommandDispatcher:
- `next`: Open a _next_ link. - `next`: Open a _next_ link.
- `up`: Go up a level in the current URL. - `up`: Go up a level in the current URL.
- `increment`: Increment the last number in the URL. - `increment`: Increment the last number in the URL.
Uses the
link:settings.html#url.incdec_segments[url.incdec_segments]
config option.
- `decrement`: Decrement the last number in the URL. - `decrement`: Decrement the last number in the URL.
Uses the
link:settings.html#url.incdec_segments[url.incdec_segments]
config option.
tab: Open in a new tab. tab: Open in a new tab.
bg: Open in a background tab. bg: Open in a background tab.
@ -849,14 +857,21 @@ class CommandDispatcher:
s = self._yank_url(what) s = self._yank_url(what)
what = 'URL' # For printing what = 'URL' # For printing
elif what == 'selection': elif what == 'selection':
def _selection_callback(s):
if not s:
message.info("Nothing to yank")
return
self._yank_to_target(s, sel, what, keep)
caret = self._current_widget().caret caret = self._current_widget().caret
s = caret.selection() caret.selection(callback=_selection_callback)
if not caret.has_selection() or not s: return
message.info("Nothing to yank")
return
else: # pragma: no cover else: # pragma: no cover
raise ValueError("Invalid value {!r} for `what'.".format(what)) raise ValueError("Invalid value {!r} for `what'.".format(what))
self._yank_to_target(s, sel, what, keep)
def _yank_to_target(self, s, sel, what, keep):
if sel and utils.supports_selection(): if sel and utils.supports_selection():
target = "primary selection" target = "primary selection"
else: else:
@ -953,22 +968,25 @@ class CommandDispatcher:
(prev and i < cur_idx) or (prev and i < cur_idx) or
(next_ and i > cur_idx)) (next_ and i > cur_idx))
# Check to see if we are closing any pinned tabs # close as many tabs as we can
if not force:
for i, tab in enumerate(self._tabbed_browser.widgets()):
if _to_close(i) and tab.data.pinned:
self._tabbed_browser.tab_close_prompt_if_pinned(
tab,
force,
lambda: self.tab_only(
prev=prev, next_=next_, force=True))
return
first_tab = True first_tab = True
pinned_tabs_cleanup = False
for i, tab in enumerate(self._tabbed_browser.widgets()): for i, tab in enumerate(self._tabbed_browser.widgets()):
if _to_close(i): if _to_close(i):
self._tabbed_browser.close_tab(tab, new_undo=first_tab) if force or not tab.data.pinned:
first_tab = False self._tabbed_browser.close_tab(tab, new_undo=first_tab)
first_tab = False
else:
pinned_tabs_cleanup = tab
# Check to see if we would like to close any pinned tabs
if pinned_tabs_cleanup:
self._tabbed_browser.tab_close_prompt_if_pinned(
pinned_tabs_cleanup,
force,
lambda: self.tab_only(
prev=prev, next_=next_, force=True),
text="Are you sure you want to close pinned tabs?")
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def undo(self): def undo(self):
@ -1074,14 +1092,16 @@ class CommandDispatcher:
Focuses window if necessary when index is given. If both index and Focuses window if necessary when index is given. If both index and
count are given, use count. count are given, use count.
With neither index nor count given, open the qute://tabs page.
Args: Args:
index: The [win_id/]index of the tab to focus. Or a substring index: The [win_id/]index of the tab to focus. Or a substring
in which case the closest match will be focused. in which case the closest match will be focused.
count: The tab index to focus, starting with 1. count: The tab index to focus, starting with 1.
""" """
if count is None and index is None: if count is None and index is None:
raise cmdexc.CommandError("buffer: Either a count or the argument " self.openurl('qute://tabs/', tab=True)
"index must be specified.") return
if count is not None: if count is not None:
index = str(count) index = str(count)
@ -1096,7 +1116,8 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', choices=['last']) @cmdutils.argument('index', choices=['last'])
@cmdutils.argument('count', count=True) @cmdutils.argument('count', count=True)
def tab_focus(self, index: typing.Union[str, int] = None, count=None): def tab_focus(self, index: typing.Union[str, int] = None,
count=None, no_last=False):
"""Select the tab given as argument/[count]. """Select the tab given as argument/[count].
If neither count nor index are given, it behaves like tab-next. If neither count nor index are given, it behaves like tab-next.
@ -1108,13 +1129,14 @@ class CommandDispatcher:
Negative indices count from the end, such that -1 is the Negative indices count from the end, such that -1 is the
last tab. last tab.
count: The tab index to focus, starting with 1. count: The tab index to focus, starting with 1.
no_last: Whether to avoid focusing last tab if already focused.
""" """
index = count if count is not None else index index = count if count is not None else index
if index == 'last': if index == 'last':
self._tab_focus_last() self._tab_focus_last()
return return
elif index == self._current_index() + 1: elif not no_last and index == self._current_index() + 1:
self._tab_focus_last(show_error=False) self._tab_focus_last(show_error=False)
return return
elif index is None: elif index is None:
@ -1204,9 +1226,29 @@ class CommandDispatcher:
log.procs.debug("Executing {} with args {}, userscript={}".format( log.procs.debug("Executing {} with args {}, userscript={}".format(
cmd, args, userscript)) cmd, args, userscript))
@pyqtSlot()
def _on_proc_finished():
if output:
tb = objreg.get('tabbed-browser', scope='window',
window='last-focused')
tb.openurl(QUrl('qute://spawn-output'), newtab=True)
if userscript: if userscript:
def _selection_callback(s):
try:
runner = self._run_userscript(s, cmd, args, verbose)
runner.finished.connect(_on_proc_finished)
except cmdexc.CommandError as e:
message.error(str(e))
# ~ expansion is handled by the userscript module. # ~ expansion is handled by the userscript module.
self._run_userscript(cmd, *args, verbose=verbose) # dirty hack for async call because of:
# https://bugreports.qt.io/browse/QTBUG-53134
# until it fixed or blocked async call implemented:
# https://github.com/qutebrowser/qutebrowser/issues/3327
caret = self._current_widget().caret
caret.selection(callback=_selection_callback)
else: else:
cmd = os.path.expanduser(cmd) cmd = os.path.expanduser(cmd)
proc = guiprocess.GUIProcess(what='command', verbose=verbose, proc = guiprocess.GUIProcess(what='command', verbose=verbose,
@ -1215,18 +1257,14 @@ class CommandDispatcher:
proc.start_detached(cmd, args) proc.start_detached(cmd, args)
else: else:
proc.start(cmd, args) proc.start(cmd, args)
proc.finished.connect(_on_proc_finished)
if output:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True)
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def home(self): def home(self):
"""Open main startpage in current tab.""" """Open main startpage in current tab."""
self.openurl(config.val.url.start_pages[0]) self.openurl(config.val.url.start_pages[0])
def _run_userscript(self, cmd, *args, verbose=False): def _run_userscript(self, selection, cmd, args, verbose):
"""Run a userscript given as argument. """Run a userscript given as argument.
Args: Args:
@ -1236,21 +1274,15 @@ class CommandDispatcher:
""" """
env = { env = {
'QUTE_MODE': 'command', 'QUTE_MODE': 'command',
'QUTE_SELECTED_TEXT': selection,
} }
idx = self._current_index() idx = self._current_index()
if idx != -1: if idx != -1:
env['QUTE_TITLE'] = self._tabbed_browser.page_title(idx) env['QUTE_TITLE'] = self._tabbed_browser.page_title(idx)
tab = self._tabbed_browser.currentWidget()
if tab is not None and tab.caret.has_selection():
env['QUTE_SELECTED_TEXT'] = tab.caret.selection()
try:
env['QUTE_SELECTED_HTML'] = tab.caret.selection(html=True)
except browsertab.UnsupportedOperationError:
pass
# FIXME:qtwebengine: If tab is None, run_async will fail! # FIXME:qtwebengine: If tab is None, run_async will fail!
tab = self._tabbed_browser.currentWidget()
try: try:
url = self._tabbed_browser.current_url() url = self._tabbed_browser.current_url()
@ -1260,10 +1292,11 @@ class CommandDispatcher:
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded) env['QUTE_URL'] = url.toString(QUrl.FullyEncoded)
try: try:
userscripts.run_async(tab, cmd, *args, win_id=self._win_id, runner = userscripts.run_async(
env=env, verbose=verbose) tab, cmd, *args, win_id=self._win_id, env=env, verbose=verbose)
except userscripts.Error as e: except userscripts.Error as e:
raise cmdexc.CommandError(e) raise cmdexc.CommandError(e)
return runner
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def quickmark_save(self): def quickmark_save(self):
@ -1325,7 +1358,8 @@ class CommandDispatcher:
link:qute://bookmarks[bookmarks page]. link:qute://bookmarks[bookmarks page].
Args: Args:
url: url to save as a bookmark. If None, use url of current page. url: url to save as a bookmark. If not given, use url of current
page.
title: title of the new bookmark. title: title of the new bookmark.
toggle: remove the bookmark instead of raising an error if it toggle: remove the bookmark instead of raising an error if it
already exists. already exists.
@ -1334,7 +1368,7 @@ class CommandDispatcher:
raise cmdexc.CommandError('Title must be provided if url has ' raise cmdexc.CommandError('Title must be provided if url has '
'been provided') 'been provided')
bookmark_manager = objreg.get('bookmark-manager') bookmark_manager = objreg.get('bookmark-manager')
if url is None: if not url:
url = self._current_url() url = self._current_url()
else: else:
try: try:
@ -1434,8 +1468,7 @@ class CommandDispatcher:
mhtml_: Download the current page and all assets as mhtml file. mhtml_: Download the current page and all assets as mhtml file.
""" """
# FIXME:qtwebengine do this with the QtWebEngine download manager? # FIXME:qtwebengine do this with the QtWebEngine download manager?
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager')
scope='window', window=self._win_id)
target = None target = None
if dest is not None: if dest is not None:
dest = downloads.transform_path(dest) dest = downloads.transform_path(dest)
@ -1480,34 +1513,26 @@ class CommandDispatcher:
) )
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
def view_source(self): def view_source(self, edit=False):
"""Show the source of the current page in a new tab.""" """Show the source of the current page in a new tab.
tab = self._current_widget()
if tab.data.viewing_source:
raise cmdexc.CommandError("Already viewing source!")
Args:
edit: Edit the source in the editor instead of opening a tab.
"""
tab = self._current_widget()
try: try:
current_url = self._current_url() current_url = self._current_url()
except cmdexc.CommandError as e: except cmdexc.CommandError as e:
message.error(str(e)) message.error(str(e))
return return
if current_url.scheme() == 'view-source':
raise cmdexc.CommandError("Already viewing source!")
def show_source_cb(source): if edit:
"""Show source as soon as it's ready.""" ed = editor.ExternalEditor(self._tabbed_browser)
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491 tab.dump_async(ed.edit)
# pylint: disable=no-member else:
lexer = pygments.lexers.HtmlLexer() tab.action.show_source()
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table',
title='Source for {}'.format(current_url.toDisplayString()))
# pylint: enable=no-member
highlighted = pygments.highlight(source, lexer, formatter)
new_tab = self._tabbed_browser.tabopen()
new_tab.set_html(highlighted)
new_tab.data.viewing_source = True
tab.dump_async(show_source_cb)
@cmdutils.register(instance='command-dispatcher', scope='window', @cmdutils.register(instance='command-dispatcher', scope='window',
debug=True) debug=True)
@ -1613,9 +1638,11 @@ class CommandDispatcher:
caret_position = elem.caret_position() caret_position = elem.caret_position()
ed = editor.ExternalEditor(self._tabbed_browser) ed = editor.ExternalEditor(watch=True, parent=self._tabbed_browser)
ed.editing_finished.connect(functools.partial( ed.file_updated.connect(functools.partial(
self.on_editing_finished, elem)) self.on_file_updated, elem))
ed.editing_finished.connect(lambda: mainwindow.raise_window(
objreg.last_focused_window(), alert=False))
ed.edit(text, caret_position) ed.edit(text, caret_position)
@cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.register(instance='command-dispatcher', scope='window')
@ -1628,10 +1655,10 @@ class CommandDispatcher:
tab = self._current_widget() tab = self._current_widget()
tab.elements.find_focused(self._open_editor_cb) tab.elements.find_focused(self._open_editor_cb)
def on_editing_finished(self, elem, text): def on_file_updated(self, elem, text):
"""Write the editor text into the form field and clean up tempfile. """Write the editor text into the form field and clean up tempfile.
Callback for GUIProcess when the editor was closed. Callback for GUIProcess when the edited text was updated.
Args: Args:
elem: The WebElementWrapper which was modified. elem: The WebElementWrapper which was modified.
@ -2137,7 +2164,7 @@ class CommandDispatcher:
ed = editor.ExternalEditor(self._tabbed_browser) ed = editor.ExternalEditor(self._tabbed_browser)
# Passthrough for openurl args (e.g. -t, -b, -w) # Passthrough for openurl args (e.g. -t, -b, -w)
ed.editing_finished.connect(functools.partial( ed.file_updated.connect(functools.partial(
self._open_if_changed, old_url=old_url, bg=bg, tab=tab, self._open_if_changed, old_url=old_url, bg=bg, tab=tab,
window=window, private=private, related=related)) window=window, private=private, related=related))
@ -2195,11 +2222,4 @@ class CommandDispatcher:
return return
window = self._tabbed_browser.window() window = self._tabbed_browser.window()
if window.isFullScreen(): window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)
window.setWindowState(
window.state_before_fullscreen & ~Qt.WindowFullScreen)
else:
window.state_before_fullscreen = window.windowState()
window.showFullScreen()
log.misc.debug('state before fullscreen: {}'.format(
debug.qflags_key(Qt, window.state_before_fullscreen)))

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -31,7 +31,7 @@ import enum
import sip import sip
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
QTimer, QAbstractListModel) QTimer, QAbstractListModel, QUrl)
from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config import config from qutebrowser.config import config
@ -166,6 +166,7 @@ def get_filename_question(*, suggested_filename, url, parent=None):
q.title = "Save file to:" q.title = "Save file to:"
q.text = "Please enter a location for <b>{}</b>".format( q.text = "Please enter a location for <b>{}</b>".format(
html.escape(url.toDisplayString())) html.escape(url.toDisplayString()))
q.url = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
q.mode = usertypes.PromptMode.download q.mode = usertypes.PromptMode.download
q.completed.connect(q.deleteLater) q.completed.connect(q.deleteLater)
q.default = _path_suggestion(suggested_filename) q.default = _path_suggestion(suggested_filename)

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2017-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -23,7 +23,6 @@ import re
import os import os
import json import json
import fnmatch import fnmatch
import functools
import glob import glob
import attr import attr
@ -135,7 +134,7 @@ class GreasemonkeyManager(QObject):
Signals: Signals:
scripts_reloaded: Emitted when scripts are reloaded from disk. scripts_reloaded: Emitted when scripts are reloaded from disk.
Any cached or already-injected scripts should be Any cached or already-injected scripts should be
considered obselete. considered obsolete.
""" """
scripts_reloaded = pyqtSignal() scripts_reloaded = pyqtSignal()
@ -178,10 +177,10 @@ class GreasemonkeyManager(QObject):
elif script.run_at == 'document-idle': elif script.run_at == 'document-idle':
self._run_idle.append(script) self._run_idle.append(script)
else: else:
log.greasemonkey.warning("Script {} has invalid run-at " if script.run_at:
"defined, defaulting to " log.greasemonkey.warning(
"document-end" "Script {} has invalid run-at defined, "
.format(script_path)) "defaulting to document-end".format(script_path))
# Default as per # Default as per
# https://wiki.greasespot.net/Metadata_Block#.40run-at # https://wiki.greasespot.net/Metadata_Block#.40run-at
self._run_end.append(script) self._run_end.append(script)
@ -196,11 +195,23 @@ class GreasemonkeyManager(QObject):
""" """
if url.scheme() not in self.greaseable_schemes: if url.scheme() not in self.greaseable_schemes:
return MatchingScripts(url, [], [], []) return MatchingScripts(url, [], [], [])
match = functools.partial(fnmatch.fnmatch,
url.toString(QUrl.FullyEncoded)) string_url = url.toString(QUrl.FullyEncoded)
def _match(pattern):
# For include and exclude rules if they start and end with '/' they
# should be treated as a (ecma syntax) regular expression.
if pattern.startswith('/') and pattern.endswith('/'):
matches = re.search(pattern[1:-1], string_url, flags=re.I)
return matches is not None
# Otherwise they are glob expressions.
return fnmatch.fnmatch(string_url, pattern)
tester = (lambda script: tester = (lambda script:
any(match(pat) for pat in script.includes) and any(_match(pat) for pat in script.includes) and
not any(match(pat) for pat in script.excludes)) not any(_match(pat) for pat in script.excludes))
return MatchingScripts( return MatchingScripts(
url, url,
[script for script in self._run_start if tester(script)], [script for script in self._run_start if tester(script)],

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -291,8 +291,7 @@ class HintActions:
user_agent = context.tab.user_agent() user_agent = context.tab.user_agent()
# FIXME:qtwebengine do this with QtWebEngine downloads? # FIXME:qtwebengine do this with QtWebEngine downloads?
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager')
scope='window', window=self._win_id)
download_manager.get(url, qnam=qnam, user_agent=user_agent, download_manager.get(url, qnam=qnam, user_agent=user_agent,
prompt_download_directory=prompt) prompt_download_directory=prompt)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2015-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -32,7 +32,7 @@ from qutebrowser.misc import objects, sql
# increment to indicate that HistoryCompletion must be regenerated # increment to indicate that HistoryCompletion must be regenerated
_USER_VERSION = 1 _USER_VERSION = 2
class CompletionHistory(sql.SqlTable): class CompletionHistory(sql.SqlTable):
@ -102,7 +102,8 @@ class WebHistory(sql.SqlTable):
data = {'url': [], 'title': [], 'last_atime': []} data = {'url': [], 'title': [], 'last_atime': []}
# select the latest entry for each url # select the latest entry for each url
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History ' q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
'WHERE NOT redirect GROUP BY url ORDER BY atime asc') 'WHERE NOT redirect and url NOT LIKE "qute://back%" '
'GROUP BY url ORDER BY atime asc')
for entry in q.run(): for entry in q.run():
data['url'].append(self._format_completion_url(QUrl(entry.url))) data['url'].append(self._format_completion_url(QUrl(entry.url)))
data['title'].append(entry.title) data['title'].append(entry.title)
@ -171,7 +172,9 @@ class WebHistory(sql.SqlTable):
@pyqtSlot(QUrl, QUrl, str) @pyqtSlot(QUrl, QUrl, str)
def add_from_tab(self, url, requested_url, title): def add_from_tab(self, url, requested_url, title):
"""Add a new history entry as slot, called from a BrowserTab.""" """Add a new history entry as slot, called from a BrowserTab."""
if url.scheme() == 'data' or requested_url.scheme() == 'data': if any(url.scheme() in ('data', 'view-source') or
(url.scheme(), url.host()) == ('qute', 'back')
for url in (url, requested_url)):
return return
if url.isEmpty(): if url.isEmpty():
# things set via setHtml # things set via setHtml

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -61,6 +61,9 @@ class ProxyFactory(QNetworkProxyFactory):
""" """
proxy = config.val.content.proxy proxy = config.val.content.proxy
if proxy is configtypes.SYSTEM_PROXY: if proxy is configtypes.SYSTEM_PROXY:
# On Linux, use "export http_proxy=socks5://host:port" to manually
# set system proxy.
# ref. http://doc.qt.io/qt-5/qnetworkproxyfactory.html#systemProxyForQuery
proxies = QNetworkProxyFactory.systemProxyForQuery(query) proxies = QNetworkProxyFactory.systemProxyForQuery(query)
elif isinstance(proxy, pac.PACFetcher): elif isinstance(proxy, pac.PACFetcher):
proxies = proxy.resolve(query) proxies = proxy.resolve(query)

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -20,6 +20,7 @@
"""Download manager.""" """Download manager."""
import io import io
import os.path
import shutil import shutil
import functools import functools
@ -198,21 +199,23 @@ class DownloadItem(downloads.AbstractDownloadItem):
def _ask_confirm_question(self, title, msg): def _ask_confirm_question(self, title, msg):
no_action = functools.partial(self.cancel, remove_data=False) no_action = functools.partial(self.cancel, remove_data=False)
url = 'file://{}'.format(self._filename)
message.confirm_async(title=title, text=msg, message.confirm_async(title=title, text=msg,
yes_action=self._after_set_filename, yes_action=self._after_set_filename,
no_action=no_action, cancel_action=no_action, no_action=no_action, cancel_action=no_action,
abort_on=[self.cancelled, self.error]) abort_on=[self.cancelled, self.error], url=url)
def _ask_create_parent_question(self, title, msg, def _ask_create_parent_question(self, title, msg,
force_overwrite, remember_directory): force_overwrite, remember_directory):
no_action = functools.partial(self.cancel, remove_data=False) no_action = functools.partial(self.cancel, remove_data=False)
url = 'file://{}'.format(os.path.dirname(self._filename))
message.confirm_async(title=title, text=msg, message.confirm_async(title=title, text=msg,
yes_action=(lambda: yes_action=(lambda:
self._after_create_parent_question( self._after_create_parent_question(
force_overwrite, force_overwrite,
remember_directory)), remember_directory)),
no_action=no_action, cancel_action=no_action, no_action=no_action, cancel_action=no_action,
abort_on=[self.cancelled, self.error]) abort_on=[self.cancelled, self.error], url=url)
def _set_fileobj(self, fileobj, *, autoclose=True): def _set_fileobj(self, fileobj, *, autoclose=True):
"""Set the file object to write the download to. """Set the file object to write the download to.
@ -378,10 +381,10 @@ class DownloadManager(downloads.AbstractDownloadManager):
_networkmanager: A NetworkManager for generic downloads. _networkmanager: A NetworkManager for generic downloads.
""" """
def __init__(self, win_id, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self._networkmanager = networkmanager.NetworkManager( self._networkmanager = networkmanager.NetworkManager(
win_id=win_id, tab_id=None, win_id=None, tab_id=None,
private=config.val.content.private_browsing, parent=self) private=config.val.content.private_browsing, parent=self)
@pyqtSlot('QUrl') @pyqtSlot('QUrl')

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -30,8 +30,10 @@ import time
import textwrap import textwrap
import mimetypes import mimetypes
import urllib import urllib
import collections
import pkg_resources import pkg_resources
import sip
from PyQt5.QtCore import QUrlQuery, QUrl from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser import qutebrowser
@ -201,6 +203,27 @@ def qute_bookmarks(_url):
return 'text/html', html return 'text/html', html
@add_handler('tabs')
def qute_tabs(_url):
"""Handler for qute://tabs. Display information about all open tabs."""
tabs = collections.defaultdict(list)
for win_id, window in objreg.window_registry.items():
if sip.isdeleted(window):
continue
tabbed_browser = objreg.get('tabbed-browser',
scope='window',
window=win_id)
for tab in tabbed_browser.widgets():
if tab.url() not in [QUrl("qute://tabs/"), QUrl("qute://tabs")]:
urlstr = tab.url().toDisplayString()
tabs[str(win_id)].append((tab.title(), urlstr))
html = jinja.render('tabs.html',
title='Tabs',
tab_list_by_window=tabs)
return 'text/html', html
def history_data(start_time, offset=None): def history_data(start_time, offset=None):
"""Return history data. """Return history data.
@ -435,6 +458,22 @@ def qute_settings(url):
return 'text/html', html return 'text/html', html
@add_handler('bindings')
def qute_bindings(_url):
"""Handler for qute://bindings. View keybindings."""
bindings = {}
defaults = config.val.bindings.default
modes = set(defaults.keys()).union(config.val.bindings.commands)
modes.remove('normal')
modes = ['normal'] + sorted(list(modes))
for mode in modes:
bindings[mode] = config.key_instance.get_bindings_for(mode)
html = jinja.render('bindings.html', title='Bindings',
bindings=bindings)
return 'text/html', html
@add_handler('back') @add_handler('back')
def qute_back(url): def qute_back(url):
"""Handler for qute://back. """Handler for qute://back.
@ -460,3 +499,10 @@ def qute_configdiff(url):
else: else:
data = config.instance.dump_userconfig().encode('utf-8') data = config.instance.dump_userconfig().encode('utf-8')
return 'text/plain', data return 'text/plain', data
@add_handler('pastebin-version')
def qute_pastebin_version(_url):
"""Handler that pastebins the version string."""
version.pastebin_version()
return 'text/plain', b'Paste called.'

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -19,7 +19,11 @@
"""Various utilities shared between webpage/webview subclasses.""" """Various utilities shared between webpage/webview subclasses."""
import os
import html import html
import netrc
from PyQt5.QtCore import QUrl
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
@ -27,7 +31,6 @@ from qutebrowser.mainwindow import mainwindow
class CallSuper(Exception): class CallSuper(Exception):
"""Raised when the caller should call the superclass instead.""" """Raised when the caller should call the superclass instead."""
@ -61,9 +64,10 @@ def authentication_required(url, authenticator, abort_on):
else: else:
msg = '<b>{}</b> needs authentication'.format( msg = '<b>{}</b> needs authentication'.format(
html.escape(url.toDisplayString())) html.escape(url.toDisplayString()))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
answer = message.ask(title="Authentication required", text=msg, answer = message.ask(title="Authentication required", text=msg,
mode=usertypes.PromptMode.user_pwd, mode=usertypes.PromptMode.user_pwd,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
if answer is not None: if answer is not None:
authenticator.setUser(answer.user) authenticator.setUser(answer.user)
authenticator.setPassword(answer.password) authenticator.setPassword(answer.password)
@ -78,9 +82,10 @@ def javascript_confirm(url, js_msg, abort_on):
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()), msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg)) html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
ans = message.ask('Javascript confirm', msg, ans = message.ask('Javascript confirm', msg,
mode=usertypes.PromptMode.yesno, mode=usertypes.PromptMode.yesno,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
return bool(ans) return bool(ans)
@ -94,10 +99,11 @@ def javascript_prompt(url, js_msg, default, abort_on):
msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()), msg = '<b>{}</b> asks:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg)) html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
answer = message.ask('Javascript prompt', msg, answer = message.ask('Javascript prompt', msg,
mode=usertypes.PromptMode.text, mode=usertypes.PromptMode.text,
default=default, default=default,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
if answer is None: if answer is None:
return (False, "") return (False, "")
@ -116,8 +122,9 @@ def javascript_alert(url, js_msg, abort_on):
msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()), msg = 'From <b>{}</b>:<br/>{}'.format(html.escape(url.toDisplayString()),
html.escape(js_msg)) html.escape(js_msg))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert, message.ask('Javascript alert', msg, mode=usertypes.PromptMode.alert,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
def javascript_log_message(level, source, line, msg): def javascript_log_message(level, source, line, msg):
@ -164,9 +171,10 @@ def ignore_certificate_errors(url, errors, abort_on):
""".strip()) """.strip())
msg = err_template.render(url=url, errors=errors) msg = err_template.render(url=url, errors=errors)
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
ignore = message.ask(title="Certificate errors - continue?", text=msg, ignore = message.ask(title="Certificate errors - continue?", text=msg,
mode=usertypes.PromptMode.yesno, default=False, mode=usertypes.PromptMode.yesno, default=False,
abort_on=abort_on) abort_on=abort_on, url=urlstr)
if ignore is None: if ignore is None:
# prompt aborted # prompt aborted
ignore = False ignore = False
@ -202,15 +210,17 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
config_val = config.instance.get(option) config_val = config.instance.get(option)
if config_val == 'ask': if config_val == 'ask':
if url.isValid(): if url.isValid():
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
text = "Allow the website at <b>{}</b> to {}?".format( text = "Allow the website at <b>{}</b> to {}?".format(
html.escape(url.toDisplayString()), msg) html.escape(url.toDisplayString()), msg)
else: else:
urlstr = None
text = "Allow the website to {}?".format(msg) text = "Allow the website to {}?".format(msg)
return message.confirm_async( return message.confirm_async(
yes_action=yes_action, no_action=no_action, yes_action=yes_action, no_action=no_action,
cancel_action=no_action, abort_on=abort_on, cancel_action=no_action, abort_on=abort_on,
title='Permission request', text=text) title='Permission request', text=text, url=urlstr)
elif config_val: elif config_val:
yes_action() yes_action()
return None return None
@ -260,3 +270,41 @@ def get_user_stylesheet():
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }' css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
return css return css
def netrc_authentication(url, authenticator):
"""Perform authorization using netrc.
Args:
url: The URL the request was done for.
authenticator: QAuthenticator object used to set credentials provided.
Return:
True if netrc found credentials for the URL.
False otherwise.
"""
if 'HOME' not in os.environ:
# We'll get an OSError by netrc if 'HOME' isn't available in
# os.environ. We don't want to log that, so we prevent it
# altogether.
return False
user, password = None, None
try:
net = netrc.netrc(config.val.content.netrc_file)
authenticators = net.authenticators(url.host())
if authenticators is not None:
(user, _account, password) = authenticators
except FileNotFoundError:
log.misc.debug("No .netrc file found")
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
authenticator.setUser(user)
authenticator.setPassword(password)
return True

View File

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

View File

@ -1,7 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Antoni Boucher <bouanto@zoho.com> # Copyright 2015-2018 Antoni Boucher <bouanto@zoho.com>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -161,7 +161,7 @@ class QuickmarkManager(UrlMarkManager):
"Add quickmark:", usertypes.PromptMode.text, "Add quickmark:", usertypes.PromptMode.text,
functools.partial(self.quickmark_add, urlstr), functools.partial(self.quickmark_add, urlstr),
text="Please enter a quickmark name for<br/><b>{}</b>".format( text="Please enter a quickmark name for<br/><b>{}</b>".format(
html.escape(url.toDisplayString()))) html.escape(url.toDisplayString())), url=urlstr)
@cmdutils.register(instance='quickmark-manager') @cmdutils.register(instance='quickmark-manager')
def quickmark_add(self, url, name): def quickmark_add(self, url, name):
@ -192,7 +192,7 @@ class QuickmarkManager(UrlMarkManager):
if name in self.marks: if name in self.marks:
message.confirm_async( message.confirm_async(
title="Override existing quickmark?", title="Override existing quickmark?",
yes_action=set_mark, default=True) yes_action=set_mark, default=True, url=url)
else: else:
set_mark() set_mark()
@ -280,7 +280,7 @@ class BookmarkManager(UrlMarkManager):
if urlstr in self.marks: if urlstr in self.marks:
if toggle: if toggle:
del self.marks[urlstr] self.delete(urlstr)
return False return False
else: else:
raise AlreadyExistsError("Bookmark already exists!") raise AlreadyExistsError("Bookmark already exists!")

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -42,7 +42,9 @@ Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
SELECTORS = { SELECTORS = {
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, ' Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, link, [onclick], [onmousedown], [role=link], ' 'frame, iframe, link, [onclick], [onmousedown], [role=link], '
'[role=option], [role=button], img'), '[role=option], [role=button], img, '
# Angular 1 selectors
'[ng-click], [ngClick], [data-ng-click], [x-ng-click]'),
Group.links: 'a[href], area[href], link[href], [role=link][href]', Group.links: 'a[href], area[href], link[href], [role=link][href]',
Group.images: 'img', Group.images: 'img',
Group.url: '[src], [href]', Group.url: '[src], [href]',

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Michal Siedlaczek <michal.siedlaczek@gmail.com> # Copyright 2017-2018 Michal Siedlaczek <michal.siedlaczek@gmail.com>
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -45,6 +45,10 @@ class DownloadItem(downloads.AbstractDownloadItem):
qt_item.downloadProgress.connect(self.stats.on_download_progress) qt_item.downloadProgress.connect(self.stats.on_download_progress)
qt_item.stateChanged.connect(self._on_state_changed) qt_item.stateChanged.connect(self._on_state_changed)
# Ensure wrapped qt_item is deleted manually when the wrapper object
# is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373
self.destroyed.connect(self._qt_item.deleteLater)
def _is_page_download(self): def _is_page_download(self):
"""Check if this item is a page (i.e. mhtml) download.""" """Check if this item is a page (i.e. mhtml) download."""
return (self._qt_item.savePageFormat() != return (self._qt_item.savePageFormat() !=
@ -96,9 +100,15 @@ class DownloadItem(downloads.AbstractDownloadItem):
self._qt_item.cancel() self._qt_item.cancel()
def retry(self): def retry(self):
# https://bugreports.qt.io/browse/QTBUG-56840 state = self._qt_item.state()
raise downloads.UnsupportedOperationError( assert state == QWebEngineDownloadItem.DownloadInterrupted, state
"Retrying downloads is unsupported with QtWebEngine")
try:
self._qt_item.resume()
except AttributeError:
raise downloads.UnsupportedOperationError(
"Retrying downloads is unsupported with QtWebEngine on "
"Qt/PyQt < 5.10")
def _get_open_filename(self): def _get_open_filename(self):
return self._filename return self._filename
@ -125,6 +135,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
question = usertypes.Question() question = usertypes.Question()
question.title = title question.title = title
question.text = msg question.text = msg
question.url = 'file://{}'.format(self._filename)
question.mode = usertypes.PromptMode.yesno question.mode = usertypes.PromptMode.yesno
question.answered_yes.connect(self._after_set_filename) question.answered_yes.connect(self._after_set_filename)
question.answered_no.connect(no_action) question.answered_no.connect(no_action)
@ -139,6 +150,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
question = usertypes.Question() question = usertypes.Question()
question.title = title question.title = title
question.text = msg question.text = msg
question.url = 'file://{}'.format(os.path.dirname(self._filename))
question.mode = usertypes.PromptMode.yesno question.mode = usertypes.PromptMode.yesno
question.answered_yes.connect(lambda: question.answered_yes.connect(lambda:
self._after_create_parent_question( self._after_create_parent_question(

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -17,9 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# We get various "abstract but not overridden" warnings
# pylint: disable=abstract-method
"""Bridge from QWebEngineSettings to our own settings. """Bridge from QWebEngineSettings to our own settings.
Module attributes: Module attributes:
@ -44,116 +41,143 @@ from qutebrowser.utils import (utils, standarddir, javascript, qtutils,
default_profile = None default_profile = None
# The QWebEngineProfile used for private (off-the-record) windows # The QWebEngineProfile used for private (off-the-record) windows
private_profile = None private_profile = None
# The global WebEngineSettings object
global_settings = None
class Base(websettings.Base): class _SettingsWrapper:
"""Base settings class with appropriate _get_global_settings.""" """Expose a QWebEngineSettings interface which acts on all profiles.
def _get_global_settings(self): For read operations, the default profile value is always used.
return [default_profile.settings(), private_profile.settings()]
class Attribute(Base, websettings.Attribute):
"""A setting set via QWebEngineSettings::setAttribute."""
ENUM_BASE = QWebEngineSettings
class Setter(Base, websettings.Setter):
"""A setting set via a QWebEngineSettings setter method."""
pass
class FontFamilySetter(Base, websettings.FontFamilySetter):
"""A setter for a font family.
Gets the default value from QFont.
""" """
def __init__(self, font): def __init__(self):
# Mapping from WebEngineSettings::initDefaults in self._settings = [default_profile.settings(),
# qtwebengine/src/core/web_engine_settings.cpp private_profile.settings()]
font_to_qfont = {
QWebEngineSettings.StandardFont: QFont.Serif, def setAttribute(self, *args, **kwargs):
QWebEngineSettings.FixedFont: QFont.Monospace, for settings in self._settings:
QWebEngineSettings.SerifFont: QFont.Serif, settings.setAttribute(*args, **kwargs)
QWebEngineSettings.SansSerifFont: QFont.SansSerif,
QWebEngineSettings.CursiveFont: QFont.Cursive, def setFontFamily(self, *args, **kwargs):
QWebEngineSettings.FantasyFont: QFont.Fantasy, for settings in self._settings:
settings.setFontFamily(*args, **kwargs)
def setFontSize(self, *args, **kwargs):
for settings in self._settings:
settings.setFontSize(*args, **kwargs)
def setDefaultTextEncoding(self, *args, **kwargs):
for settings in self._settings:
settings.setDefaultTextEncoding(*args, **kwargs)
def testAttribute(self, *args, **kwargs):
return self._settings[0].testAttribute(*args, **kwargs)
def fontSize(self, *args, **kwargs):
return self._settings[0].fontSize(*args, **kwargs)
def fontFamily(self, *args, **kwargs):
return self._settings[0].fontFamily(*args, **kwargs)
def defaultTextEncoding(self, *args, **kwargs):
return self._settings[0].defaultTextEncoding(*args, **kwargs)
class WebEngineSettings(websettings.AbstractSettings):
"""A wrapper for the config for QWebEngineSettings."""
_ATTRIBUTES = {
'content.xss_auditing':
[QWebEngineSettings.XSSAuditingEnabled],
'content.images':
[QWebEngineSettings.AutoLoadImages],
'content.javascript.enabled':
[QWebEngineSettings.JavascriptEnabled],
'content.javascript.can_open_tabs_automatically':
[QWebEngineSettings.JavascriptCanOpenWindows],
'content.javascript.can_access_clipboard':
[QWebEngineSettings.JavascriptCanAccessClipboard],
'content.plugins':
[QWebEngineSettings.PluginsEnabled],
'content.hyperlink_auditing':
[QWebEngineSettings.HyperlinkAuditingEnabled],
'content.local_content_can_access_remote_urls':
[QWebEngineSettings.LocalContentCanAccessRemoteUrls],
'content.local_content_can_access_file_urls':
[QWebEngineSettings.LocalContentCanAccessFileUrls],
'content.webgl':
[QWebEngineSettings.WebGLEnabled],
'content.local_storage':
[QWebEngineSettings.LocalStorageEnabled],
'input.spatial_navigation':
[QWebEngineSettings.SpatialNavigationEnabled],
'input.links_included_in_focus_chain':
[QWebEngineSettings.LinksIncludedInFocusChain],
'scrolling.smooth':
[QWebEngineSettings.ScrollAnimatorEnabled],
# Missing QtWebEngine attributes:
# - ScreenCaptureEnabled
# - Accelerated2dCanvasEnabled
# - AutoLoadIconsForPage
# - TouchIconsEnabled
# - FocusOnNavigationEnabled (5.8)
# - AllowRunningInsecureContent (5.8)
}
_FONT_SIZES = {
'fonts.web.size.minimum':
QWebEngineSettings.MinimumFontSize,
'fonts.web.size.minimum_logical':
QWebEngineSettings.MinimumLogicalFontSize,
'fonts.web.size.default':
QWebEngineSettings.DefaultFontSize,
'fonts.web.size.default_fixed':
QWebEngineSettings.DefaultFixedFontSize,
}
_FONT_FAMILIES = {
'fonts.web.family.standard': QWebEngineSettings.StandardFont,
'fonts.web.family.fixed': QWebEngineSettings.FixedFont,
'fonts.web.family.serif': QWebEngineSettings.SerifFont,
'fonts.web.family.sans_serif': QWebEngineSettings.SansSerifFont,
'fonts.web.family.cursive': QWebEngineSettings.CursiveFont,
'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont,
# Missing QtWebEngine fonts:
# - PictographFont
}
# Mapping from WebEngineSettings::initDefaults in
# qtwebengine/src/core/web_engine_settings.cpp
_FONT_TO_QFONT = {
QWebEngineSettings.StandardFont: QFont.Serif,
QWebEngineSettings.FixedFont: QFont.Monospace,
QWebEngineSettings.SerifFont: QFont.Serif,
QWebEngineSettings.SansSerifFont: QFont.SansSerif,
QWebEngineSettings.CursiveFont: QFont.Cursive,
QWebEngineSettings.FantasyFont: QFont.Fantasy,
}
def __init__(self, settings):
super().__init__(settings)
# Attributes which don't exist in all Qt versions.
new_attributes = {
# Qt 5.8
'content.print_element_backgrounds': 'PrintElementBackgrounds',
} }
super().__init__(setter=QWebEngineSettings.setFontFamily, font=font, for name, attribute in new_attributes.items():
qfont=font_to_qfont[font]) try:
value = getattr(QWebEngineSettings, attribute)
except AttributeError:
continue
self._ATTRIBUTES[name] = [value]
class DefaultProfileSetter(websettings.Base):
"""A setting set on the QWebEngineProfile."""
def __init__(self, setter, converter=None, default=websettings.UNSET):
super().__init__(default)
self._setter = setter
self._converter = converter
def __repr__(self):
return utils.get_repr(self, setter=self._setter, constructor=True)
def _set(self, value, settings=None):
if settings is not None:
raise ValueError("'settings' may not be set with "
"DefaultProfileSetters!")
setter = getattr(default_profile, self._setter)
if self._converter is not None:
value = self._converter(value)
setter(value)
class PersistentCookiePolicy(DefaultProfileSetter):
"""The content.cookies.store setting is different from other settings."""
def __init__(self):
super().__init__('setPersistentCookiesPolicy')
def _set(self, value, settings=None):
if settings is not None:
raise ValueError("'settings' may not be set with "
"PersistentCookiePolicy!")
setter = getattr(QWebEngineProfile.defaultProfile(), self._setter)
setter(
QWebEngineProfile.AllowPersistentCookies if value else
QWebEngineProfile.NoPersistentCookies
)
class DictionaryLanguageSetter(DefaultProfileSetter):
"""Sets paths to dictionary files based on language codes."""
def __init__(self):
super().__init__('setSpellCheckLanguages', default=[])
def _find_installed(self, code):
local_filename = spell.local_filename(code)
if not local_filename:
message.warning(
"Language {} is not installed - see scripts/dictcli.py "
"in qutebrowser's sources".format(code))
return local_filename
def _set(self, value, settings=None):
if settings is not None:
raise ValueError("'settings' may not be set with "
"DictionaryLanguageSetter!")
filenames = [self._find_installed(code) for code in value]
log.config.debug("Found dicts: {}".format(filenames))
super()._set([f for f in filenames if f], settings)
def _init_stylesheet(profile): def _init_stylesheet(profile):
@ -210,9 +234,47 @@ def _set_http_headers(profile):
profile.setHttpAcceptLanguage(accept_language) profile.setHttpAcceptLanguage(accept_language)
def _set_http_cache_size(profile):
"""Initialize the HTTP cache size for the given profile."""
size = config.val.content.cache.size
if size is None:
size = 0
else:
size = qtutils.check_overflow(size, 'int', fatal=False)
# 0: automatically managed by QtWebEngine
profile.setHttpCacheMaximumSize(size)
def _set_persistent_cookie_policy(profile):
"""Set the HTTP Cookie size for the given profile."""
if config.val.content.cookies.store:
value = QWebEngineProfile.AllowPersistentCookies
else:
value = QWebEngineProfile.NoPersistentCookies
profile.setPersistentCookiesPolicy(value)
def _set_dictionary_language(profile):
filenames = []
for code in config.val.spellcheck.languages or []:
local_filename = spell.local_filename(code)
if not local_filename:
message.warning(
"Language {} is not installed - see scripts/dictcli.py "
"in qutebrowser's sources".format(code))
continue
filenames.append(local_filename)
log.config.debug("Found dicts: {}".format(filenames))
profile.setSpellCheckLanguages(filenames)
def _update_settings(option): def _update_settings(option):
"""Update global settings when qwebsettings changed.""" """Update global settings when qwebsettings changed."""
websettings.update_mappings(MAPPINGS, option) global_settings.update_setting(option)
if option in ['scrolling.bar', 'content.user_stylesheets']: if option in ['scrolling.bar', 'content.user_stylesheets']:
_init_stylesheet(default_profile) _init_stylesheet(default_profile)
_init_stylesheet(private_profile) _init_stylesheet(private_profile)
@ -221,27 +283,46 @@ def _update_settings(option):
'content.headers.accept_language']: 'content.headers.accept_language']:
_set_http_headers(default_profile) _set_http_headers(default_profile)
_set_http_headers(private_profile) _set_http_headers(private_profile)
elif option == 'content.cache.size':
_set_http_cache_size(default_profile)
_set_http_cache_size(private_profile)
elif (option == 'content.cookies.store' and
# https://bugreports.qt.io/browse/QTBUG-58650
qtutils.version_check('5.9', compiled=False)):
_set_persistent_cookie_policy(default_profile)
# We're not touching the private profile's cookie policy.
elif option == 'spellcheck.languages' and qtutils.version_check('5.8'):
_set_dictionary_language(default_profile)
_set_dictionary_language(private_profile)
def _init_profile(profile):
"""Init the given profile."""
_init_stylesheet(profile)
_set_http_headers(profile)
_set_http_cache_size(profile)
profile.settings().setAttribute(
QWebEngineSettings.FullScreenSupportEnabled, True)
if qtutils.version_check('5.8'):
profile.setSpellCheckEnabled(True)
_set_dictionary_language(profile)
def _init_profiles(): def _init_profiles():
"""Init the two used QWebEngineProfiles.""" """Init the two used QWebEngineProfiles."""
global default_profile, private_profile global default_profile, private_profile
default_profile = QWebEngineProfile.defaultProfile() default_profile = QWebEngineProfile.defaultProfile()
default_profile.setCachePath( default_profile.setCachePath(
os.path.join(standarddir.cache(), 'webengine')) os.path.join(standarddir.cache(), 'webengine'))
default_profile.setPersistentStoragePath( default_profile.setPersistentStoragePath(
os.path.join(standarddir.data(), 'webengine')) os.path.join(standarddir.data(), 'webengine'))
_init_stylesheet(default_profile) _init_profile(default_profile)
_set_http_headers(default_profile) _set_persistent_cookie_policy(default_profile)
private_profile = QWebEngineProfile() private_profile = QWebEngineProfile()
assert private_profile.isOffTheRecord() assert private_profile.isOffTheRecord()
_init_stylesheet(private_profile) _init_profile(private_profile)
_set_http_headers(private_profile)
if qtutils.version_check('5.8'):
default_profile.setSpellCheckEnabled(True)
private_profile.setSpellCheckEnabled(True)
def inject_userscripts(): def inject_userscripts():
@ -287,111 +368,13 @@ def init(args):
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port()) os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
_init_profiles() _init_profiles()
# We need to do this here as a WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58650
if not qtutils.version_check('5.9', compiled=False):
PersistentCookiePolicy().set(config.val.content.cookies.store)
Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
websettings.init_mappings(MAPPINGS)
config.instance.changed.connect(_update_settings) config.instance.changed.connect(_update_settings)
global global_settings
global_settings = WebEngineSettings(_SettingsWrapper())
global_settings.init_settings()
def shutdown(): def shutdown():
# FIXME:qtwebengine do we need to do something for a clean shutdown here? # FIXME:qtwebengine do we need to do something for a clean shutdown here?
pass pass
# Missing QtWebEngine attributes:
# - ScreenCaptureEnabled
# - Accelerated2dCanvasEnabled
# - AutoLoadIconsForPage
# - TouchIconsEnabled
# - FocusOnNavigationEnabled (5.8)
# - AllowRunningInsecureContent (5.8)
#
# Missing QtWebEngine fonts:
# - PictographFont
MAPPINGS = {
'content.images':
Attribute(QWebEngineSettings.AutoLoadImages),
'content.javascript.enabled':
Attribute(QWebEngineSettings.JavascriptEnabled),
'content.javascript.can_open_tabs_automatically':
Attribute(QWebEngineSettings.JavascriptCanOpenWindows),
'content.javascript.can_access_clipboard':
Attribute(QWebEngineSettings.JavascriptCanAccessClipboard),
'content.plugins':
Attribute(QWebEngineSettings.PluginsEnabled),
'content.hyperlink_auditing':
Attribute(QWebEngineSettings.HyperlinkAuditingEnabled),
'content.local_content_can_access_remote_urls':
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
'content.local_content_can_access_file_urls':
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
'content.webgl':
Attribute(QWebEngineSettings.WebGLEnabled),
'content.local_storage':
Attribute(QWebEngineSettings.LocalStorageEnabled),
'content.cache.size':
# 0: automatically managed by QtWebEngine
DefaultProfileSetter('setHttpCacheMaximumSize', default=0,
converter=lambda val:
qtutils.check_overflow(val, 'int', fatal=False)),
'content.xss_auditing':
Attribute(QWebEngineSettings.XSSAuditingEnabled),
'content.default_encoding':
Setter(QWebEngineSettings.setDefaultTextEncoding),
'input.spatial_navigation':
Attribute(QWebEngineSettings.SpatialNavigationEnabled),
'input.links_included_in_focus_chain':
Attribute(QWebEngineSettings.LinksIncludedInFocusChain),
'fonts.web.family.standard':
FontFamilySetter(QWebEngineSettings.StandardFont),
'fonts.web.family.fixed':
FontFamilySetter(QWebEngineSettings.FixedFont),
'fonts.web.family.serif':
FontFamilySetter(QWebEngineSettings.SerifFont),
'fonts.web.family.sans_serif':
FontFamilySetter(QWebEngineSettings.SansSerifFont),
'fonts.web.family.cursive':
FontFamilySetter(QWebEngineSettings.CursiveFont),
'fonts.web.family.fantasy':
FontFamilySetter(QWebEngineSettings.FantasyFont),
'fonts.web.size.minimum':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumFontSize]),
'fonts.web.size.minimum_logical':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumLogicalFontSize]),
'fonts.web.size.default':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFontSize]),
'fonts.web.size.default_fixed':
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFixedFontSize]),
'scrolling.smooth':
Attribute(QWebEngineSettings.ScrollAnimatorEnabled),
}
try:
MAPPINGS['content.print_element_backgrounds'] = Attribute(
QWebEngineSettings.PrintElementBackgrounds)
except AttributeError:
# Added in Qt 5.8
pass
if qtutils.version_check('5.8'):
MAPPINGS['spellcheck.languages'] = DictionaryLanguageSetter()
if qtutils.version_check('5.9', compiled=False):
# https://bugreports.qt.io/browse/QTBUG-58650
MAPPINGS['content.cookies.store'] = PersistentCookiePolicy()

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -21,6 +21,8 @@
import math import math
import functools import functools
import sys
import re
import html as html_utils import html as html_utils
import sip import sip
@ -98,6 +100,19 @@ class WebEngineAction(browsertab.AbstractAction):
"""Save the current page.""" """Save the current page."""
self._widget.triggerPageAction(QWebEnginePage.SavePage) self._widget.triggerPageAction(QWebEnginePage.SavePage)
def show_source(self):
try:
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
except AttributeError:
# Qt < 5.8
tb = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
urlstr = self._tab.url().toString(QUrl.RemoveUserInfo)
# The original URL becomes the path of a view-source: URL
# (without a host), but query/fragment should stay.
url = QUrl('view-source:' + urlstr)
tb.tabopen(url, background=False, related=True)
class WebEnginePrinting(browsertab.AbstractPrinting): class WebEnginePrinting(browsertab.AbstractPrinting):
@ -201,70 +216,90 @@ class WebEngineCaret(browsertab.AbstractCaret):
@pyqtSlot(usertypes.KeyMode) @pyqtSlot(usertypes.KeyMode)
def _on_mode_entered(self, mode): def _on_mode_entered(self, mode):
pass if mode != usertypes.KeyMode.caret:
return
self._tab.run_js_async(
javascript.assemble('caret', 'setPlatform', sys.platform))
self._js_call('setInitialCursor')
@pyqtSlot(usertypes.KeyMode) @pyqtSlot(usertypes.KeyMode)
def _on_mode_left(self): def _on_mode_left(self, mode):
pass if mode != usertypes.KeyMode.caret:
return
self.drop_selection()
self._js_call('disableCaret')
def move_to_next_line(self, count=1): def move_to_next_line(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveDown')
def move_to_prev_line(self, count=1): def move_to_prev_line(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveUp')
def move_to_next_char(self, count=1): def move_to_next_char(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveRight')
def move_to_prev_char(self, count=1): def move_to_prev_char(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveLeft')
def move_to_end_of_word(self, count=1): def move_to_end_of_word(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveToEndOfWord')
def move_to_next_word(self, count=1): def move_to_next_word(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveToNextWord')
def move_to_prev_word(self, count=1): def move_to_prev_word(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveToPreviousWord')
def move_to_start_of_line(self): def move_to_start_of_line(self):
log.stub() self._js_call('moveToStartOfLine')
def move_to_end_of_line(self): def move_to_end_of_line(self):
log.stub() self._js_call('moveToEndOfLine')
def move_to_start_of_next_block(self, count=1): def move_to_start_of_next_block(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveToStartOfNextBlock')
def move_to_start_of_prev_block(self, count=1): def move_to_start_of_prev_block(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveToStartOfPrevBlock')
def move_to_end_of_next_block(self, count=1): def move_to_end_of_next_block(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveToEndOfNextBlock')
def move_to_end_of_prev_block(self, count=1): def move_to_end_of_prev_block(self, count=1):
log.stub() for _ in range(count):
self._js_call('moveToEndOfPrevBlock')
def move_to_start_of_document(self): def move_to_start_of_document(self):
log.stub() self._js_call('moveToStartOfDocument')
def move_to_end_of_document(self): def move_to_end_of_document(self):
log.stub() self._js_call('moveToEndOfDocument')
def toggle_selection(self): def toggle_selection(self):
log.stub() self._js_call('toggleSelection')
def drop_selection(self): def drop_selection(self):
log.stub() self._js_call('dropSelection')
def has_selection(self): def selection(self, callback):
return self._widget.hasSelection() # Not using selectedText() as WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-53134
def selection(self, html=False): # Even on Qt 5.10 selectedText() seems to work poorly, see
if html: # https://github.com/qutebrowser/qutebrowser/issues/3523
raise browsertab.UnsupportedOperationError self._tab.run_js_async(javascript.assemble('caret', 'getSelection'),
return self._widget.selectedText() callback)
def _follow_selected_cb(self, js_elem, tab=False): def _follow_selected_cb(self, js_elem, tab=False):
"""Callback for javascript which clicks the selected element. """Callback for javascript which clicks the selected element.
@ -308,6 +343,10 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.run_js_async(js_code, lambda jsret: self._tab.run_js_async(js_code, lambda jsret:
self._follow_selected_cb(jsret, tab)) self._follow_selected_cb(jsret, tab))
def _js_call(self, command):
self._tab.run_js_async(
javascript.assemble('caret', command))
class WebEngineScroller(browsertab.AbstractScroller): class WebEngineScroller(browsertab.AbstractScroller):
@ -435,7 +474,8 @@ class WebEngineHistory(browsertab.AbstractHistory):
return self._history.itemAt(i) return self._history.itemAt(i)
def _go_to_item(self, item): def _go_to_item(self, item):
return self._history.goToItem(item) self._tab.predicted_navigation.emit(item.url())
self._history.goToItem(item)
def serialize(self): def serialize(self):
if not qtutils.version_check('5.9', compiled=False): if not qtutils.version_check('5.9', compiled=False):
@ -455,13 +495,18 @@ class WebEngineHistory(browsertab.AbstractHistory):
def load_items(self, items): def load_items(self, items):
stream, _data, cur_data = tabhistory.serialize(items) stream, _data, cur_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, self._history) qtutils.deserialize_stream(stream, self._history)
@pyqtSlot()
def _on_load_finished():
self._tab.scroller.to_point(cur_data['scroll-pos'])
self._tab.load_finished.disconnect(_on_load_finished)
if cur_data is not None: if cur_data is not None:
if 'zoom' in cur_data: if 'zoom' in cur_data:
self._tab.zoom.set_factor(cur_data['zoom']) self._tab.zoom.set_factor(cur_data['zoom'])
if ('scroll-pos' in cur_data and if ('scroll-pos' in cur_data and
self._tab.scroller.pos_px() == QPoint(0, 0)): self._tab.scroller.pos_px() == QPoint(0, 0)):
QTimer.singleShot(0, functools.partial( self._tab.load_finished.connect(_on_load_finished)
self._tab.scroller.to_point, cur_data['scroll-pos']))
class WebEngineZoom(browsertab.AbstractZoom): class WebEngineZoom(browsertab.AbstractZoom):
@ -557,19 +602,22 @@ class WebEngineTab(browsertab.AbstractTab):
private=private) private=private)
self.history = WebEngineHistory(self) self.history = WebEngineHistory(self)
self.scroller = WebEngineScroller(self, parent=self) self.scroller = WebEngineScroller(self, parent=self)
self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager, self.caret = WebEngineCaret(mode_manager=mode_manager,
tab=self, parent=self) tab=self, parent=self)
self.zoom = WebEngineZoom(win_id=win_id, parent=self) self.zoom = WebEngineZoom(tab=self, parent=self)
self.search = WebEngineSearch(parent=self) self.search = WebEngineSearch(parent=self)
self.printing = WebEnginePrinting() self.printing = WebEnginePrinting()
self.elements = WebEngineElements(self) self.elements = WebEngineElements(tab=self)
self.action = WebEngineAction() self.action = WebEngineAction(tab=self)
# We're assigning settings in _set_widget
self.settings = webenginesettings.WebEngineSettings(settings=None)
self._set_widget(widget) self._set_widget(widget)
self._connect_signals() self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine self.backend = usertypes.Backend.QtWebEngine
self._init_js() self._init_js()
self._child_event_filter = None self._child_event_filter = None
self._saved_zoom = None self._saved_zoom = None
self._reload_url = None
def _init_js(self): def _init_js(self):
js_code = '\n'.join([ js_code = '\n'.join([
@ -577,9 +625,12 @@ class WebEngineTab(browsertab.AbstractTab):
'window._qutebrowser = window._qutebrowser || {};', 'window._qutebrowser = window._qutebrowser || {};',
utils.read_file('javascript/scroll.js'), utils.read_file('javascript/scroll.js'),
utils.read_file('javascript/webelem.js'), utils.read_file('javascript/webelem.js'),
utils.read_file('javascript/caret.js'),
]) ])
script = QWebEngineScript() script = QWebEngineScript()
script.setInjectionPoint(QWebEngineScript.DocumentCreation) # We can't use DocumentCreation here as WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-66011
script.setInjectionPoint(QWebEngineScript.DocumentReady)
script.setSourceCode(js_code) script.setSourceCode(js_code)
page = self._widget.page() page = self._widget.page()
@ -597,6 +648,9 @@ class WebEngineTab(browsertab.AbstractTab):
@pyqtSlot() @pyqtSlot()
def _restore_zoom(self): def _restore_zoom(self):
if sip.isdeleted(self._widget):
# https://github.com/qutebrowser/qutebrowser/issues/3498
return
if self._saved_zoom is None: if self._saved_zoom is None:
return return
self.zoom.set_factor(self._saved_zoom) self.zoom.set_factor(self._saved_zoom)
@ -682,6 +736,16 @@ class WebEngineTab(browsertab.AbstractTab):
self.send_event(press_evt) self.send_event(press_evt)
self.send_event(release_evt) self.send_event(release_evt)
def _show_error_page(self, url, error):
"""Show an error page in the tab."""
log.misc.debug("Showing error page for {}".format(error))
url_string = url.toDisplayString()
error_page = jinja.render(
'error.html',
title="Error loading page: {}".format(url_string),
url=url_string, error=error)
self.set_html(error_page)
@pyqtSlot() @pyqtSlot()
def _on_history_trigger(self): def _on_history_trigger(self):
try: try:
@ -716,10 +780,11 @@ class WebEngineTab(browsertab.AbstractTab):
"""Called when a proxy needs authentication.""" """Called when a proxy needs authentication."""
msg = "<b>{}</b> requires a username and password.".format( msg = "<b>{}</b> requires a username and password.".format(
html_utils.escape(proxy_host)) html_utils.escape(proxy_host))
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
answer = message.ask( answer = message.ask(
title="Proxy authentication required", text=msg, title="Proxy authentication required", text=msg,
mode=usertypes.PromptMode.user_pwd, mode=usertypes.PromptMode.user_pwd,
abort_on=[self.shutting_down, self.load_started]) abort_on=[self.shutting_down, self.load_started], url=urlstr)
if answer is not None: if answer is not None:
authenticator.setUser(answer.user) authenticator.setUser(answer.user)
authenticator.setPassword(answer.password) authenticator.setPassword(answer.password)
@ -729,21 +794,19 @@ class WebEngineTab(browsertab.AbstractTab):
sip.assign(authenticator, QAuthenticator()) sip.assign(authenticator, QAuthenticator())
# pylint: enable=no-member, useless-suppression # pylint: enable=no-member, useless-suppression
except AttributeError: except AttributeError:
url_string = url.toDisplayString() self._show_error_page(url, "Proxy authentication required")
error_page = jinja.render(
'error.html',
title="Error loading page: {}".format(url_string),
url=url_string, error="Proxy authentication required",
icon='')
self.set_html(error_page)
@pyqtSlot(QUrl, 'QAuthenticator*') @pyqtSlot(QUrl, 'QAuthenticator*')
def _on_authentication_required(self, url, authenticator): def _on_authentication_required(self, url, authenticator):
# FIXME:qtwebengine support .netrc netrc_success = False
answer = shared.authentication_required( if not self.data.netrc_used:
url, authenticator, abort_on=[self.shutting_down, self.data.netrc_used = True
self.load_started]) netrc_success = shared.netrc_authentication(url, authenticator)
if answer is None: if not netrc_success:
abort_on = [self.shutting_down, self.load_started]
answer = shared.authentication_required(url, authenticator,
abort_on)
if not netrc_success and answer is None:
try: try:
# pylint: disable=no-member, useless-suppression # pylint: disable=no-member, useless-suppression
sip.assign(authenticator, QAuthenticator()) sip.assign(authenticator, QAuthenticator())
@ -751,12 +814,7 @@ class WebEngineTab(browsertab.AbstractTab):
except AttributeError: except AttributeError:
# WORKAROUND for # WORKAROUND for
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html # https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
url_string = url.toDisplayString() self._show_error_page(url, "Authentication required")
error_page = jinja.render(
'error.html',
title="Error loading page: {}".format(url_string),
url=url_string, error="Authentication required")
self.set_html(error_page)
@pyqtSlot('QWebEngineFullScreenRequest') @pyqtSlot('QWebEngineFullScreenRequest')
def _on_fullscreen_requested(self, request): def _on_fullscreen_requested(self, request):
@ -779,6 +837,7 @@ class WebEngineTab(browsertab.AbstractTab):
# https://bugreports.qt.io/browse/QTBUG-61506 # https://bugreports.qt.io/browse/QTBUG-61506
self.search.clear() self.search.clear()
super()._on_load_started() super()._on_load_started()
self.data.netrc_used = False
@pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int) @pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int)
def _on_render_process_terminated(self, status, exitcode): def _on_render_process_terminated(self, status, exitcode):
@ -820,6 +879,54 @@ class WebEngineTab(browsertab.AbstractTab):
if not ok: if not ok:
self._load_finished_fake.emit(False) self._load_finished_fake.emit(False)
def _error_page_workaround(self, html):
"""Check if we're displaying a Chromium error page.
This gets only called if we got loadFinished(False) without JavaScript,
so we can display at least some error page.
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66643
Needs to check the page content as a WORKAROUND for
https://bugreports.qt.io/browse/QTBUG-66661
"""
match = re.search(r'"errorCode":"([^"]*)"', html)
if match is None:
return
self._show_error_page(self.url(), error=match.group(1))
@pyqtSlot(bool)
def _on_load_finished(self, ok):
"""Display a static error page if JavaScript is disabled."""
super()._on_load_finished(ok)
js_enabled = self.settings.test_attribute('content.javascript.enabled')
if not ok and not js_enabled:
self.dump_async(self._error_page_workaround)
if ok and self._reload_url is not None:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
log.config.debug(
"Reloading {} because of config change".format(
self._reload_url.toDisplayString()))
QTimer.singleShot(100, lambda url=self._reload_url:
self.openurl(url))
self._reload_url = None
@pyqtSlot(QUrl)
def _on_predicted_navigation(self, url):
"""If we know we're going to visit an URL soon, change the settings."""
self.settings.update_for_url(url)
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
super()._on_navigation_request(navigation)
if navigation.accepted and navigation.is_main_frame:
changed = self.settings.update_for_url(navigation.url)
needs_reload = {'content.plugins', 'content.javascript.enabled'}
if (changed & needs_reload and navigation.navigation_type !=
navigation.Type.link_clicked):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
self._reload_url = navigation.url
def _connect_signals(self): def _connect_signals(self):
view = self._widget view = self._widget
page = view.page() page = view.page()
@ -834,6 +941,7 @@ class WebEngineTab(browsertab.AbstractTab):
self._on_proxy_authentication_required) self._on_proxy_authentication_required)
page.fullScreenRequested.connect(self._on_fullscreen_requested) page.fullScreenRequested.connect(self._on_fullscreen_requested)
page.contentsSizeChanged.connect(self.contents_size_changed) page.contentsSizeChanged.connect(self.contents_size_changed)
page.navigation_request.connect(self._on_navigation_request)
view.titleChanged.connect(self.title_changed) view.titleChanged.connect(self.title_changed)
view.urlChanged.connect(self._on_url_changed) view.urlChanged.connect(self._on_url_changed)
@ -854,5 +962,7 @@ class WebEngineTab(browsertab.AbstractTab):
page.loadFinished.connect(self._restore_zoom) page.loadFinished.connect(self._restore_zoom)
page.loadFinished.connect(self._on_load_finished) page.loadFinished.connect(self._on_load_finished)
self.predicted_navigation.connect(self._on_predicted_navigation)
def event_target(self): def event_target(self):
return self._widget.focusProxy() return self._widget.focusProxy()

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -29,8 +29,7 @@ from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
from qutebrowser.browser import shared from qutebrowser.browser import shared
from qutebrowser.browser.webengine import certificateerror, webenginesettings from qutebrowser.browser.webengine import certificateerror, webenginesettings
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message, from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
objreg, qtutils)
class WebEngineView(QWebEngineView): class WebEngineView(QWebEngineView):
@ -124,10 +123,12 @@ class WebEnginePage(QWebEnginePage):
Signals: Signals:
certificate_error: Emitted on certificate errors. certificate_error: Emitted on certificate errors.
shutting_down: Emitted when the page is shutting down. shutting_down: Emitted when the page is shutting down.
navigation_request: Emitted on acceptNavigationRequest.
""" """
certificate_error = pyqtSignal() certificate_error = pyqtSignal()
shutting_down = pyqtSignal() shutting_down = pyqtSignal()
navigation_request = pyqtSignal(usertypes.NavigationRequest)
def __init__(self, *, theme_color, profile, parent=None): def __init__(self, *, theme_color, profile, parent=None):
super().__init__(profile, parent) super().__init__(profile, parent)
@ -288,21 +289,26 @@ class WebEnginePage(QWebEnginePage):
url: QUrl, url: QUrl,
typ: QWebEnginePage.NavigationType, typ: QWebEnginePage.NavigationType,
is_main_frame: bool): is_main_frame: bool):
"""Override acceptNavigationRequest to handle clicked links. """Override acceptNavigationRequest to forward it to the tab API."""
type_map = {
This only show an error on invalid links - everything else is handled QWebEnginePage.NavigationTypeLinkClicked:
in createWindow. usertypes.NavigationRequest.Type.link_clicked,
""" QWebEnginePage.NavigationTypeTyped:
log.webview.debug("navigation request: url {}, type {}, is_main_frame " usertypes.NavigationRequest.Type.typed,
"{}".format(url.toDisplayString(), QWebEnginePage.NavigationTypeFormSubmitted:
debug.qenum_key(QWebEnginePage, typ), usertypes.NavigationRequest.Type.form_submitted,
is_main_frame)) QWebEnginePage.NavigationTypeBackForward:
if (typ == QWebEnginePage.NavigationTypeLinkClicked and usertypes.NavigationRequest.Type.back_forward,
not url.isValid()): QWebEnginePage.NavigationTypeReload:
msg = urlutils.get_errstring(url, "Invalid link clicked") usertypes.NavigationRequest.Type.reloaded,
message.error(msg) QWebEnginePage.NavigationTypeOther:
return False usertypes.NavigationRequest.Type.other,
return True }
navigation = usertypes.NavigationRequest(url=url,
navigation_type=type_map[typ],
is_main_frame=is_main_frame)
self.navigation_request.emit(navigation)
return navigation.accepted
@pyqtSlot('QUrl') @pyqtSlot('QUrl')
def _inject_userjs(self, url): def _inject_userjs(self, url):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2017 Daniel Schadt # Copyright 2015-2018 Daniel Schadt
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -254,7 +254,6 @@ class _Downloader:
_finished_file: A flag indicating if the file has already been _finished_file: A flag indicating if the file has already been
written. written.
_used: A flag indicating if the downloader has already been used. _used: A flag indicating if the downloader has already been used.
_win_id: The window this downloader belongs to.
""" """
def __init__(self, tab, target): def __init__(self, tab, target):
@ -265,7 +264,6 @@ class _Downloader:
self.pending_downloads = set() self.pending_downloads = set()
self._finished_file = False self._finished_file = False
self._used = False self._used = False
self._win_id = tab.win_id
def run(self): def run(self):
"""Download and save the page. """Download and save the page.
@ -365,8 +363,7 @@ class _Downloader:
self.writer.add_file(urlutils.encoded_url(url), b'') self.writer.add_file(urlutils.encoded_url(url), b'')
return return
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager')
scope='window', window=self._win_id)
target = downloads.FileObjDownloadTarget(_NoCloseBytesIO()) target = downloads.FileObjDownloadTarget(_NoCloseBytesIO())
item = download_manager.get(url, target=target, item = download_manager.get(url, target=target,
auto_remove=True) auto_remove=True)

View File

@ -1,7 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Antoni Boucher (antoyo) <bouanto@zoho.com> # Copyright 2015-2018 Antoni Boucher (antoyo) <bouanto@zoho.com>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -25,7 +25,7 @@
import os import os
from qutebrowser.browser.webkit.network import schemehandler, networkreply from qutebrowser.browser.webkit.network import networkreply
from qutebrowser.utils import jinja from qutebrowser.utils import jinja
@ -111,27 +111,21 @@ def dirbrowser_html(path):
return html.encode('UTF-8', errors='xmlcharrefreplace') return html.encode('UTF-8', errors='xmlcharrefreplace')
class FileSchemeHandler(schemehandler.SchemeHandler): def handler(request):
"""Handler for a file:// URL.
"""Scheme handler for file: URLs.""" Args:
request: QNetworkRequest to answer to.
def createRequest(self, _op, request, _outgoing_data): Return:
"""Create a new request. A QNetworkReply for directories, None for files.
"""
Args: path = request.url().toLocalFile()
request: const QNetworkRequest & req try:
_op: Operation op if os.path.isdir(path):
_outgoing_data: QIODevice * outgoingData data = dirbrowser_html(path)
return networkreply.FixedDataNetworkReply(
Return: request, data, 'text/html')
A QNetworkReply for directories, None for files. return None
""" except UnicodeEncodeError:
path = request.url().toLocalFile() return None
try:
if os.path.isdir(path):
data = dirbrowser_html(path)
return networkreply.FixedDataNetworkReply(
request, data, 'text/html', self.parent())
return None
except UnicodeEncodeError:
return None

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -19,9 +19,7 @@
"""Our own QNetworkAccessManager.""" """Our own QNetworkAccessManager."""
import os
import collections import collections
import netrc
import html import html
import attr import attr
@ -127,10 +125,13 @@ class NetworkManager(QNetworkAccessManager):
_scheme_handlers: A dictionary (scheme -> handler) of supported custom _scheme_handlers: A dictionary (scheme -> handler) of supported custom
schemes. schemes.
_win_id: The window ID this NetworkManager is associated with. _win_id: The window ID this NetworkManager is associated with.
(or None for generic network managers)
_tab_id: The tab ID this NetworkManager is associated with. _tab_id: The tab ID this NetworkManager is associated with.
(or None for generic network managers)
_rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors. _rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors.
_accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors. _accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors.
_private: Whether we're in private browsing mode. _private: Whether we're in private browsing mode.
netrc_used: Whether netrc authentication was performed.
Signals: Signals:
shutting_down: Emitted when the QNAM is shutting down. shutting_down: Emitted when the QNAM is shutting down.
@ -150,8 +151,8 @@ class NetworkManager(QNetworkAccessManager):
self._tab_id = tab_id self._tab_id = tab_id
self._private = private self._private = private
self._scheme_handlers = { self._scheme_handlers = {
'qute': webkitqutescheme.QuteSchemeHandler(win_id), 'qute': webkitqutescheme.handler,
'file': filescheme.FileSchemeHandler(win_id), 'file': filescheme.handler,
} }
self._set_cookiejar() self._set_cookiejar()
self._set_cache() self._set_cache()
@ -161,6 +162,7 @@ class NetworkManager(QNetworkAccessManager):
self.authenticationRequired.connect(self.on_authentication_required) self.authenticationRequired.connect(self.on_authentication_required)
self.proxyAuthenticationRequired.connect( self.proxyAuthenticationRequired.connect(
self.on_proxy_authentication_required) self.on_proxy_authentication_required)
self.netrc_used = False
def _set_cookiejar(self): def _set_cookiejar(self):
"""Set the cookie jar of the NetworkManager correctly.""" """Set the cookie jar of the NetworkManager correctly."""
@ -194,6 +196,7 @@ class NetworkManager(QNetworkAccessManager):
# This might be a generic network manager, e.g. one belonging to a # This might be a generic network manager, e.g. one belonging to a
# DownloadManager. In this case, just skip the webview thing. # DownloadManager. In this case, just skip the webview thing.
if self._tab_id is not None: if self._tab_id is not None:
assert self._win_id is not None
tab = objreg.get('tab', scope='tab', window=self._win_id, tab = objreg.get('tab', scope='tab', window=self._win_id,
tab=self._tab_id) tab=self._tab_id)
abort_on.append(tab.load_started) abort_on.append(tab.load_started)
@ -270,28 +273,12 @@ class NetworkManager(QNetworkAccessManager):
@pyqtSlot('QNetworkReply*', 'QAuthenticator*') @pyqtSlot('QNetworkReply*', 'QAuthenticator*')
def on_authentication_required(self, reply, authenticator): def on_authentication_required(self, reply, authenticator):
"""Called when a website needs authentication.""" """Called when a website needs authentication."""
user, password = None, None netrc_success = False
if not hasattr(reply, "netrc_used") and 'HOME' in os.environ: if not self.netrc_used:
# We'll get an OSError by netrc if 'HOME' isn't available in self.netrc_used = True
# os.environ. We don't want to log that, so we prevent it netrc_success = shared.netrc_authentication(reply.url(),
# altogether. authenticator)
reply.netrc_used = True if not netrc_success:
try:
net = netrc.netrc(config.val.content.netrc_file)
authenticators = net.authenticators(reply.url().host())
if authenticators is not None:
(user, _account, password) = authenticators
except FileNotFoundError:
log.misc.debug("No .netrc file found")
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 not None:
authenticator.setUser(user)
authenticator.setPassword(password)
else:
abort_on = self._get_abort_signals(reply) abort_on = self._get_abort_signals(reply)
shared.authentication_required(reply.url(), authenticator, shared.authentication_required(reply.url(), authenticator,
abort_on=abort_on) abort_on=abort_on)
@ -386,9 +373,9 @@ class NetworkManager(QNetworkAccessManager):
scheme = req.url().scheme() scheme = req.url().scheme()
if scheme in self._scheme_handlers: if scheme in self._scheme_handlers:
result = self._scheme_handlers[scheme].createRequest( result = self._scheme_handlers[scheme](req)
op, req, outgoing_data)
if result is not None: if result is not None:
result.setParent(self)
return result return result
for header, value in shared.custom_headers(): for header, value in shared.custom_headers():
@ -408,6 +395,7 @@ class NetworkManager(QNetworkAccessManager):
current_url = QUrl() current_url = QUrl()
if self._tab_id is not None: if self._tab_id is not None:
assert self._win_id is not None
try: try:
tab = objreg.get('tab', scope='tab', window=self._win_id, tab = objreg.get('tab', scope='tab', window=self._win_id,
tab=self._tab_id) tab=self._tab_id)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# Based on the Eric5 helpviewer, # Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>

View File

@ -1,51 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Base class for custom scheme handlers."""
from PyQt5.QtCore import QObject
class SchemeHandler(QObject):
"""Abstract base class for custom scheme handlers.
Attributes:
_win_id: The window ID this scheme handler is associated with.
"""
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
def createRequest(self, op, request, outgoing_data):
"""Create a new request.
Args:
op: Operation op
req: const QNetworkRequest & req
outgoing_data: QIODevice * outgoingData
Return:
A QNetworkReply.
"""
raise NotImplementedError

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -24,46 +24,36 @@ import mimetypes
from PyQt5.QtNetwork import QNetworkReply from PyQt5.QtNetwork import QNetworkReply
from qutebrowser.browser import pdfjs, qutescheme from qutebrowser.browser import pdfjs, qutescheme
from qutebrowser.browser.webkit.network import schemehandler, networkreply from qutebrowser.browser.webkit.network import networkreply
from qutebrowser.utils import log, usertypes, qtutils from qutebrowser.utils import log, usertypes, qtutils
class QuteSchemeHandler(schemehandler.SchemeHandler): def handler(request):
"""Scheme handler for qute:// URLs.
"""Scheme handler for qute:// URLs.""" Args:
request: QNetworkRequest to answer to.
def createRequest(self, _op, request, _outgoing_data): Return:
"""Create a new request. A QNetworkReply.
"""
try:
mimetype, data = qutescheme.data_for_url(request.url())
except qutescheme.NoHandlerFound:
errorstr = "No handler found for {}!".format(
request.url().toDisplayString())
return networkreply.ErrorNetworkReply(
request, errorstr, QNetworkReply.ContentNotFoundError)
except qutescheme.QuteSchemeOSError as e:
return networkreply.ErrorNetworkReply(
request, str(e), QNetworkReply.ContentNotFoundError)
except qutescheme.QuteSchemeError as e:
return networkreply.ErrorNetworkReply(request, e.errorstring, e.error)
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
return networkreply.RedirectNetworkReply(e.url)
Args: return networkreply.FixedDataNetworkReply(request, data, mimetype)
request: const QNetworkRequest & req
_op: Operation op
_outgoing_data: QIODevice * outgoingData
Return:
A QNetworkReply.
"""
try:
mimetype, data = qutescheme.data_for_url(request.url())
except qutescheme.NoHandlerFound:
errorstr = "No handler found for {}!".format(
request.url().toDisplayString())
return networkreply.ErrorNetworkReply(
request, errorstr, QNetworkReply.ContentNotFoundError,
self.parent())
except qutescheme.QuteSchemeOSError as e:
return networkreply.ErrorNetworkReply(
request, str(e), QNetworkReply.ContentNotFoundError,
self.parent())
except qutescheme.QuteSchemeError as e:
return networkreply.ErrorNetworkReply(request, e.errorstring,
e.error, self.parent())
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
return networkreply.RedirectNetworkReply(e.url, self.parent())
return networkreply.FixedDataNetworkReply(request, data, mimetype,
self.parent())
@qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit) @qutescheme.add_handler('pdfjs', backend=usertypes.Backend.QtWebKit)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -17,9 +17,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# We get various "abstract but not overridden" warnings
# pylint: disable=abstract-method
"""Bridge from QWebSettings to our own settings. """Bridge from QWebSettings to our own settings.
Module attributes: Module attributes:
@ -37,85 +34,130 @@ from qutebrowser.utils import standarddir, urlutils
from qutebrowser.browser import shared from qutebrowser.browser import shared
class Base(websettings.Base): # The global WebKitSettings object
global_settings = None
"""Base settings class with appropriate _get_global_settings."""
def _get_global_settings(self):
return [QWebSettings.globalSettings()]
class Attribute(Base, websettings.Attribute): class WebKitSettings(websettings.AbstractSettings):
"""A setting set via QWebSettings::setAttribute.""" """A wrapper for the config for QWebSettings."""
ENUM_BASE = QWebSettings _ATTRIBUTES = {
'content.images':
[QWebSettings.AutoLoadImages],
'content.javascript.enabled':
[QWebSettings.JavascriptEnabled],
'content.javascript.can_open_tabs_automatically':
[QWebSettings.JavascriptCanOpenWindows],
'content.javascript.can_close_tabs':
[QWebSettings.JavascriptCanCloseWindows],
'content.javascript.can_access_clipboard':
[QWebSettings.JavascriptCanAccessClipboard],
'content.plugins':
[QWebSettings.PluginsEnabled],
'content.webgl':
[QWebSettings.WebGLEnabled],
'content.hyperlink_auditing':
[QWebSettings.HyperlinkAuditingEnabled],
'content.local_content_can_access_remote_urls':
[QWebSettings.LocalContentCanAccessRemoteUrls],
'content.local_content_can_access_file_urls':
[QWebSettings.LocalContentCanAccessFileUrls],
'content.dns_prefetch':
[QWebSettings.DnsPrefetchEnabled],
'content.frame_flattening':
[QWebSettings.FrameFlatteningEnabled],
'content.cache.appcache':
[QWebSettings.OfflineWebApplicationCacheEnabled],
'content.local_storage':
[QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled],
'content.developer_extras':
[QWebSettings.DeveloperExtrasEnabled],
'content.print_element_backgrounds':
[QWebSettings.PrintElementBackgrounds],
'content.xss_auditing':
[QWebSettings.XSSAuditingEnabled],
'input.spatial_navigation':
[QWebSettings.SpatialNavigationEnabled],
'input.links_included_in_focus_chain':
[QWebSettings.LinksIncludedInFocusChain],
'zoom.text_only':
[QWebSettings.ZoomTextOnly],
'scrolling.smooth':
[QWebSettings.ScrollAnimatorEnabled],
}
_FONT_SIZES = {
'fonts.web.size.minimum':
QWebSettings.MinimumFontSize,
'fonts.web.size.minimum_logical':
QWebSettings.MinimumLogicalFontSize,
'fonts.web.size.default':
QWebSettings.DefaultFontSize,
'fonts.web.size.default_fixed':
QWebSettings.DefaultFixedFontSize,
}
_FONT_FAMILIES = {
'fonts.web.family.standard': QWebSettings.StandardFont,
'fonts.web.family.fixed': QWebSettings.FixedFont,
'fonts.web.family.serif': QWebSettings.SerifFont,
'fonts.web.family.sans_serif': QWebSettings.SansSerifFont,
'fonts.web.family.cursive': QWebSettings.CursiveFont,
'fonts.web.family.fantasy': QWebSettings.FantasyFont,
}
# Mapping from QWebSettings::QWebSettings() in
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
_FONT_TO_QFONT = {
QWebSettings.StandardFont: QFont.Serif,
QWebSettings.FixedFont: QFont.Monospace,
QWebSettings.SerifFont: QFont.Serif,
QWebSettings.SansSerifFont: QFont.SansSerif,
QWebSettings.CursiveFont: QFont.Cursive,
QWebSettings.FantasyFont: QFont.Fantasy,
}
class Setter(Base, websettings.Setter): def _set_user_stylesheet(settings):
"""Set the generated user-stylesheet."""
"""A setting set via a QWebSettings setter method.""" stylesheet = shared.get_user_stylesheet().encode('utf-8')
url = urlutils.data_url('text/css;charset=utf-8', stylesheet)
pass settings.setUserStyleSheetUrl(url)
class StaticSetter(Base, websettings.StaticSetter): def _set_cookie_accept_policy(settings):
"""Update the content.cookies.accept setting."""
"""A setting set via a static QWebSettings setter method.""" mapping = {
pass
class FontFamilySetter(Base, websettings.FontFamilySetter):
"""A setter for a font family.
Gets the default value from QFont.
"""
def __init__(self, font):
# Mapping from QWebSettings::QWebSettings() in
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
font_to_qfont = {
QWebSettings.StandardFont: QFont.Serif,
QWebSettings.FixedFont: QFont.Monospace,
QWebSettings.SerifFont: QFont.Serif,
QWebSettings.SansSerifFont: QFont.SansSerif,
QWebSettings.CursiveFont: QFont.Cursive,
QWebSettings.FantasyFont: QFont.Fantasy,
}
super().__init__(setter=QWebSettings.setFontFamily, font=font,
qfont=font_to_qfont[font])
class CookiePolicy(Base):
"""The ThirdPartyCookiePolicy setting is different from other settings."""
MAPPING = {
'all': QWebSettings.AlwaysAllowThirdPartyCookies, 'all': QWebSettings.AlwaysAllowThirdPartyCookies,
'no-3rdparty': QWebSettings.AlwaysBlockThirdPartyCookies, 'no-3rdparty': QWebSettings.AlwaysBlockThirdPartyCookies,
'never': QWebSettings.AlwaysBlockThirdPartyCookies, 'never': QWebSettings.AlwaysBlockThirdPartyCookies,
'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies, 'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies,
} }
value = config.val.content.cookies.accept
def _set(self, value, settings=None): settings.setThirdPartyCookiePolicy(mapping[value])
for obj in self._get_settings(settings):
obj.setThirdPartyCookiePolicy(self.MAPPING[value])
def _set_user_stylesheet(): def _set_cache_maximum_pages(settings):
"""Set the generated user-stylesheet.""" """Update the content.cache.maximum_pages setting."""
stylesheet = shared.get_user_stylesheet().encode('utf-8') value = config.val.content.cache.maximum_pages
url = urlutils.data_url('text/css;charset=utf-8', stylesheet) settings.setMaximumPagesInCache(value)
QWebSettings.globalSettings().setUserStyleSheetUrl(url)
def _update_settings(option): def _update_settings(option):
"""Update global settings when qwebsettings changed.""" """Update global settings when qwebsettings changed."""
global_settings.update_setting(option)
settings = QWebSettings.globalSettings()
if option in ['scrollbar.hide', 'content.user_stylesheets']: if option in ['scrollbar.hide', 'content.user_stylesheets']:
_set_user_stylesheet() _set_user_stylesheet(settings)
websettings.update_mappings(MAPPINGS, option) elif option == 'content.cookies.accept':
_set_cookie_accept_policy(settings)
elif option == 'content.cache.maximum_pages':
_set_cache_maximum_pages(settings)
def init(_args): def init(_args):
@ -131,92 +173,20 @@ def init(_args):
QWebSettings.setOfflineStoragePath( QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage')) os.path.join(data_path, 'offline-storage'))
websettings.init_mappings(MAPPINGS) settings = QWebSettings.globalSettings()
_set_user_stylesheet() _set_user_stylesheet(settings)
_set_cookie_accept_policy(settings)
_set_cache_maximum_pages(settings)
config.instance.changed.connect(_update_settings) config.instance.changed.connect(_update_settings)
global global_settings
global_settings = WebKitSettings(QWebSettings.globalSettings())
global_settings.init_settings()
def shutdown(): def shutdown():
"""Disable storage so removing tmpdir will work.""" """Disable storage so removing tmpdir will work."""
QWebSettings.setIconDatabasePath('') QWebSettings.setIconDatabasePath('')
QWebSettings.setOfflineWebApplicationCachePath('') QWebSettings.setOfflineWebApplicationCachePath('')
QWebSettings.globalSettings().setLocalStoragePath('') QWebSettings.globalSettings().setLocalStoragePath('')
MAPPINGS = {
'content.images':
Attribute(QWebSettings.AutoLoadImages),
'content.javascript.enabled':
Attribute(QWebSettings.JavascriptEnabled),
'content.javascript.can_open_tabs_automatically':
Attribute(QWebSettings.JavascriptCanOpenWindows),
'content.javascript.can_close_tabs':
Attribute(QWebSettings.JavascriptCanCloseWindows),
'content.javascript.can_access_clipboard':
Attribute(QWebSettings.JavascriptCanAccessClipboard),
'content.plugins':
Attribute(QWebSettings.PluginsEnabled),
'content.webgl':
Attribute(QWebSettings.WebGLEnabled),
'content.hyperlink_auditing':
Attribute(QWebSettings.HyperlinkAuditingEnabled),
'content.local_content_can_access_remote_urls':
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
'content.local_content_can_access_file_urls':
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
'content.cookies.accept':
CookiePolicy(),
'content.dns_prefetch':
Attribute(QWebSettings.DnsPrefetchEnabled),
'content.frame_flattening':
Attribute(QWebSettings.FrameFlatteningEnabled),
'content.cache.appcache':
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
'content.local_storage':
Attribute(QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled),
'content.cache.maximum_pages':
StaticSetter(QWebSettings.setMaximumPagesInCache),
'content.developer_extras':
Attribute(QWebSettings.DeveloperExtrasEnabled),
'content.print_element_backgrounds':
Attribute(QWebSettings.PrintElementBackgrounds),
'content.xss_auditing':
Attribute(QWebSettings.XSSAuditingEnabled),
'content.default_encoding':
Setter(QWebSettings.setDefaultTextEncoding),
# content.user_stylesheets is handled separately
'input.spatial_navigation':
Attribute(QWebSettings.SpatialNavigationEnabled),
'input.links_included_in_focus_chain':
Attribute(QWebSettings.LinksIncludedInFocusChain),
'fonts.web.family.standard':
FontFamilySetter(QWebSettings.StandardFont),
'fonts.web.family.fixed':
FontFamilySetter(QWebSettings.FixedFont),
'fonts.web.family.serif':
FontFamilySetter(QWebSettings.SerifFont),
'fonts.web.family.sans_serif':
FontFamilySetter(QWebSettings.SansSerifFont),
'fonts.web.family.cursive':
FontFamilySetter(QWebSettings.CursiveFont),
'fonts.web.family.fantasy':
FontFamilySetter(QWebSettings.FantasyFont),
'fonts.web.size.minimum':
Setter(QWebSettings.setFontSize, args=[QWebSettings.MinimumFontSize]),
'fonts.web.size.minimum_logical':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.MinimumLogicalFontSize]),
'fonts.web.size.default':
Setter(QWebSettings.setFontSize, args=[QWebSettings.DefaultFontSize]),
'fonts.web.size.default_fixed':
Setter(QWebSettings.setFontSize,
args=[QWebSettings.DefaultFixedFontSize]),
'zoom.text_only':
Attribute(QWebSettings.ZoomTextOnly),
'scrolling.smooth':
Attribute(QWebSettings.ScrollAnimatorEnabled),
}

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -23,6 +23,10 @@ import re
import functools import functools
import xml.etree.ElementTree import xml.etree.ElementTree
import pygments
import pygments.lexers
import pygments.formatters
import sip import sip
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF, from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
QSize) QSize)
@ -31,8 +35,9 @@ from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtPrintSupport import QPrinter from PyQt5.QtPrintSupport import QPrinter
from qutebrowser.browser import browsertab from qutebrowser.browser import browsertab, shared
from qutebrowser.browser.webkit import webview, tabhistory, webkitelem from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
webkitsettings)
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
@ -50,6 +55,29 @@ class WebKitAction(browsertab.AbstractAction):
"""Save the current page.""" """Save the current page."""
raise browsertab.UnsupportedOperationError raise browsertab.UnsupportedOperationError
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): class WebKitPrinting(browsertab.AbstractPrinting):
@ -161,7 +189,7 @@ class WebKitCaret(browsertab.AbstractCaret):
settings = self._widget.settings() settings = self._widget.settings()
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True) settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
self.selection_enabled = bool(self.selection()) self.selection_enabled = self._widget.hasSelection()
if self._widget.isVisible(): if self._widget.isVisible():
# Sometimes the caret isn't immediately visible, but unfocusing # Sometimes the caret isn't immediately visible, but unfocusing
@ -174,12 +202,12 @@ class WebKitCaret(browsertab.AbstractCaret):
# #
# Note: We can't use hasSelection() here, as that's always # Note: We can't use hasSelection() here, as that's always
# true in caret mode. # true in caret mode.
if not self.selection(): if not self.selection_enabled:
self._widget.page().currentFrame().evaluateJavaScript( self._widget.page().currentFrame().evaluateJavaScript(
utils.read_file('javascript/position_caret.js')) utils.read_file('javascript/position_caret.js'))
@pyqtSlot() @pyqtSlot(usertypes.KeyMode)
def _on_mode_left(self): def _on_mode_left(self, _mode):
settings = self._widget.settings() settings = self._widget.settings()
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled): if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
if self.selection_enabled and self._widget.hasSelection(): if self.selection_enabled and self._widget.hasSelection():
@ -327,23 +355,16 @@ class WebKitCaret(browsertab.AbstractCaret):
def toggle_selection(self): def toggle_selection(self):
self.selection_enabled = not self.selection_enabled self.selection_enabled = not self.selection_enabled
mainwindow = objreg.get('main-window', scope='window', mainwindow = objreg.get('main-window', scope='window',
window=self._win_id) window=self._tab.win_id)
mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True) mainwindow.status.set_mode_active(usertypes.KeyMode.caret, True)
def drop_selection(self): def drop_selection(self):
self._widget.triggerPageAction(QWebPage.MoveToNextChar) self._widget.triggerPageAction(QWebPage.MoveToNextChar)
def has_selection(self): def selection(self, callback):
return self._widget.hasSelection() callback(self._widget.selectedText())
def selection(self, html=False):
if html:
return self._widget.selectedHtml()
return self._widget.selectedText()
def follow_selected(self, *, tab=False): def follow_selected(self, *, tab=False):
if not self.has_selection():
return
if QWebSettings.globalSettings().testAttribute( if QWebSettings.globalSettings().testAttribute(
QWebSettings.JavascriptEnabled): QWebSettings.JavascriptEnabled):
if tab: if tab:
@ -351,7 +372,9 @@ class WebKitCaret(browsertab.AbstractCaret):
self._tab.run_js_async( self._tab.run_js_async(
'window.getSelection().anchorNode.parentNode.click()') 'window.getSelection().anchorNode.parentNode.click()')
else: else:
selection = self.selection(html=True) selection = self._widget.selectedHtml()
if not selection:
return
try: try:
selected_element = xml.etree.ElementTree.fromstring( selected_element = xml.etree.ElementTree.fromstring(
'<html>{}</html>'.format(selection)).find('a') '<html>{}</html>'.format(selection)).find('a')
@ -495,7 +518,8 @@ class WebKitHistory(browsertab.AbstractHistory):
return self._history.itemAt(i) return self._history.itemAt(i)
def _go_to_item(self, item): def _go_to_item(self, item):
return self._history.goToItem(item) self._tab.predicted_navigation.emit(item.url())
self._history.goToItem(item)
def serialize(self): def serialize(self):
return qtutils.serialize(self._history) return qtutils.serialize(self._history)
@ -615,13 +639,15 @@ class WebKitTab(browsertab.AbstractTab):
self._make_private(widget) self._make_private(widget)
self.history = WebKitHistory(self) self.history = WebKitHistory(self)
self.scroller = WebKitScroller(self, parent=self) self.scroller = WebKitScroller(self, parent=self)
self.caret = WebKitCaret(win_id=win_id, mode_manager=mode_manager, self.caret = WebKitCaret(mode_manager=mode_manager,
tab=self, parent=self) tab=self, parent=self)
self.zoom = WebKitZoom(win_id=win_id, parent=self) self.zoom = WebKitZoom(tab=self, parent=self)
self.search = WebKitSearch(parent=self) self.search = WebKitSearch(parent=self)
self.printing = WebKitPrinting() self.printing = WebKitPrinting()
self.elements = WebKitElements(self) self.elements = WebKitElements(tab=self)
self.action = WebKitAction() self.action = WebKitAction(tab=self)
# We're assigning settings in _set_widget
self.settings = webkitsettings.WebKitSettings(settings=None)
self._set_widget(widget) self._set_widget(widget)
self._connect_signals() self._connect_signals()
self.backend = usertypes.Backend.QtWebKit self.backend = usertypes.Backend.QtWebKit
@ -704,6 +730,11 @@ class WebKitTab(browsertab.AbstractTab):
page = self._widget.page() page = self._widget.page()
return page.userAgentForUrl(self.url()) return page.userAgentForUrl(self.url())
@pyqtSlot()
def _on_load_started(self):
super()._on_load_started()
self.networkaccessmanager().netrc_used = False
@pyqtSlot() @pyqtSlot()
def _on_frame_load_finished(self): def _on_frame_load_finished(self):
"""Make sure we emit an appropriate status when loading finished. """Make sure we emit an appropriate status when loading finished.
@ -734,6 +765,31 @@ class WebKitTab(browsertab.AbstractTab):
def _on_contents_size_changed(self, size): def _on_contents_size_changed(self, size):
self.contents_size_changed.emit(QSizeF(size)) self.contents_size_changed.emit(QSizeF(size))
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
super()._on_navigation_request(navigation)
if not navigation.accepted:
return
log.webview.debug("target {} override {}".format(
self.data.open_target, self.data.override_target))
if self.data.override_target is not None:
target = self.data.override_target
self.data.override_target = None
else:
target = self.data.open_target
if (navigation.navigation_type == navigation.Type.link_clicked and
target != usertypes.ClickTarget.normal):
tab = shared.get_tab(self.win_id, target)
tab.openurl(navigation.url)
self.data.open_target = usertypes.ClickTarget.normal
navigation.accepted = False
if navigation.is_main_frame:
self.settings.update_for_url(navigation.url)
def _connect_signals(self): def _connect_signals(self):
view = self._widget view = self._widget
page = view.page() page = view.page()
@ -752,6 +808,7 @@ class WebKitTab(browsertab.AbstractTab):
page.frameCreated.connect(self._on_frame_created) page.frameCreated.connect(self._on_frame_created)
frame.contentsSizeChanged.connect(self._on_contents_size_changed) frame.contentsSizeChanged.connect(self._on_contents_size_changed)
frame.initialLayoutCompleted.connect(self._on_history_trigger) frame.initialLayoutCompleted.connect(self._on_history_trigger)
page.navigation_request.connect(self._on_navigation_request)
def event_target(self): def event_target(self):
return self._widget return self._widget

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -33,8 +33,7 @@ from qutebrowser.config import config
from qutebrowser.browser import pdfjs, shared from qutebrowser.browser import pdfjs, shared
from qutebrowser.browser.webkit import http from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, objreg, debug, from qutebrowser.utils import message, usertypes, log, jinja, objreg
urlutils)
class BrowserPage(QWebPage): class BrowserPage(QWebPage):
@ -54,10 +53,12 @@ class BrowserPage(QWebPage):
shutting_down: Emitted when the page is currently shutting down. shutting_down: Emitted when the page is currently shutting down.
reloading: Emitted before a web page reloads. reloading: Emitted before a web page reloads.
arg: The URL which gets reloaded. arg: The URL which gets reloaded.
navigation_request: Emitted on acceptNavigationRequest.
""" """
shutting_down = pyqtSignal() shutting_down = pyqtSignal()
reloading = pyqtSignal(QUrl) reloading = pyqtSignal(QUrl)
navigation_request = pyqtSignal(usertypes.NavigationRequest)
def __init__(self, win_id, tab_id, tabdata, private, parent=None): def __init__(self, win_id, tab_id, tabdata, private, parent=None):
super().__init__(parent) super().__init__(parent)
@ -70,7 +71,6 @@ class BrowserPage(QWebPage):
} }
self._ignore_load_started = False self._ignore_load_started = False
self.error_occurred = False self.error_occurred = False
self.open_target = usertypes.ClickTarget.normal
self._networkmanager = networkmanager.NetworkManager( self._networkmanager = networkmanager.NetworkManager(
win_id=win_id, tab_id=tab_id, private=private, parent=self) win_id=win_id, tab_id=tab_id, private=private, parent=self)
self.setNetworkAccessManager(self._networkmanager) self.setNetworkAccessManager(self._networkmanager)
@ -147,7 +147,8 @@ class BrowserPage(QWebPage):
title="Open external application for {}-link?".format(scheme), title="Open external application for {}-link?".format(scheme),
text="URL: <b>{}</b>".format( text="URL: <b>{}</b>".format(
html.escape(url.toDisplayString())), html.escape(url.toDisplayString())),
yes_action=functools.partial(QDesktopServices.openUrl, url)) yes_action=functools.partial(QDesktopServices.openUrl, url),
url=info.url.toString(QUrl.RemovePassword | QUrl.FullyEncoded))
return True return True
elif (info.domain, info.error) in ignored_errors: elif (info.domain, info.error) in ignored_errors:
log.webview.debug("Ignored error on {}: {} (error domain: {}, " log.webview.debug("Ignored error on {}: {} (error domain: {}, "
@ -219,8 +220,7 @@ class BrowserPage(QWebPage):
"""Prepare the web page for being deleted.""" """Prepare the web page for being deleted."""
self._is_shutting_down = True self._is_shutting_down = True
self.shutting_down.emit() self.shutting_down.emit()
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager')
scope='window', window=self._win_id)
nam = self.networkAccessManager() nam = self.networkAccessManager()
if download_manager.has_downloads_with_nam(nam): if download_manager.has_downloads_with_nam(nam):
nam.setParent(download_manager) nam.setParent(download_manager)
@ -248,8 +248,7 @@ class BrowserPage(QWebPage):
after this slot returns. after this slot returns.
""" """
req = QNetworkRequest(request) req = QNetworkRequest(request)
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager')
scope='window', window=self._win_id)
download_manager.get_request(req, qnam=self.networkAccessManager()) download_manager.get_request(req, qnam=self.networkAccessManager())
@pyqtSlot('QNetworkReply*') @pyqtSlot('QNetworkReply*')
@ -263,8 +262,7 @@ class BrowserPage(QWebPage):
here: http://mimesniff.spec.whatwg.org/ here: http://mimesniff.spec.whatwg.org/
""" """
inline, suggested_filename = http.parse_content_disposition(reply) inline, suggested_filename = http.parse_content_disposition(reply)
download_manager = objreg.get('qtnetwork-download-manager', download_manager = objreg.get('qtnetwork-download-manager')
scope='window', window=self._win_id)
if not inline: if not inline:
# Content-Disposition: attachment -> force download # Content-Disposition: attachment -> force download
download_manager.fetch(reply, download_manager.fetch(reply,
@ -476,7 +474,7 @@ class BrowserPage(QWebPage):
source, line, msg) source, line, msg)
def acceptNavigationRequest(self, def acceptNavigationRequest(self,
_frame: QWebFrame, frame: QWebFrame,
request: QNetworkRequest, request: QNetworkRequest,
typ: QWebPage.NavigationType): typ: QWebPage.NavigationType):
"""Override acceptNavigationRequest to handle clicked links. """Override acceptNavigationRequest to handle clicked links.
@ -488,36 +486,27 @@ class BrowserPage(QWebPage):
Checks if it should open it in a tab (middle-click or control) or not, Checks if it should open it in a tab (middle-click or control) or not,
and then conditionally opens the URL here or in another tab/window. and then conditionally opens the URL here or in another tab/window.
""" """
url = request.url() type_map = {
log.webview.debug("navigation request: url {}, type {}, " QWebPage.NavigationTypeLinkClicked:
"target {} override {}".format( usertypes.NavigationRequest.Type.link_clicked,
url.toDisplayString(), QWebPage.NavigationTypeFormSubmitted:
debug.qenum_key(QWebPage, typ), usertypes.NavigationRequest.Type.form_submitted,
self.open_target, QWebPage.NavigationTypeFormResubmitted:
self._tabdata.override_target)) usertypes.NavigationRequest.Type.form_resubmitted,
QWebPage.NavigationTypeBackOrForward:
usertypes.NavigationRequest.Type.back_forward,
QWebPage.NavigationTypeReload:
usertypes.NavigationRequest.Type.reloaded,
QWebPage.NavigationTypeOther:
usertypes.NavigationRequest.Type.other,
}
is_main_frame = frame is self.mainFrame()
navigation = usertypes.NavigationRequest(url=request.url(),
navigation_type=type_map[typ],
is_main_frame=is_main_frame)
if self._tabdata.override_target is not None: if navigation.navigation_type == navigation.Type.reloaded:
target = self._tabdata.override_target self.reloading.emit(navigation.url)
self._tabdata.override_target = None
else:
target = self.open_target
if typ == QWebPage.NavigationTypeReload: self.navigation_request.emit(navigation)
self.reloading.emit(url) return navigation.accepted
return True
elif typ != QWebPage.NavigationTypeLinkClicked:
return True
if not url.isValid():
msg = urlutils.get_errstring(url, "Invalid link clicked")
message.error(msg)
self.open_target = usertypes.ClickTarget.normal
return False
if target == usertypes.ClickTarget.normal:
return True
tab = shared.get_tab(self._win_id, target)
tab.openurl(url)
self.open_target = usertypes.ClickTarget.normal
return False

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -262,10 +262,10 @@ class WebView(QWebView):
target = usertypes.ClickTarget.tab_bg target = usertypes.ClickTarget.tab_bg
else: else:
target = usertypes.ClickTarget.tab target = usertypes.ClickTarget.tab
self.page().open_target = target self._tabdata.open_target = target
log.mouse.debug("Ctrl/Middle click, setting target: {}".format( log.mouse.debug("Ctrl/Middle click, setting target: {}".format(
target)) target))
else: else:
self.page().open_target = usertypes.ClickTarget.normal self._tabdata.open_target = usertypes.ClickTarget.normal
log.mouse.debug("Normal click, setting normal target") log.mouse.debug("Normal click, setting normal target")
super().mousePressEvent(e) super().mousePressEvent(e)

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -105,7 +105,8 @@ class register: # noqa: N801,N806 pylint: disable=invalid-name
else: else:
assert isinstance(self._name, str), self._name assert isinstance(self._name, str), self._name
name = self._name name = self._name
log.commands.vdebug("Registering command {}".format(name)) log.commands.vdebug("Registering command {} (from {}:{})".format(
name, func.__module__, func.__qualname__))
if name in cmd_dict: if name in cmd_dict:
raise ValueError("{} is already registered!".format(name)) raise ValueError("{} is already registered!".format(name))
cmd = command.Command(name=name, instance=self._instance, cmd = command.Command(name=name, instance=self._instance,

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -193,10 +193,10 @@ class Command:
return False return False
def _inspect_func(self): def _inspect_func(self):
"""Inspect the function to get useful informations from it. """Inspect the function to get useful information from it.
Sets instance attributes (desc, type_conv, name_conv) based on the Sets instance attributes (desc, type_conv, name_conv) based on the
informations. information.
Return: Return:
How many user-visible arguments the command has. How many user-visible arguments the command has.
@ -394,11 +394,12 @@ class Command:
if isinstance(typ, tuple): if isinstance(typ, tuple):
raise TypeError("{}: Legacy tuple type annotation!".format( raise TypeError("{}: Legacy tuple type annotation!".format(
self.name)) self.name))
elif type(typ) is type(typing.Union): # noqa: E721 elif getattr(typ, '__origin__', None) is typing.Union or (
# Older Python 3.5 patch versions
# pylint: disable=no-member,useless-suppression
hasattr(typing, 'UnionMeta') and
isinstance(typ, typing.UnionMeta)):
# this is... slightly evil, I know # this is... slightly evil, I know
# We also can't use isinstance here because typing.Union doesn't
# support that.
# pylint: disable=no-member,useless-suppression
try: try:
types = list(typ.__args__) types = list(typ.__args__)
except AttributeError: except AttributeError:

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -63,9 +63,13 @@ def replace_variables(win_id, arglist):
QUrl.FullyEncoded | QUrl.RemovePassword), QUrl.FullyEncoded | QUrl.RemovePassword),
'url:pretty': lambda: _current_url(tabbed_browser).toString( 'url:pretty': lambda: _current_url(tabbed_browser).toString(
QUrl.DecodeReserved | QUrl.RemovePassword), QUrl.DecodeReserved | QUrl.RemovePassword),
'url:host': lambda: _current_url(tabbed_browser).host(),
'clipboard': utils.get_clipboard, 'clipboard': utils.get_clipboard,
'primary': lambda: utils.get_clipboard(selection=True), 'primary': lambda: utils.get_clipboard(selection=True),
} }
for key in list(variables):
modified_key = '{' + key + '}'
variables[modified_key] = lambda x=modified_key: x
values = {} values = {}
args = [] args = []
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
@ -162,7 +166,7 @@ class CommandParser:
yield self.parse(sub, *args, **kwargs) yield self.parse(sub, *args, **kwargs)
def parse_all(self, *args, **kwargs): def parse_all(self, *args, **kwargs):
"""Wrapper over parse_all.""" """Wrapper over _parse_all_gen."""
return list(self._parse_all_gen(*args, **kwargs)) return list(self._parse_all_gen(*args, **kwargs))
def parse(self, text, *, fallback=False, keep=False): def parse(self, text, *, fallback=False, keep=False):

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -446,3 +446,4 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
runner.prepare_run(cmd_path, *args, env=env, verbose=verbose) runner.prepare_run(cmd_path, *args, env=env, verbose=verbose)
tab.dump_async(runner.store_html) tab.dump_async(runner.store_html)
tab.dump_async(runner.store_text, plain=True) tab.dump_async(runner.store_text, plain=True)
return runner

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -87,8 +87,6 @@ class Completer(QObject):
# cursor on a flag or after an explicit split (--) # cursor on a flag or after an explicit split (--)
return None return None
log.completion.debug("Before removing flags: {}".format(before_cursor)) log.completion.debug("Before removing flags: {}".format(before_cursor))
before_cursor = [x for x in before_cursor if not x.startswith('-')]
log.completion.debug("After removing flags: {}".format(before_cursor))
if not before_cursor: if not before_cursor:
# '|' or 'set|' # '|' or 'set|'
log.completion.debug('Starting command completion') log.completion.debug('Starting command completion')
@ -99,6 +97,9 @@ class Completer(QObject):
log.completion.debug("No completion for unknown command: {}" log.completion.debug("No completion for unknown command: {}"
.format(before_cursor[0])) .format(before_cursor[0]))
return None return None
before_cursor = [x for x in before_cursor if not x.startswith('-')]
log.completion.debug("After removing flags: {}".format(before_cursor))
argpos = len(before_cursor) - 1 argpos = len(before_cursor) - 1
try: try:
func = cmd.get_pos_arg_info(argpos).completion func = cmd.get_pos_arg_info(argpos).completion

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org> # Copyright 2014-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #
@ -203,9 +203,9 @@ class CompletionItemDelegate(QStyledItemDelegate):
columns_to_filter = index.model().columns_to_filter(index) columns_to_filter = index.model().columns_to_filter(index)
if index.column() in columns_to_filter and pattern: if index.column() in columns_to_filter and pattern:
repl = r'<span class="highlight">\g<0></span>' repl = r'<span class="highlight">\g<0></span>'
text = re.sub(re.escape(pattern).replace(r'\ ', r'|'), pat = html.escape(re.escape(pattern)).replace(r'\ ', r'|')
repl, html.escape(self._opt.text), txt = html.escape(self._opt.text)
flags=re.IGNORECASE) text = re.sub(pat, repl, txt, flags=re.IGNORECASE)
self._doc.setHtml(text) self._doc.setHtml(text)
else: else:
self._doc.setPlainText(self._opt.text) self._doc.setPlainText(self._opt.text)

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net> # Copyright 2017-2018 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net> # Copyright 2017-2018 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #

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