Merge remote-tracking branch 'upstream/master' into jay/frame-hinting
This commit is contained in:
commit
12d729c3bc
@ -12,6 +12,7 @@ exclude_lines =
|
|||||||
def __repr__
|
def __repr__
|
||||||
raise AssertionError
|
raise AssertionError
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
raise utils\.Unreachable
|
||||||
if __name__ == ["']__main__["']:
|
if __name__ == ["']__main__["']:
|
||||||
|
|
||||||
[xml]
|
[xml]
|
||||||
|
3
.flake8
3
.flake8
@ -1,6 +1,7 @@
|
|||||||
[flake8]
|
[flake8]
|
||||||
exclude = .*,__pycache__,resources.py
|
exclude = .*,__pycache__,resources.py
|
||||||
# B001: bare except
|
# B001: bare except
|
||||||
|
# B008: Do not perform calls in argument defaults. (fine with some Qt stuff)
|
||||||
# B305: .next() (false-positives)
|
# B305: .next() (false-positives)
|
||||||
# E128: continuation line under-indented for visual indent
|
# E128: continuation line under-indented for visual indent
|
||||||
# E226: missing whitespace around arithmetic operator
|
# E226: missing whitespace around arithmetic operator
|
||||||
@ -33,7 +34,7 @@ exclude = .*,__pycache__,resources.py
|
|||||||
# D413: Missing blank line after last section (not in pep257?)
|
# D413: Missing blank line after last section (not in pep257?)
|
||||||
# A003: Builtin name for class attribute (needed for attrs)
|
# A003: Builtin name for class attribute (needed for attrs)
|
||||||
ignore =
|
ignore =
|
||||||
B001,B305,
|
B001,B008,B305,
|
||||||
E128,E226,E265,E501,E402,E266,E722,E731,
|
E128,E226,E265,E501,E402,E266,E722,E731,
|
||||||
F401,
|
F401,
|
||||||
N802,
|
N802,
|
||||||
|
16
.gitignore
vendored
16
.gitignore
vendored
@ -31,12 +31,12 @@ __pycache__
|
|||||||
/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
|
||||||
|
35
.pylintrc
35
.pylintrc
@ -13,38 +13,33 @@ persistent=n
|
|||||||
|
|
||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
enable=all
|
enable=all
|
||||||
disable=no-self-use,
|
disable=locally-disabled,
|
||||||
fixme,
|
|
||||||
global-statement,
|
|
||||||
locally-disabled,
|
|
||||||
locally-enabled,
|
locally-enabled,
|
||||||
too-many-ancestors,
|
suppressed-message,
|
||||||
too-few-public-methods,
|
fixme,
|
||||||
too-many-public-methods,
|
no-self-use,
|
||||||
cyclic-import,
|
cyclic-import,
|
||||||
bad-continuation,
|
|
||||||
too-many-instance-attributes,
|
|
||||||
blacklisted-name,
|
blacklisted-name,
|
||||||
too-many-lines,
|
|
||||||
logging-format-interpolation,
|
logging-format-interpolation,
|
||||||
|
logging-not-lazy,
|
||||||
broad-except,
|
broad-except,
|
||||||
bare-except,
|
bare-except,
|
||||||
eval-used,
|
eval-used,
|
||||||
exec-used,
|
exec-used,
|
||||||
ungrouped-imports,
|
global-statement,
|
||||||
suppressed-message,
|
|
||||||
too-many-return-statements,
|
|
||||||
duplicate-code,
|
|
||||||
wrong-import-position,
|
wrong-import-position,
|
||||||
|
duplicate-code,
|
||||||
no-else-return,
|
no-else-return,
|
||||||
# https://github.com/PyCQA/pylint/issues/1698
|
too-many-ancestors,
|
||||||
unsupported-membership-test,
|
too-many-public-methods,
|
||||||
unsupported-assignment-operation,
|
too-many-instance-attributes,
|
||||||
unsubscriptable-object,
|
too-many-lines,
|
||||||
|
too-many-return-statements,
|
||||||
too-many-boolean-expressions,
|
too-many-boolean-expressions,
|
||||||
too-many-locals,
|
too-many-locals,
|
||||||
too-many-branches,
|
too-many-branches,
|
||||||
too-many-statements
|
too-many-statements,
|
||||||
|
too-few-public-methods
|
||||||
|
|
||||||
[BASIC]
|
[BASIC]
|
||||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||||
@ -73,10 +68,10 @@ valid-metaclass-classmethod-first-arg=cls
|
|||||||
|
|
||||||
[TYPECHECK]
|
[TYPECHECK]
|
||||||
ignored-modules=PyQt5,PyQt5.QtWebKit
|
ignored-modules=PyQt5,PyQt5.QtWebKit
|
||||||
ignored-classes=_CountingAttr
|
|
||||||
|
|
||||||
[IMPORTS]
|
[IMPORTS]
|
||||||
# WORKAROUND
|
# WORKAROUND
|
||||||
# For some reason, pylint doesn't know about some Python 3 modules on
|
# For some reason, pylint doesn't know about some Python 3 modules on
|
||||||
# AppVeyor...
|
# AppVeyor...
|
||||||
known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types
|
known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types
|
||||||
|
known-third-party=sip
|
||||||
|
@ -23,7 +23,7 @@ matrix:
|
|||||||
env: TESTENV=py36-pyqt59-cov
|
env: TESTENV=py36-pyqt59-cov
|
||||||
- os: osx
|
- os: osx
|
||||||
env: TESTENV=py36 OSX=sierra
|
env: TESTENV=py36 OSX=sierra
|
||||||
osx_image: xcode8.3
|
osx_image: xcode9.2
|
||||||
language: generic
|
language: generic
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||||
# - os: osx
|
# - os: osx
|
||||||
@ -52,6 +52,10 @@ matrix:
|
|||||||
language: node_js
|
language: node_js
|
||||||
python: null
|
python: null
|
||||||
node_js: "lts/*"
|
node_js: "lts/*"
|
||||||
|
- os: linux
|
||||||
|
language: generic
|
||||||
|
env: TESTENV=shellcheck
|
||||||
|
services: docker
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
|
@ -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
|
||||||
|
@ -15,57 +15,105 @@ 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
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
- There's now a `misc/Makefile` file in releases, which should help
|
|
||||||
distributions which package qutebrowser, as they can run something like
|
|
||||||
`make -f misc/Makefile DESTDIR="$pkgdir" install` now.
|
|
||||||
- New `{current_url}` field for `window.title_format` and `tabs.title.format`.
|
|
||||||
- New `colors.statusbar.passthrough.fg`/`.bg` settings.
|
|
||||||
- New `completion.delay` and `completion.min_chars` settings to update the
|
|
||||||
completion less often.
|
|
||||||
- New `completion.use_best_match` setting to automatically use the best-matching
|
|
||||||
command in the completion.
|
|
||||||
- New `:tab-give` and `:tab-take` commands, to give tabs to another window, or
|
|
||||||
take them from another window.
|
|
||||||
- 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
|
|
||||||
cursor correctly.
|
|
||||||
- New `qute-pass` userscript as alternative to `password_fill` which allows
|
|
||||||
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.
|
|
||||||
|
|
||||||
Changed
|
Changed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
- Some tabs settings got renamed:
|
- 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.
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Improved fullscreen handling with Qt 5.10.
|
||||||
|
|
||||||
|
v1.1.1 (unreleased)
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Fixed
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- The Makefile now actually works.
|
||||||
|
|
||||||
|
v1.1.0
|
||||||
|
------
|
||||||
|
|
||||||
|
Added
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
- Initial support for Greasemonkey scripts. There are still some rough edges,
|
||||||
|
but many scripts should already work.
|
||||||
|
- There's now a `misc/Makefile` file in releases, which should help
|
||||||
|
distributions which package qutebrowser, as they can run something like
|
||||||
|
`make -f misc/Makefile DESTDIR="$pkgdir" install` now.
|
||||||
|
- New fields for `window.title_format` and `tabs.title.format`:
|
||||||
|
* `{current_url}`
|
||||||
|
* `{protocol}`
|
||||||
|
- New settings:
|
||||||
|
* `colors.statusbar.passthrough.fg`/`.bg`
|
||||||
|
* `completion.delay` and `completion.min_chars` to update the completion less
|
||||||
|
often.
|
||||||
|
* `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 `{line}` and `{column}` replacements for `editor.command` to position the
|
||||||
|
cursor correctly.
|
||||||
|
- New `qute-pass` userscript as alternative to `password_fill` which allows
|
||||||
|
selecting accounts via rofi or any other dmenu-compatile application.
|
||||||
|
- New `hist_importer.py` script to import history from Firefox/Chromium.
|
||||||
|
|
||||||
|
Changed
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
- Some settings got renamed:
|
||||||
* `tabs.width.bar` -> `tabs.width`
|
* `tabs.width.bar` -> `tabs.width`
|
||||||
* `tabs.width.indicator` -> `tabs.indicator.width`
|
* `tabs.width.indicator` -> `tabs.indicator.width`
|
||||||
* `tabs.indicator_padding` -> `tabs.indicator.padding`
|
* `tabs.indicator_padding` -> `tabs.indicator.padding`
|
||||||
|
* `session_default_name` -> `session.default_name`
|
||||||
|
* `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.
|
||||||
@ -77,36 +125,46 @@ 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
|
||||||
|
completion.
|
||||||
|
- The default font size for the UI got bumped up from 8pt to 10pt.
|
||||||
|
- Improved matching in the completion: The words entered are now matched in any
|
||||||
|
order, and mixed matches on URL/tite are possible.
|
||||||
|
- The system's default encoding (rather than UTF-8) is now used to decode
|
||||||
|
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
|
||||||
|
results being found is shown anymore.
|
||||||
|
- 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 closing a tab immediately after hinting.
|
||||||
|
- 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
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
@ -119,6 +177,8 @@ Removed
|
|||||||
|
|
||||||
- The long-deprecated `:prompt-yes`, `:prompt-no`, `:paste-primary` and `:paste`
|
- The long-deprecated `:prompt-yes`, `:prompt-no`, `:paste-primary` and `:paste`
|
||||||
commands have been removed.
|
commands have been removed.
|
||||||
|
- The invocation `:download <url> <dest>` which was deprecated in v0.5.0 was
|
||||||
|
removed, use `:download --dest <dest> <url>` instead.
|
||||||
- The `messages.unfocused` option which wasn't used anymore was removed.
|
- The `messages.unfocused` option which wasn't used anymore was removed.
|
||||||
- The `x[xtb]` default bindings got removed again as many users accidentally
|
- The `x[xtb]` default bindings got removed again as many users accidentally
|
||||||
triggered them.
|
triggered them.
|
||||||
|
@ -221,5 +221,5 @@ My issue is not listed.::
|
|||||||
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
||||||
using the `:report` command.
|
using the `:report` command.
|
||||||
If you are reporting a segfault, make sure you read the
|
If you are reporting a segfault, make sure you read the
|
||||||
link:doc/stacktrace.asciidoc[guide] on how to report them with all needed
|
link:stacktrace.asciidoc[guide] on how to report them with all needed
|
||||||
information.
|
information.
|
||||||
|
@ -54,6 +54,7 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
|||||||
|<<follow-selected,follow-selected>>|Follow the selected text.
|
|<<follow-selected,follow-selected>>|Follow the selected text.
|
||||||
|<<forward,forward>>|Go forward in the history of the current tab.
|
|<<forward,forward>>|Go forward in the history of the current tab.
|
||||||
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
||||||
|
|<<greasemonkey-reload,greasemonkey-reload>>|Re-read Greasemonkey scripts from disk.
|
||||||
|<<help,help>>|Show help about a command or setting.
|
|<<help,help>>|Show help about a command or setting.
|
||||||
|<<hint,hint>>|Start hinting.
|
|<<hint,hint>>|Start hinting.
|
||||||
|<<history,history>>|Show browsing history.
|
|<<history,history>>|Show browsing history.
|
||||||
@ -333,12 +334,10 @@ Write the current configuration to a config.py file.
|
|||||||
|
|
||||||
[[download]]
|
[[download]]
|
||||||
=== download
|
=== download
|
||||||
Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url'] ['dest-old']+
|
Syntax: +:download [*--mhtml*] [*--dest* 'dest'] ['url']+
|
||||||
|
|
||||||
Download a given URL, or current page if no URL given.
|
Download a given URL, or current page if no URL given.
|
||||||
|
|
||||||
The form `:download [url] [dest]` is deprecated, use `:download --dest [dest] [url]` instead.
|
|
||||||
|
|
||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'url'+: The URL to download. If not given, download the current page.
|
* +'url'+: The URL to download. If not given, download the current page.
|
||||||
|
|
||||||
@ -491,6 +490,12 @@ Toggle fullscreen mode.
|
|||||||
==== optional arguments
|
==== optional arguments
|
||||||
* +*-l*+, +*--leave*+: Only leave fullscreen if it was entered by the page.
|
* +*-l*+, +*--leave*+: Only leave fullscreen if it was entered by the page.
|
||||||
|
|
||||||
|
[[greasemonkey-reload]]
|
||||||
|
=== greasemonkey-reload
|
||||||
|
Re-read Greasemonkey scripts from disk.
|
||||||
|
|
||||||
|
The scripts are read from a 'greasemonkey' subdirectory in qutebrowser's data directory (see `:version`).
|
||||||
|
|
||||||
[[help]]
|
[[help]]
|
||||||
=== help
|
=== help
|
||||||
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+
|
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+
|
||||||
@ -511,7 +516,7 @@ Show help about a command or setting.
|
|||||||
|
|
||||||
[[hint]]
|
[[hint]]
|
||||||
=== hint
|
=== hint
|
||||||
Syntax: +:hint [*--rapid*] [*--mode* 'mode'] [*--add-history*]
|
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*]
|
||||||
['group'] ['target'] ['args' ['args' ...]]+
|
['group'] ['target'] ['args' ['args' ...]]+
|
||||||
|
|
||||||
Start hinting.
|
Start hinting.
|
||||||
@ -565,11 +570,6 @@ Start hinting.
|
|||||||
|
|
||||||
|
|
||||||
==== optional arguments
|
==== optional arguments
|
||||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. With rapid hinting, the hint mode isn't left after a hint is followed, so you can easily
|
|
||||||
open multiple links. This is only possible with targets
|
|
||||||
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
|
||||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
|
||||||
|
|
||||||
* +*-m*+, +*--mode*+: The hinting mode to use.
|
* +*-m*+, +*--mode*+: The hinting mode to use.
|
||||||
|
|
||||||
- `number`: Use numeric hints.
|
- `number`: Use numeric hints.
|
||||||
@ -581,6 +581,11 @@ Start hinting.
|
|||||||
|
|
||||||
* +*-a*+, +*--add-history*+: Whether to add the spawned or yanked link to the browsing history.
|
* +*-a*+, +*--add-history*+: Whether to add the spawned or yanked link to the browsing history.
|
||||||
|
|
||||||
|
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. With rapid hinting, the hint mode isn't left after a hint is followed, so you can easily
|
||||||
|
open multiple links. This is only possible with targets
|
||||||
|
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||||
|
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||||
|
|
||||||
|
|
||||||
==== 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.
|
||||||
@ -1083,7 +1088,7 @@ Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-win
|
|||||||
Save a session.
|
Save a session.
|
||||||
|
|
||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'name'+: The name of the session. If not given, the session configured in session_default_name is saved.
|
* +'name'+: The name of the session. If not given, the session configured in session.default_name is saved.
|
||||||
|
|
||||||
|
|
||||||
==== optional arguments
|
==== optional arguments
|
||||||
@ -1141,7 +1146,7 @@ Set a mark at the current scroll position in the current tab.
|
|||||||
|
|
||||||
[[spawn]]
|
[[spawn]]
|
||||||
=== spawn
|
=== spawn
|
||||||
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+
|
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--detach*] 'cmdline'+
|
||||||
|
|
||||||
Spawn a command in a shell.
|
Spawn a command in a shell.
|
||||||
|
|
||||||
@ -1156,6 +1161,7 @@ Spawn a command in a shell.
|
|||||||
- `/usr/share/qutebrowser/userscripts`
|
- `/usr/share/qutebrowser/userscripts`
|
||||||
|
|
||||||
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
||||||
|
* +*-o*+, +*--output*+: Whether the output should be shown in a new tab.
|
||||||
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
||||||
|
|
||||||
==== note
|
==== note
|
||||||
@ -1415,8 +1421,13 @@ How many steps to zoom out.
|
|||||||
|==============
|
|==============
|
||||||
[[command-accept]]
|
[[command-accept]]
|
||||||
=== command-accept
|
=== command-accept
|
||||||
|
Syntax: +:command-accept [*--rapid*]+
|
||||||
|
|
||||||
Execute the command currently in the commandline.
|
Execute the command currently in the commandline.
|
||||||
|
|
||||||
|
==== optional arguments
|
||||||
|
* +*-r*+, +*--rapid*+: Run the command without closing or clearing the command bar.
|
||||||
|
|
||||||
[[command-history-next]]
|
[[command-history-next]]
|
||||||
=== command-history-next
|
=== command-history-next
|
||||||
Go forward in the commandline history.
|
Go forward in the commandline history.
|
||||||
|
@ -264,7 +264,7 @@ get a string:
|
|||||||
.config.py:
|
.config.py:
|
||||||
[source,python]
|
[source,python]
|
||||||
----
|
----
|
||||||
print(str(config.configdir / 'config.py')
|
print(str(config.configdir / 'config.py'))
|
||||||
----
|
----
|
||||||
|
|
||||||
Handling errors
|
Handling errors
|
||||||
|
@ -199,7 +199,6 @@
|
|||||||
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|
||||||
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
||||||
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|
||||||
|<<ignore_case,ignore_case>>|When to find text on a page case-insensitively.
|
|
||||||
|<<input.forward_unbound_keys,input.forward_unbound_keys>>|Which unbound keys to forward to the webview in normal mode.
|
|<<input.forward_unbound_keys,input.forward_unbound_keys>>|Which unbound keys to forward to the webview in normal mode.
|
||||||
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
|
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
|
||||||
|<<input.insert_mode.auto_load,input.insert_mode.auto_load>>|Automatically enter insert mode if an editable element is focused after loading the page.
|
|<<input.insert_mode.auto_load,input.insert_mode.auto_load>>|Automatically enter insert mode if an editable element is focused after loading the page.
|
||||||
@ -222,7 +221,10 @@
|
|||||||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
||||||
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|
||||||
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|
||||||
|<<session_default_name,session_default_name>>|Name of the session to save by default.
|
|<<search.ignore_case,search.ignore_case>>|When to find text on a page case-insensitively.
|
||||||
|
|<<search.incremental,search.incremental>>|Find text on a page incrementally, renewing the search for each typed character.
|
||||||
|
|<<session.default_name,session.default_name>>|Name of the session to save by default.
|
||||||
|
|<<session.lazy_restore,session.lazy_restore>>|Load a restored tab as soon as it takes focus.
|
||||||
|<<spellcheck.languages,spellcheck.languages>>|Languages to use for spell checking.
|
|<<spellcheck.languages,spellcheck.languages>>|Languages to use for spell checking.
|
||||||
|<<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.
|
||||||
@ -425,6 +427,7 @@ Default:
|
|||||||
* +pass:[<Ctrl-K>]+: +pass:[rl-kill-line]+
|
* +pass:[<Ctrl-K>]+: +pass:[rl-kill-line]+
|
||||||
* +pass:[<Ctrl-N>]+: +pass:[command-history-next]+
|
* +pass:[<Ctrl-N>]+: +pass:[command-history-next]+
|
||||||
* +pass:[<Ctrl-P>]+: +pass:[command-history-prev]+
|
* +pass:[<Ctrl-P>]+: +pass:[command-history-prev]+
|
||||||
|
* +pass:[<Ctrl-Return>]+: +pass:[command-accept --rapid]+
|
||||||
* +pass:[<Ctrl-Shift-C>]+: +pass:[completion-item-yank --sel]+
|
* +pass:[<Ctrl-Shift-C>]+: +pass:[completion-item-yank --sel]+
|
||||||
* +pass:[<Ctrl-Shift-Tab>]+: +pass:[completion-item-focus prev-category]+
|
* +pass:[<Ctrl-Shift-Tab>]+: +pass:[completion-item-focus prev-category]+
|
||||||
* +pass:[<Ctrl-Tab>]+: +pass:[completion-item-focus next-category]+
|
* +pass:[<Ctrl-Tab>]+: +pass:[completion-item-focus next-category]+
|
||||||
@ -698,10 +701,15 @@ Default: +pass:[#333333]+
|
|||||||
[[colors.completion.fg]]
|
[[colors.completion.fg]]
|
||||||
=== colors.completion.fg
|
=== colors.completion.fg
|
||||||
Text color of the completion widget.
|
Text color of the completion widget.
|
||||||
|
May be a single color to use for all columns or a list of three colors, one for each column.
|
||||||
|
|
||||||
Type: <<types,QtColor>>
|
Type: <<types,List of QtColor, or QtColor>>
|
||||||
|
|
||||||
Default: +pass:[white]+
|
Default:
|
||||||
|
|
||||||
|
- +pass:[white]+
|
||||||
|
- +pass:[white]+
|
||||||
|
- +pass:[white]+
|
||||||
|
|
||||||
[[colors.completion.item.selected.bg]]
|
[[colors.completion.item.selected.bg]]
|
||||||
=== colors.completion.item.selected.bg
|
=== colors.completion.item.selected.bg
|
||||||
@ -2325,20 +2333,6 @@ Type: <<types,Int>>
|
|||||||
|
|
||||||
Default: +pass:[30]+
|
Default: +pass:[30]+
|
||||||
|
|
||||||
[[ignore_case]]
|
|
||||||
=== ignore_case
|
|
||||||
When to find text on a page case-insensitively.
|
|
||||||
|
|
||||||
Type: <<types,String>>
|
|
||||||
|
|
||||||
Valid values:
|
|
||||||
|
|
||||||
* +always+: Search case-insensitively.
|
|
||||||
* +never+: Search case-sensitively.
|
|
||||||
* +smart+: Search case-sensitively if there are capital characters.
|
|
||||||
|
|
||||||
Default: +pass:[smart]+
|
|
||||||
|
|
||||||
[[input.forward_unbound_keys]]
|
[[input.forward_unbound_keys]]
|
||||||
=== input.forward_unbound_keys
|
=== input.forward_unbound_keys
|
||||||
Which unbound keys to forward to the webview in normal mode.
|
Which unbound keys to forward to the webview in normal mode.
|
||||||
@ -2556,8 +2550,30 @@ Type: <<types,Bool>>
|
|||||||
|
|
||||||
Default: +pass:[false]+
|
Default: +pass:[false]+
|
||||||
|
|
||||||
[[session_default_name]]
|
[[search.ignore_case]]
|
||||||
=== session_default_name
|
=== search.ignore_case
|
||||||
|
When to find text on a page case-insensitively.
|
||||||
|
|
||||||
|
Type: <<types,String>>
|
||||||
|
|
||||||
|
Valid values:
|
||||||
|
|
||||||
|
* +always+: Search case-insensitively.
|
||||||
|
* +never+: Search case-sensitively.
|
||||||
|
* +smart+: Search case-sensitively if there are capital characters.
|
||||||
|
|
||||||
|
Default: +pass:[smart]+
|
||||||
|
|
||||||
|
[[search.incremental]]
|
||||||
|
=== search.incremental
|
||||||
|
Find text on a page incrementally, renewing the search for each typed character.
|
||||||
|
|
||||||
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
|
Default: +pass:[true]+
|
||||||
|
|
||||||
|
[[session.default_name]]
|
||||||
|
=== session.default_name
|
||||||
Name of the session to save by default.
|
Name of the session to save by default.
|
||||||
If this is set to null, the session which was last loaded is saved.
|
If this is set to null, the session which was last loaded is saved.
|
||||||
|
|
||||||
@ -2565,6 +2581,14 @@ Type: <<types,SessionName>>
|
|||||||
|
|
||||||
Default: empty
|
Default: empty
|
||||||
|
|
||||||
|
[[session.lazy_restore]]
|
||||||
|
=== session.lazy_restore
|
||||||
|
Load a restored tab as soon as it takes focus.
|
||||||
|
|
||||||
|
Type: <<types,Bool>>
|
||||||
|
|
||||||
|
Default: +pass:[false]+
|
||||||
|
|
||||||
[[spellcheck.languages]]
|
[[spellcheck.languages]]
|
||||||
=== spellcheck.languages
|
=== spellcheck.languages
|
||||||
Languages to use for spell checking.
|
Languages to use for spell checking.
|
||||||
@ -2904,8 +2928,9 @@ The following placeholders are defined:
|
|||||||
* `{scroll_pos}`: Page scroll position.
|
* `{scroll_pos}`: Page scroll position.
|
||||||
* `{host}`: Host of the current web page.
|
* `{host}`: Host of the current web page.
|
||||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||||
* `{private}` : Indicates when private mode is enabled.
|
* `{private}`: Indicates when private mode is enabled.
|
||||||
* `{current_url}` : URL of the current web page.
|
* `{current_url}`: URL of the current web page.
|
||||||
|
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||||
|
|
||||||
|
|
||||||
Type: <<types,FormatString>>
|
Type: <<types,FormatString>>
|
||||||
|
@ -21,7 +21,7 @@ Those distributions only have Python 3.4 and a too old Qt version available,
|
|||||||
while qutebrowser requires Python 3.5 and Qt 5.7.1 or newer.
|
while qutebrowser requires Python 3.5 and Qt 5.7.1 or newer.
|
||||||
|
|
||||||
It should be possible to install Python 3.5 e.g. from the
|
It should be possible to install Python 3.5 e.g. from the
|
||||||
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via_ipca
|
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via
|
||||||
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
|
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
|
||||||
|
|
||||||
If you get qutebrowser running on those distributions, please
|
If you get qutebrowser running on those distributions, please
|
||||||
@ -35,7 +35,7 @@ Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
|
|||||||
QtWebEngine). However, it comes with Python 3.5, so you can
|
QtWebEngine). However, it comes with Python 3.5, so you can
|
||||||
<<tox,install qutebrowser via tox>>.
|
<<tox,install qutebrowser via tox>>.
|
||||||
|
|
||||||
Debian Stretch / Ubuntu 17.04 and newer
|
Debian Stretch / Ubuntu 17.04 and 17.10
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Those versions come with QtWebEngine in the repositories. This makes it possible
|
Those versions come with QtWebEngine in the repositories. This makes it possible
|
||||||
@ -54,7 +54,18 @@ Install the packages:
|
|||||||
# apt install ./qutebrowser_*_all.deb
|
# apt install ./qutebrowser_*_all.deb
|
||||||
----
|
----
|
||||||
|
|
||||||
Some additional hints:
|
Debian Testing / Ubuntu 18.04
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
On Debian Testing, qutebrowser is in the official repositories, and you can
|
||||||
|
install it with apt:
|
||||||
|
|
||||||
|
----
|
||||||
|
# apt install qutebrowser
|
||||||
|
----
|
||||||
|
|
||||||
|
Additional hints
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
|
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
|
||||||
QtWebEngine version.
|
QtWebEngine version.
|
||||||
@ -67,8 +78,7 @@ $ python3 scripts/asciidoc2html.py
|
|||||||
----
|
----
|
||||||
|
|
||||||
- If you prefer using QtWebKit, there's an up-to-date version available in
|
- If you prefer using QtWebKit, there's an up-to-date version available in
|
||||||
Debian experimental, or from http://repo.paretje.be/unstable/[this repository]
|
https://packages.debian.org/buster/libqt5webkit5[Debian Testing].
|
||||||
for Debian Stretch.
|
|
||||||
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
|
||||||
+
|
+
|
||||||
----
|
----
|
||||||
@ -90,6 +100,18 @@ qutebrowser is available in the official repositories:
|
|||||||
However, note that Fedora 25/26 won't be updated to qutebrowser v1.0, so you
|
However, note that Fedora 25/26 won't be updated to qutebrowser v1.0, so you
|
||||||
might want to <<tox,install qutebrowser via tox>> instead there.
|
might want to <<tox,install qutebrowser via tox>> instead there.
|
||||||
|
|
||||||
|
Additional hints
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Fedora only ships free software in the repositories.
|
||||||
|
To be able to play videos with proprietary codecs with QtWebEngine, you will
|
||||||
|
need to install an additional package from the RPM Fusion Free repository.
|
||||||
|
For more information see https://rpmfusion.org/Configuration.
|
||||||
|
|
||||||
|
-----
|
||||||
|
# dnf install qt5-qtwebengine-freeworld
|
||||||
|
-----
|
||||||
|
|
||||||
On Archlinux
|
On Archlinux
|
||||||
------------
|
------------
|
||||||
|
|
||||||
@ -379,8 +401,8 @@ local Qt install instead of installing PyQt in the virtualenv. However, unless
|
|||||||
you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It
|
you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It
|
||||||
also typically means you'll be using an older release of QtWebEngine.
|
also typically means you'll be using an older release of QtWebEngine.
|
||||||
|
|
||||||
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
On Windows, run `set PYTHON=C:\path\to\python.exe` (CMD) or ``$Env:PYTHON =
|
||||||
Python3 is in your PATH before running tox.
|
"..."` (Powershell) first.
|
||||||
|
|
||||||
Creating a wrapper script
|
Creating a wrapper script
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -1,207 +1,302 @@
|
|||||||
/* XPM */
|
/* XPM */
|
||||||
static char *qutebrowser[] = {
|
static char * qutebrowser_xpm[] = {
|
||||||
/* columns rows colors chars-per-pixel */
|
"32 32 267 2",
|
||||||
"32 32 169 2 ",
|
" c None",
|
||||||
" c #0A396E",
|
". c #9FD4FD",
|
||||||
". c #0B3C72",
|
"+ c #99CBFE",
|
||||||
"X c #0B4077",
|
"@ c #90C3FE",
|
||||||
"o c #0C437B",
|
"# c #89BFFE",
|
||||||
"O c #134175",
|
"$ c #81BCFF",
|
||||||
"+ c #15467C",
|
"% c #80BBFF",
|
||||||
"@ c #18477B",
|
"& c #9BCAFD",
|
||||||
"# c #1A497D",
|
"* c #A9DBFB",
|
||||||
"$ c #0D4B86",
|
"= c #88D3FB",
|
||||||
"% c #0F4E8D",
|
"- c #98CBFE",
|
||||||
"& c #124A80",
|
"; c #81BBFF",
|
||||||
"* c #1F4F83",
|
"> c #7EBAFF",
|
||||||
"= c #0E518C",
|
", c #84BDFF",
|
||||||
"- c #1F5084",
|
"' c #8DC2FF",
|
||||||
"; c #11508C",
|
") c #96C7FE",
|
||||||
": c #0F5193",
|
"! c #A0CCFE",
|
||||||
"> c #115799",
|
"~ c #A9D1FE",
|
||||||
", c #115B9C",
|
"{ c #CEE5FD",
|
||||||
"< c #204F83",
|
"] c #C7E3FC",
|
||||||
"1 c #245287",
|
"^ c #8AD3FB",
|
||||||
"2 c #2A598C",
|
"/ c #9DCFFD",
|
||||||
"3 c #325E8F",
|
"( c #C3DFFD",
|
||||||
"4 c #11609F",
|
"_ c #CDE4FD",
|
||||||
"5 c #346496",
|
": c #A3CEFE",
|
||||||
"6 c #3B6898",
|
"< c #94C6FE",
|
||||||
"7 c #115CA1",
|
"[ c #CAE5FC",
|
||||||
"8 c #115EAC",
|
"} c #7DD0FB",
|
||||||
"9 c #1263A3",
|
"| c #9ECDFD",
|
||||||
"0 c #1260AD",
|
"1 c #A1CDFE",
|
||||||
"q c #136BAC",
|
"2 c #8BC1FF",
|
||||||
"w c #136BB2",
|
"3 c #87BFFF",
|
||||||
"e c #1366BA",
|
"4 c #ADD4FE",
|
||||||
"r c #196BB2",
|
"5 c #C6E1FD",
|
||||||
"t c #157ABB",
|
"6 c #CCE3FC",
|
||||||
"y c #1577BB",
|
"7 c #A7DAFB",
|
||||||
"u c #2E6DB0",
|
"8 c #9DCBFE",
|
||||||
"i c #387FB1",
|
"9 c #78AFF1",
|
||||||
"p c #456E9A",
|
"0 c #6096D4",
|
||||||
"a c #4873A1",
|
"a c #4B82C0",
|
||||||
"s c #4375AA",
|
"b c #5A84B3",
|
||||||
"d c #507AA6",
|
"c c #6589B1",
|
||||||
"f c #597EA4",
|
"d c #6F92B9",
|
||||||
"g c #4D7EB3",
|
"e c #90AED0",
|
||||||
"h c #156FCB",
|
"f c #C4DBF5",
|
||||||
"j c #167AC5",
|
"g c #6286AE",
|
||||||
"k c #1675CA",
|
"h c #7D9EC2",
|
||||||
"l c #177BCE",
|
"i c #BADFFC",
|
||||||
"z c #1777D8",
|
"j c #85BDFE",
|
||||||
"x c #1476E4",
|
"k c #78B4F8",
|
||||||
"c c #167BE6",
|
"l c #4C83C0",
|
||||||
"v c #167DE8",
|
"m c #1E4F87",
|
||||||
"b c #197EEF",
|
"n c #0A396E",
|
||||||
"n c #1A7FF0",
|
"o c #345D8D",
|
||||||
"m c #1A80BE",
|
"p c #CDE4FC",
|
||||||
"M c #5F87AF",
|
"q c #88A7CA",
|
||||||
"N c #5D8BBA",
|
"r c #1D497C",
|
||||||
"B c #5A84B1",
|
"s c #799BBF",
|
||||||
"V c #6C8FB3",
|
"t c #8AC1FD",
|
||||||
"C c #6F96BE",
|
"u c #5E97D7",
|
||||||
"Z c #1886CC",
|
"v c #14457B",
|
||||||
"A c #1883D7",
|
"w c #4F76A0",
|
||||||
"S c #198DD5",
|
"x c #A9D5FC",
|
||||||
"D c #1987D9",
|
"y c #95C9FD",
|
||||||
"F c #198ADC",
|
"z c #4C82C1",
|
||||||
"G c #1A96DC",
|
"A c #0A3A6F",
|
||||||
"H c #3090D9",
|
"B c #C9E3FD",
|
||||||
"J c #1682E9",
|
"C c #95CCFC",
|
||||||
"K c #1983ED",
|
"D c #629BDB",
|
||||||
"L c #1689E9",
|
"E c #0B3A6F",
|
||||||
"P c #1A8DEE",
|
"F c #0C3B6F",
|
||||||
"I c #1B95ED",
|
"G c #4E749F",
|
||||||
"U c #1C9EEA",
|
"H c #8CACCE",
|
||||||
"Y c #1B97E4",
|
"I c #6185AD",
|
||||||
"T c #1A84F2",
|
"J c #CBE4FD",
|
||||||
"R c #1A8BF2",
|
"K c #89C0FF",
|
||||||
"E c #1C94F4",
|
"L c #98CDFA",
|
||||||
"W c #1D9CF5",
|
"M c #27558A",
|
||||||
"Q c #3388E6",
|
"N c #144175",
|
||||||
"! c #3D90E9",
|
"O c #9BB8D8",
|
||||||
"~ c #228EF3",
|
"P c #335D8C",
|
||||||
"^ c #229FF6",
|
"Q c #AFC9E6",
|
||||||
"/ c #3294F4",
|
"R c #AFD4FE",
|
||||||
"( c #3D9FF6",
|
"S c #91C7FD",
|
||||||
") c #339CF4",
|
"T c #A0C0DE",
|
||||||
"_ c #1CA2E5",
|
"U c #194779",
|
||||||
"` c #1DABEE",
|
"V c #80A1C5",
|
||||||
"' c #1DA4F6",
|
"W c #C8E1F9",
|
||||||
"] c #1EA9F7",
|
"X c #9CB9D8",
|
||||||
"[ c #1EADF8",
|
"Y c #7799BE",
|
||||||
"{ c #1FB4F9",
|
"Z c #6489B0",
|
||||||
"} c #1FB9FA",
|
"` c #7092B9",
|
||||||
"| c #20ACF8",
|
" . c #6E9DCF",
|
||||||
" . c #27A4F6",
|
".. c #79B5F9",
|
||||||
".. c #3DA9F6",
|
"+. c #83BDFE",
|
||||||
"X. c #20B9FA",
|
"@. c #7395BA",
|
||||||
"o. c #2EB6F9",
|
"#. c #315C8B",
|
||||||
"O. c #458DC9",
|
"$. c #7C9EC2",
|
||||||
"+. c #5C8DC1",
|
"%. c #C0D9F3",
|
||||||
"@. c #5795C6",
|
"&. c #7294BA",
|
||||||
"#. c #709DCB",
|
"*. c #5C94D4",
|
||||||
"$. c #74A8DD",
|
"=. c #91CCFC",
|
||||||
"%. c #4A97EA",
|
"-. c #88CBFA",
|
||||||
"&. c #4896EA",
|
";. c #5179A3",
|
||||||
"*. c #559EEA",
|
">. c #6E91B7",
|
||||||
"=. c #439AF5",
|
",. c #6084AC",
|
||||||
"-. c #46A3F6",
|
"'. c #96B3D4",
|
||||||
";. c #5FA9F6",
|
"). c #275283",
|
||||||
":. c #5EA6F3",
|
"!. c #0C3C71",
|
||||||
">. c #47BCF9",
|
"~. c #629CDC",
|
||||||
",. c #51B5F8",
|
"{. c #94C6FD",
|
||||||
"<. c #58BDF8",
|
"]. c #A7D2FC",
|
||||||
"1. c #68ABEF",
|
"^. c #36659A",
|
||||||
"2. c #7DB9E7",
|
"/. c #2C5788",
|
||||||
"3. c #63AEF7",
|
"(. c #9DBAD9",
|
||||||
"4. c #6FB1F7",
|
"_. c #B4CEEA",
|
||||||
"5. c #66B9F8",
|
":. c #476E9A",
|
||||||
"6. c #61B2F6",
|
"<. c #7EB9FE",
|
||||||
"7. c #71B4F7",
|
"[. c #8DC3FD",
|
||||||
"8. c #78B7F4",
|
"}. c #8CC2FE",
|
||||||
"9. c #72BFF9",
|
"|. c #2F619B",
|
||||||
"0. c #3BC0FA",
|
"1. c #87A6C9",
|
||||||
"q. c #6FCEFB",
|
"2. c #7A9BC0",
|
||||||
"w. c #6CC5FA",
|
"3. c #CBE2FB",
|
||||||
"e. c #7BCAF9",
|
"4. c #C7DFF8",
|
||||||
"r. c #89A7C3",
|
"5. c #6C8FB5",
|
||||||
"t. c #83A2C1",
|
"6. c #113F73",
|
||||||
"y. c #98B6D3",
|
"7. c #0F3D71",
|
||||||
"u. c #9DB9D3",
|
"8. c #547AA4",
|
||||||
"i. c #89B6E4",
|
"9. c #9CBAD9",
|
||||||
"p. c #83B6E9",
|
"0. c #B9D3EE",
|
||||||
"a. c #81BDF7",
|
"a. c #A3C0DE",
|
||||||
"s. c #83BFF8",
|
"b. c #31629A",
|
||||||
"d. c #9EC4E9",
|
"c. c #659EE0",
|
||||||
"f. c #8CC2F9",
|
"d. c #87BFFE",
|
||||||
"g. c #85CDFB",
|
"e. c #C3E0FD",
|
||||||
"h. c #87C4F9",
|
"f. c #4371A4",
|
||||||
"j. c #92C6F9",
|
"g. c #7496BB",
|
||||||
"k. c #95CAFA",
|
"h. c #90AFD1",
|
||||||
"l. c #9CCBFA",
|
"i. c #245081",
|
||||||
"z. c #89D7FC",
|
"j. c #416A96",
|
||||||
"x. c #91D9FC",
|
"k. c #B0CBE7",
|
||||||
"c. c #9CDEFD",
|
"l. c #CCE4FD",
|
||||||
"v. c #9ED2FB",
|
"m. c #7DB8FD",
|
||||||
"b. c #A7CAEC",
|
"n. c #1E5088",
|
||||||
"n. c #B5CEE3",
|
"o. c #497EBC",
|
||||||
"m. c #A1CEFA",
|
"p. c #C9E3FC",
|
||||||
"M. c #AED0F0",
|
"q. c #7193B9",
|
||||||
"N. c #ACD6FA",
|
"r. c #C6E0FB",
|
||||||
"B. c #A0DFFC",
|
"s. c #A2CDFE",
|
||||||
"V. c #AFD8FC",
|
"t. c #97C8FE",
|
||||||
"C. c #B5D9FB",
|
"u. c #A7D0FE",
|
||||||
"Z. c #BCDDFC",
|
"v. c #BDDCFD",
|
||||||
"A. c #BFDCF5",
|
"w. c #9EC2E8",
|
||||||
"S. c #ACE3FD",
|
"x. c #416996",
|
||||||
"D. c #B5E5FE",
|
"y. c #366AA6",
|
||||||
"F. c #BBE2FC",
|
"z. c #C0DEFC",
|
||||||
"G. c #CFE5F5",
|
"A. c #A2BFDD",
|
||||||
"H. c #C3E1FC",
|
"B. c #326299",
|
||||||
"J. c #CAE6FD",
|
"C. c #649DDF",
|
||||||
"K. c #CCEBFD",
|
"D. c #71ABED",
|
||||||
"L. c #C4EBFE",
|
"E. c #3569A4",
|
||||||
"P. c #D6EDFE",
|
"F. c #0D3C71",
|
||||||
"I. c #DAEEFD",
|
"G. c #6998CD",
|
||||||
"U. c #DEF1FE",
|
"H. c #30639D",
|
||||||
"Y. c #D6F2FE",
|
"I. c #A8D3F8",
|
||||||
"T. c #E4F4FE",
|
"J. c #2B5686",
|
||||||
"R. c #E9F6FE",
|
"K. c #3A679B",
|
||||||
"E. c #EBF8FF",
|
"L. c #ADCAEA",
|
||||||
"W. c None",
|
"M. c #85A6C9",
|
||||||
/* pixels */
|
"N. c #33639B",
|
||||||
"W.W.W.W.W.W.W.W.W.W.W.c.S.L.Y.E.E.S.X.} W.W.W.W.W.W.W.W.W.W.W.W.",
|
"O. c #9CCBFD",
|
||||||
"W.W.W.W.W.W.W.W.W.D.T.E.E.T.L.D.c.z.} } X.} } W.W.W.W.W.W.W.W.W.",
|
"P. c #86C2F7",
|
||||||
"W.W.W.W.W.W.W.B.T.T.R.T.R.U.0.X.z.S.} } } } { { X.W.W.W.W.W.W.W.",
|
"Q. c #0E3C71",
|
||||||
"W.W.W.W.W.W.x.x.K.T.T.T.L.P.q.o.{ } } ` _ { { { { { W.W.W.W.W.W.",
|
"R. c #1B4C83",
|
||||||
"W.W.W.W.W.c.P.D.G.u.r.i 9 Z _ { { G 4 X t { { { { { { W.W.W.W.W.",
|
"S. c #5D95D5",
|
||||||
"W.W.W.W.K.U.n.f O { = t { { { { [ { { W.W.W.W.",
|
"T. c #557BA5",
|
||||||
"W.W.W.F.I.t.. ' t { { [ [ [ [ [ >.W.W.W.",
|
"U. c #85C0F6",
|
||||||
"W.W.x.P.V ' X t ` [ [ [ [ [ [ o.e.W.W.",
|
"V. c #55A8EF",
|
||||||
"W.W.J.y. X t S Y Z $ ' . y [ [ [ ] [ [ | Z.J.W.W.",
|
"W. c #94B3D3",
|
||||||
"W.<.e.& , _ ] ] [ ] U . ' . y [ ' [ ] ] ] w.K.J.g.W.",
|
"X. c #1C497C",
|
||||||
"W.' S o ' ' [ ' [ ' ] o ' . y Y 9 = = 9 @.J.J.J.F.W.",
|
"Y. c #13437A",
|
||||||
"W.| , j ' ' ' ' ' ' ' o ' . $ p A.J.J.g.",
|
"Z. c #487DBB",
|
||||||
"' .. G ' ' ' ' ' ' ' o ' . M H.H.h.",
|
"`. c #7BB7FB",
|
||||||
",.2. . W ' W ' ' ' ' W . ' . M.A.x.",
|
" + c #76B1F5",
|
||||||
"N.M.. . W W W ' W W W W .w 9 I U 0 #.Z.m.",
|
".+ c #4E85C3",
|
||||||
" .9.O D W W W W ' W j $ % F W W W .5 d Z.C.",
|
"++ c #ACD3FE",
|
||||||
"W W ; 9 9.h.5...Q % o j W W W W W W O. 3 C.N.",
|
"@+ c #2F5989",
|
||||||
"E W 7 B b.d.a . w E E W W W E W E A @ C.l.",
|
"#+ c #7597BC",
|
||||||
"I E l u W E W E W E E E E A . - k.6.",
|
"$+ c #53A7EF",
|
||||||
"P E E 7 m.o E E E E E E E E l . = E P ",
|
"%+ c #C6E1FC",
|
||||||
"L E E E > . O s.o E E E E E E E E 7 , E L ",
|
"&+ c #B6D5F7",
|
||||||
"W.R E R ) #.5 1 6 N i.2 s.+ E E E E E E R L . k R W.",
|
"*+ c #5890D0",
|
||||||
"W.L R E -.m.m.m.m.m.m.2 m.@ N m.m.s.( R R % X E J W.",
|
"=+ c #4076B2",
|
||||||
"W.W.K R ~ a.m.l.l.l.l.2 s.+ < i.l.m.j.h % e K W.W.",
|
"-+ c #619ADB",
|
||||||
"W.W.J R R / l.l.l.l.k.2 s.+ * 5 + 8 R J W.W.",
|
";+ c #7CB7FC",
|
||||||
"W.W.W.v T R 3.k.k.j.k.2 2 j.& . 8 R v W.W.W.",
|
">+ c #7DB9FE",
|
||||||
"W.W.W.W.J T ~ 7.j.j.j.g +.p.j.s.+. . . : z T v W.W.W.W.",
|
",+ c #5087C6",
|
||||||
"W.W.W.W.W.c T T =.f.j.j.s.j.j.j.j.$.g s u e h b T T v W.W.W.W.W.",
|
"'+ c #134479",
|
||||||
"W.W.W.W.W.W.c b n 4.f.f.s.m.s.s.s.j.s.j./ T n T b c W.W.W.W.W.W.",
|
")+ c #23548D",
|
||||||
"W.W.W.W.W.W.W.c x 1.s.s.s.s.s.s.s.s.4.=.n T n c c W.W.W.W.W.W.W.",
|
"!+ c #24558D",
|
||||||
"W.W.W.W.W.W.W.W.W.&.*.1.a.s.s.s.s.3.n n v x x W.W.W.W.W.W.W.W.W.",
|
"~+ c #8AAACC",
|
||||||
"W.W.W.W.W.W.W.W.W.W.W.%.%.%.%.*.*.Q x x x W.W.W.W.W.W.W.W.W.W.W."
|
"{+ c #A2C1E1",
|
||||||
};
|
"]+ c #86C1F5",
|
||||||
|
"^+ c #B4D7FE",
|
||||||
|
"/+ c #6CA5E8",
|
||||||
|
"(+ c #22548C",
|
||||||
|
"_+ c #6D94BF",
|
||||||
|
":+ c #98B6D6",
|
||||||
|
"<+ c #134174",
|
||||||
|
"[+ c #84BDF5",
|
||||||
|
"}+ c #CAE4FC",
|
||||||
|
"|+ c #CBE3FD",
|
||||||
|
"1+ c #8FC3FF",
|
||||||
|
"2+ c #3F72AD",
|
||||||
|
"3+ c #49719C",
|
||||||
|
"4+ c #0C3B70",
|
||||||
|
"5+ c #9CBBDB",
|
||||||
|
"6+ c #79B7F3",
|
||||||
|
"7+ c #BFDCFD",
|
||||||
|
"8+ c #7FBBFF",
|
||||||
|
"9+ c #7E9FC3",
|
||||||
|
"0+ c #77B6F3",
|
||||||
|
"a+ c #A5CEF7",
|
||||||
|
"b+ c #9FCBFE",
|
||||||
|
"c+ c #3267A1",
|
||||||
|
"d+ c #A4CDF7",
|
||||||
|
"e+ c #B9D9FA",
|
||||||
|
"f+ c #C7E1FD",
|
||||||
|
"g+ c #90C3FF",
|
||||||
|
"h+ c #15457C",
|
||||||
|
"i+ c #558CCB",
|
||||||
|
"j+ c #2E5889",
|
||||||
|
"k+ c #7B9CC1",
|
||||||
|
"l+ c #C4DDF6",
|
||||||
|
"m+ c #BBDAFA",
|
||||||
|
"n+ c #CDE5FD",
|
||||||
|
"o+ c #B3D6FE",
|
||||||
|
"p+ c #80BAFF",
|
||||||
|
"q+ c #4E84C3",
|
||||||
|
"r+ c #3E73AF",
|
||||||
|
"s+ c #78B3F7",
|
||||||
|
"t+ c #5991D1",
|
||||||
|
"u+ c #477DBA",
|
||||||
|
"v+ c #4075B2",
|
||||||
|
"w+ c #5783B6",
|
||||||
|
"x+ c #BDD6F0",
|
||||||
|
"y+ c #A1CBF6",
|
||||||
|
"z+ c #90C4FF",
|
||||||
|
"A+ c #BCDBFD",
|
||||||
|
"B+ c #73B0F1",
|
||||||
|
"C+ c #C5E0FB",
|
||||||
|
"D+ c #91C5FF",
|
||||||
|
"E+ c #AED3FE",
|
||||||
|
"F+ c #C9E2FC",
|
||||||
|
"G+ c #76B2F2",
|
||||||
|
"H+ c #8BBFF9",
|
||||||
|
"I+ c #81BBFE",
|
||||||
|
"J+ c #9ECBFE",
|
||||||
|
"K+ c #84B8F3",
|
||||||
|
"L+ c #79B4F4",
|
||||||
|
"M+ c #88BEFA",
|
||||||
|
"N+ c #83BCFE",
|
||||||
|
"O+ c #A4CFFC",
|
||||||
|
"P+ c #A6CDF6",
|
||||||
|
"Q+ c #82B8F2",
|
||||||
|
"R+ c #529BEC",
|
||||||
|
" . + @ # $ % & * = ",
|
||||||
|
" - ; > > , ' ) ! ~ { { { ] ^ ",
|
||||||
|
" / ; > > > > ; ( _ : < { { { { { [ } ",
|
||||||
|
" | 1 2 > > > 2 3 4 5 { { { { { 6 { { { 7 ",
|
||||||
|
" 8 $ < 9 0 a b c d e { { { { f g h { { { { i ",
|
||||||
|
" j k l m n n n n n n o { { p q r n s { { { { { i ",
|
||||||
|
" t u v n n n n n n n n o { { w n n n s { { { { { { x ",
|
||||||
|
" y z A n n n n n n n n n o { { o n n n s { { { { { { B C ",
|
||||||
|
" D E n n n F G H I n n n o { { o n n n s { { { { { J K % ",
|
||||||
|
" L M n n n N O { { s n n n o { { o n n P Q { { { { { R > > S ",
|
||||||
|
" T n n n n H { { { s n n n o { { o U V 6 W X Y Z ` ...> > +. ",
|
||||||
|
" @.n n n #.{ { { { s n n n o { { $.%.W &.U n n n n n v *.> > =.",
|
||||||
|
"-.;.n n n >.{ { { { s n n n ,.{ { { '.).n n n n n n n n !.~.> {.",
|
||||||
|
"].^.n n n q { { { { s n /.(.{ { _.:.n n n n n n n n n n n m <.[.",
|
||||||
|
"}.|.n n n H { { { { 1.2.3.{ 4.5.6.n n n 7.8.9.0.a.b.n n n n c.d.",
|
||||||
|
"e.f.n n n g.{ { { { { { { h.i.n n n n j.k.{ { { l.m.n.n n n o.$ ",
|
||||||
|
"p.q.n n n /.r.s.t.u.v.w.x.n n n n i.h.{ { { { { { u.o.n n n y.$ ",
|
||||||
|
"z.A.n n n n B.C.D.u E.F.n n n 6.5.4.{ 3.2.1.{ { { { G.n n n H.d.",
|
||||||
|
"I.p J.n n n n n n n n n n n K.L.{ { (./.n s { { { { M.n n n N.O.",
|
||||||
|
"P.{ (.Q.n n n n n n n n R.S.> K _ ,.n n n s { { { { 5.n n n T.U.",
|
||||||
|
"V.{ { W.X.n n n n n Y.Z.`. +.+> ++o n n n s { { { { @+n n n #+$+",
|
||||||
|
" %+{ { &+*+Z.=+a -+;+>+,+'+)+> > !+n n n s { { { ~+n n n n {+ ",
|
||||||
|
" ]+{ { ^+> > > > > /+(+n n )+> > )+n n n _+{ { :+<+n n n o [+ ",
|
||||||
|
" }+{ |+1+> > > > l n n n )+> > )+n n n 2+~+3+E n n n 4+5+ ",
|
||||||
|
" 6+{ { 7+8+> > > l n n n )+> > )+n n n n n n n n n F 9+0+ ",
|
||||||
|
" a+{ { b+> > > l n n n c+> > )+n n n n n n n n r O d+ ",
|
||||||
|
" e+{ f+g+> > l n h+i+<.> > )+n n n n n E j+k+l+m+ ",
|
||||||
|
" e+{ n+o+p+q+r+s+> > > > t+u+v+w+2.W.x+{ { e+ ",
|
||||||
|
" y+{ { z+>+> > > > > > > > > A+{ { { { d+ ",
|
||||||
|
" B+C+) > > > > > > > > D+E+{ { { F+G+ ",
|
||||||
|
" H+I+> > > > > > J+{ { { C+K+ ",
|
||||||
|
" L+M+# N+; 8+O+P+Q+R+ "};
|
||||||
|
@ -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/*))
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
This directory contains various `requirements` files which are used by `tox` to
|
This directory contains various `requirements` files which are used by `tox` to
|
||||||
have reproducable tests with pinned versions.
|
have reproducible tests with pinned versions.
|
||||||
|
|
||||||
The files are generated based on unpinned requirements in `*.txt-raw` files.
|
The files are generated based on unpinned requirements in `*.txt-raw` files.
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
certifi==2017.11.5
|
certifi==2017.11.5
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
codecov==2.0.9
|
codecov==2.0.13
|
||||||
coverage==4.4.2
|
coverage==4.4.2
|
||||||
idna==2.6
|
idna==2.6
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
|
@ -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.4.0
|
flake8-bugbear==17.12.0
|
||||||
flake8-builtins==1.0
|
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.0.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
|
||||||
|
@ -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.1
|
setuptools==38.4.0
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
wheel==0.30.0
|
wheel==0.30.0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
altgraph==0.14
|
altgraph==0.15
|
||||||
future==0.16.0
|
future==0.16.0
|
||||||
macholib==1.8
|
macholib==1.9
|
||||||
pefile==2017.11.5
|
pefile==2017.11.5
|
||||||
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
astroid==1.5.3
|
astroid==1.6.0
|
||||||
certifi==2017.11.5
|
certifi==2017.11.5
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
github3.py==0.9.6
|
github3.py==0.9.6
|
||||||
@ -8,7 +8,7 @@ idna==2.6
|
|||||||
isort==4.2.15
|
isort==4.2.15
|
||||||
lazy-object-proxy==1.3.1
|
lazy-object-proxy==1.3.1
|
||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
pylint==1.7.4
|
pylint==1.8.1
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
|
@ -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
|
||||||
|
|
||||||
docutils==0.14
|
docutils==0.14
|
||||||
pyroma==2.2
|
pyroma==2.3
|
||||||
|
@ -12,12 +12,6 @@ git+https://github.com/jenisys/parse_type.git
|
|||||||
hg+https://bitbucket.org/pytest-dev/py
|
hg+https://bitbucket.org/pytest-dev/py
|
||||||
git+https://github.com/pytest-dev/pytest.git@features
|
git+https://github.com/pytest-dev/pytest.git@features
|
||||||
git+https://github.com/pytest-dev/pytest-bdd.git
|
git+https://github.com/pytest-dev/pytest-bdd.git
|
||||||
|
|
||||||
# This is broken at the moment because logfail tries to access
|
|
||||||
# LogCaptureHandler
|
|
||||||
# git+https://github.com/eisensheng/pytest-catchlog.git
|
|
||||||
pytest-catchlog==1.2.2
|
|
||||||
|
|
||||||
git+https://github.com/pytest-dev/pytest-cov.git
|
git+https://github.com/pytest-dev/pytest-cov.git
|
||||||
git+https://github.com/pytest-dev/pytest-faulthandler.git
|
git+https://github.com/pytest-dev/pytest-faulthandler.git
|
||||||
git+https://github.com/pytest-dev/pytest-instafail.git
|
git+https://github.com/pytest-dev/pytest-instafail.git
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# 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==5.10.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.4.2
|
||||||
@ -11,29 +11,29 @@ 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.38.5
|
hypothesis==3.44.16
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
# Jinja2==2.9.6
|
# Jinja2==2.10
|
||||||
Mako==1.0.7
|
Mako==1.0.7
|
||||||
# MarkupSafe==1.0
|
# MarkupSafe==1.0
|
||||||
parse==1.8.2
|
parse==1.8.2
|
||||||
parse-type==0.4.2
|
parse-type==0.4.2
|
||||||
|
pluggy==0.6.0
|
||||||
py==1.5.2
|
py==1.5.2
|
||||||
py-cpuinfo==3.3.0
|
py-cpuinfo==3.3.0
|
||||||
pytest==3.2.5
|
pytest==3.3.1 # rq.filter: != 3.3.2
|
||||||
pytest-bdd==2.19.0
|
pytest-bdd==2.19.0
|
||||||
pytest-benchmark==3.1.1
|
pytest-benchmark==3.1.1
|
||||||
pytest-catchlog==1.2.2
|
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
pytest-faulthandler==1.3.1
|
pytest-faulthandler==1.3.1
|
||||||
pytest-instafail==0.3.0
|
pytest-instafail==0.3.0
|
||||||
pytest-mock==1.6.3
|
pytest-mock==1.6.3
|
||||||
pytest-qt==2.3.0
|
pytest-qt==2.3.1
|
||||||
pytest-repeat==0.4.1
|
pytest-repeat==0.4.1
|
||||||
pytest-rerunfailures==3.1
|
pytest-rerunfailures==4.0
|
||||||
pytest-travis-fold==1.2.0
|
pytest-travis-fold==1.3.0
|
||||||
pytest-xvfb==1.0.0
|
pytest-xvfb==1.0.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.12.2
|
Werkzeug==0.14.1
|
||||||
|
@ -4,10 +4,9 @@ coverage
|
|||||||
Flask
|
Flask
|
||||||
hunter
|
hunter
|
||||||
hypothesis
|
hypothesis
|
||||||
pytest
|
pytest==3.3.1
|
||||||
pytest-bdd
|
pytest-bdd
|
||||||
pytest-benchmark
|
pytest-benchmark
|
||||||
pytest-catchlog
|
|
||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-faulthandler
|
pytest-faulthandler
|
||||||
pytest-instafail
|
pytest-instafail
|
||||||
@ -20,3 +19,4 @@ pytest-xvfb
|
|||||||
vulture
|
vulture
|
||||||
|
|
||||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||||
|
#@ filter: pytest != 3.3.2
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
pluggy==0.6.0
|
pluggy==0.6.0
|
||||||
py==1.5.2
|
py==1.5.2
|
||||||
|
six==1.11.0
|
||||||
tox==2.9.1
|
tox==2.9.1
|
||||||
virtualenv==15.1.0
|
virtualenv==15.1.0
|
||||||
|
@ -144,7 +144,7 @@ fi
|
|||||||
pkill -f "${program_}"
|
pkill -f "${program_}"
|
||||||
|
|
||||||
# start youtube download in stream mode (-o -) into temporary file
|
# start youtube download in stream mode (-o -) into temporary file
|
||||||
youtube-dl -qo - "$1" > ${file_to_cast} &
|
youtube-dl -qo - "$1" > "${file_to_cast}" &
|
||||||
ytdl_pid=$!
|
ytdl_pid=$!
|
||||||
|
|
||||||
msg info "Casting $1" >> "$QUTE_FIFO"
|
msg info "Casting $1" >> "$QUTE_FIFO"
|
||||||
@ -153,4 +153,4 @@ tail -F "${file_to_cast}" | ${program_} -
|
|||||||
|
|
||||||
# cleanup remaining background process and file on disk
|
# cleanup remaining background process and file on disk
|
||||||
kill ${ytdl_pid}
|
kill ${ytdl_pid}
|
||||||
rm -rf ${tmpdir}
|
rm -rf "${tmpdir}"
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
[ -z "$QUTE_URL" ] && QUTE_URL='http://google.com'
|
[ -z "$QUTE_URL" ] && QUTE_URL='http://google.com'
|
||||||
|
|
||||||
url=$(echo "$QUTE_URL" | cat - "$QUTE_CONFIG_DIR/quickmarks" "$QUTE_DATA_DIR/history" | dmenu -l 15 -p qutebrowser)
|
url=$(echo "$QUTE_URL" | cat - "$QUTE_CONFIG_DIR/quickmarks" "$QUTE_DATA_DIR/history" | dmenu -l 15 -p qutebrowser)
|
||||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | egrep "https?:" || echo "$url")
|
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | grep -E "https?:" || echo "$url")
|
||||||
|
|
||||||
[ -z "${url// }" ] && exit
|
[ -z "${url// }" ] && exit
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
set -euo pipefail
|
||||||
#
|
#
|
||||||
# Behavior:
|
# Behavior:
|
||||||
# Userscript for qutebrowser which will take the raw JSON text of the current
|
# Userscript for qutebrowser which will take the raw JSON text of the current
|
||||||
@ -19,29 +20,23 @@
|
|||||||
#
|
#
|
||||||
# Bryan Gilbert, 2017
|
# Bryan Gilbert, 2017
|
||||||
|
|
||||||
|
# do not run pygmentize on files larger than this amount of bytes
|
||||||
|
MAX_SIZE_PRETTIFY=10485760 # 10 MB
|
||||||
# default style to monokai if none is provided
|
# default style to monokai if none is provided
|
||||||
STYLE=${1:-monokai}
|
STYLE=${1:-monokai}
|
||||||
# format json using jq
|
|
||||||
FORMATTED_JSON="$(cat "$QUTE_TEXT" | jq '.')"
|
|
||||||
|
|
||||||
# if jq command failed or formatted json is empty, assume failure and terminate
|
TEMP_FILE="$(mktemp)"
|
||||||
if [ $? -ne 0 ] || [ -z "$FORMATTED_JSON" ]; then
|
jq . "$QUTE_TEXT" >"$TEMP_FILE"
|
||||||
echo "Invalid json, aborting..."
|
|
||||||
exit 1
|
# try GNU stat first and then OSX stat if the former fails
|
||||||
|
FILE_SIZE=$(
|
||||||
|
stat --printf="%s" "$TEMP_FILE" 2>/dev/null ||
|
||||||
|
stat -f%z "$TEMP_FILE" 2>/dev/null
|
||||||
|
)
|
||||||
|
if [ "$FILE_SIZE" -lt "$MAX_SIZE_PRETTIFY" ]; then
|
||||||
|
pygmentize -l json -f html -O full,style="$STYLE" <"$TEMP_FILE" >"${TEMP_FILE}_"
|
||||||
|
mv -f "${TEMP_FILE}_" "$TEMP_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# calculate the filesize of the json document
|
|
||||||
FILE_SIZE=$(ls -s --block-size=1048576 "$QUTE_TEXT" | cut -d' ' -f1)
|
|
||||||
|
|
||||||
# use pygments to pretty-up the json (syntax highlight) if file is less than 10MB
|
|
||||||
if [ "$FILE_SIZE" -lt "10" ]; then
|
|
||||||
FORMATTED_JSON="$(echo "$FORMATTED_JSON" | pygmentize -l json -f html -O full,style=$STYLE)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# create a temp file and write the formatted json to that file
|
|
||||||
TEMP_FILE="$(mktemp --suffix '.html')"
|
|
||||||
echo "$FORMATTED_JSON" > $TEMP_FILE
|
|
||||||
|
|
||||||
|
|
||||||
# send the command to qutebrowser to open the new file containing the formatted json
|
# send the command to qutebrowser to open the new file containing the formatted json
|
||||||
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"
|
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"
|
||||||
|
@ -76,6 +76,7 @@ crop-first-column() {
|
|||||||
ls-files() {
|
ls-files() {
|
||||||
# add the slash at the end of the download dir enforces to follow the
|
# add the slash at the end of the download dir enforces to follow the
|
||||||
# symlink, if the DOWNLOAD_DIR itself is a symlink
|
# symlink, if the DOWNLOAD_DIR itself is a symlink
|
||||||
|
# shellcheck disable=SC2010
|
||||||
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
|
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
|
||||||
| grep '^[-]' \
|
| grep '^[-]' \
|
||||||
| cut -d' ' -f3- \
|
| cut -d' ' -f3- \
|
||||||
@ -91,10 +92,10 @@ if [ "${#entries[@]}" -eq 0 ] ; then
|
|||||||
die "Download directory »${DOWNLOAD_DIR}« empty"
|
die "Download directory »${DOWNLOAD_DIR}« empty"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
line=$(printf "%s\n" "${entries[@]}" \
|
line=$(printf '%s\n' "${entries[@]}" \
|
||||||
| crop-first-column 55 \
|
| crop-first-column 55 \
|
||||||
| column -s $'\t' -t \
|
| column -s $'\t' -t \
|
||||||
| $ROFI_CMD "${rofi_default_args[@]}" $ROFI_ARGS) || true
|
| $ROFI_CMD "${rofi_default_args[@]}" "$ROFI_ARGS") || true
|
||||||
if [ -z "$line" ]; then
|
if [ -z "$line" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
@ -64,7 +64,7 @@ die() {
|
|||||||
javascript_escape() {
|
javascript_escape() {
|
||||||
# print the first argument in an escaped way, such that it can safely
|
# print the first argument in an escaped way, such that it can safely
|
||||||
# be used within javascripts double quotes
|
# be used within javascripts double quotes
|
||||||
sed "s,[\\\'\"],\\\&,g" <<< "$1"
|
sed "s,[\\\\'\"],\\\\&,g" <<< "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ======================================================= #
|
# ======================================================= #
|
||||||
@ -178,7 +178,7 @@ choose_entry_menu() {
|
|||||||
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
|
if [ "$nr" -eq 1 ] && ! ((menu_if_one_entry)) ; then
|
||||||
file="${files[0]}"
|
file="${files[0]}"
|
||||||
else
|
else
|
||||||
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
|
file=$( printf '%s\n' "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ pass_backend() {
|
|||||||
if ((match_line)) ; then
|
if ((match_line)) ; then
|
||||||
# add entries with matching URL-tag
|
# add entries with matching URL-tag
|
||||||
while read -r -d "" passfile ; do
|
while read -r -d "" passfile ; do
|
||||||
if $GPG "${GPG_OPTS}" -d "$passfile" \
|
if $GPG "${GPG_OPTS[@]}" -d "$passfile" \
|
||||||
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
|
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
|
||||||
then
|
then
|
||||||
passfile="${passfile#$PREFIX}"
|
passfile="${passfile#$PREFIX}"
|
||||||
@ -269,7 +269,7 @@ pass_backend() {
|
|||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < <($GPG "${GPG_OPTS}" -d "$path" )
|
done < <($GPG "${GPG_OPTS[@]}" -d "$path" )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# =======================================================
|
# =======================================================
|
||||||
@ -283,8 +283,8 @@ secret_backend() {
|
|||||||
query_entries() {
|
query_entries() {
|
||||||
local domain="$1"
|
local domain="$1"
|
||||||
while read -r line ; do
|
while read -r line ; do
|
||||||
if [[ "$line" =~ "attribute.username = " ]] ; then
|
if [[ "$line" == "attribute.username = "* ]] ; then
|
||||||
files+=("$domain ${line#${BASH_REMATCH[0]}}")
|
files+=("$domain ${line:21}")
|
||||||
fi
|
fi
|
||||||
done < <( secret-tool search --unlock --all domain "$domain" 2>&1 )
|
done < <( secret-tool search --unlock --all domain "$domain" 2>&1 )
|
||||||
}
|
}
|
||||||
@ -303,6 +303,7 @@ pass_backend
|
|||||||
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
QUTE_CONFIG_DIR=${QUTE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/qutebrowser/}
|
||||||
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
||||||
if [ -f "$PWFILL_CONFIG" ] ; then
|
if [ -f "$PWFILL_CONFIG" ] ; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
source "$PWFILL_CONFIG"
|
source "$PWFILL_CONFIG"
|
||||||
fi
|
fi
|
||||||
init
|
init
|
||||||
@ -311,7 +312,7 @@ simplify_url "$QUTE_URL"
|
|||||||
query_entries "${simple_url}"
|
query_entries "${simple_url}"
|
||||||
no_entries_found
|
no_entries_found
|
||||||
# remove duplicates
|
# remove duplicates
|
||||||
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
|
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort | uniq )
|
||||||
choose_entry
|
choose_entry
|
||||||
if [ -z "$file" ] ; then
|
if [ -z "$file" ] ; then
|
||||||
# choose_entry didn't want any of these entries
|
# choose_entry didn't want any of these entries
|
||||||
|
@ -35,17 +35,12 @@ get_selection() {
|
|||||||
|
|
||||||
# Main
|
# Main
|
||||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
|
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
|
||||||
if [[ -s $confdir/dmenu/font ]]; then
|
[[ -s $confdir/dmenu/font ]] && read -r font < "$confdir"/dmenu/font
|
||||||
read -r font < "$confdir"/dmenu/font
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $font ]]; then
|
[[ $font ]] && opts+=(-fn "$font")
|
||||||
opts+=(-fn "$font")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -s $optsfile ]]; then
|
# shellcheck source=/dev/null
|
||||||
source "$optsfile"
|
[[ -s $optsfile ]] && source "$optsfile"
|
||||||
fi
|
|
||||||
|
|
||||||
url=$(get_selection)
|
url=$(get_selection)
|
||||||
url=${url/*http/http}
|
url=${url/*http/http}
|
||||||
|
@ -32,7 +32,7 @@ add_feed () {
|
|||||||
if grep -Fq "$1" "feeds"; then
|
if grep -Fq "$1" "feeds"; then
|
||||||
notice "$1 is saved already."
|
notice "$1 is saved already."
|
||||||
else
|
else
|
||||||
printf "%s\n" "$1" >> "feeds"
|
printf '%s\n' "$1" >> "feeds"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ notice () {
|
|||||||
|
|
||||||
# Update a database of a feed and open new URLs
|
# Update a database of a feed and open new URLs
|
||||||
read_items () {
|
read_items () {
|
||||||
cd read_urls
|
cd read_urls || return 1
|
||||||
feed_file="$(echo "$1" | tr -d /)"
|
feed_file="$(echo "$1" | tr -d /)"
|
||||||
feed_temp_file="$(mktemp "$feed_file.tmp.XXXXXXXXXX")"
|
feed_temp_file="$(mktemp "$feed_file.tmp.XXXXXXXXXX")"
|
||||||
feed_new_items="$(mktemp "$feed_file.new.XXXXXXXXXX")"
|
feed_new_items="$(mktemp "$feed_file.new.XXXXXXXXXX")"
|
||||||
@ -75,7 +75,7 @@ read_items () {
|
|||||||
cat "$feed_new_items" >> "$feed_file"
|
cat "$feed_new_items" >> "$feed_file"
|
||||||
sort -o "$feed_file" "$feed_file"
|
sort -o "$feed_file" "$feed_file"
|
||||||
rm "$feed_temp_file" "$feed_new_items"
|
rm "$feed_temp_file" "$feed_new_items"
|
||||||
fi | while read item; do
|
fi | while read -r item; do
|
||||||
echo "open -t $item" > "$QUTE_FIFO"
|
echo "open -t $item" > "$QUTE_FIFO"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@ -85,7 +85,7 @@ if [ ! -d "$config_dir/read_urls" ]; then
|
|||||||
mkdir -p "$config_dir/read_urls"
|
mkdir -p "$config_dir/read_urls"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$config_dir"
|
cd "$config_dir" || exit 1
|
||||||
|
|
||||||
if [ $# != 0 ]; then
|
if [ $# != 0 ]; then
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
@ -115,7 +115,7 @@ if < /dev/null grep --help 2>&1 | grep -q -- -a; then
|
|||||||
text_only="-a"
|
text_only="-a"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
while read feed_url; do
|
while read -r feed_url; do
|
||||||
read_items "$feed_url" &
|
read_items "$feed_url" &
|
||||||
done < "$config_dir/feeds"
|
done < "$config_dir/feeds"
|
||||||
|
|
||||||
|
@ -25,12 +25,10 @@
|
|||||||
[[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE
|
[[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE
|
||||||
|
|
||||||
# try to add the task and grab the output
|
# try to add the task and grab the output
|
||||||
msg="$(task add $title $@ 2>&1)"
|
if msg="$(task add "$title" "$*" 2>&1)"; then
|
||||||
|
|
||||||
if [[ $? == 0 ]]; then
|
|
||||||
# annotate the new task with the url, send the output back to the browser
|
# annotate the new task with the url, send the output back to the browser
|
||||||
task +LATEST annotate "$QUTE_URL"
|
task +LATEST annotate "$QUTE_URL"
|
||||||
echo "message-info '$msg'" >> $QUTE_FIFO
|
echo "message-info '$msg'" >> "$QUTE_FIFO"
|
||||||
else
|
else
|
||||||
echo "message-error '$msg'" >> $QUTE_FIFO
|
echo "message-error '$msg'" >> "$QUTE_FIFO"
|
||||||
fi
|
fi
|
||||||
|
@ -50,7 +50,7 @@ msg() {
|
|||||||
MPV_COMMAND=${MPV_COMMAND:-mpv}
|
MPV_COMMAND=${MPV_COMMAND:-mpv}
|
||||||
# Warning: spaces in single flags are not supported
|
# Warning: spaces in single flags are not supported
|
||||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl --ytdl-raw-options=yes-playlist=}
|
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl --ytdl-raw-options=yes-playlist=}
|
||||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
IFS=" " read -r -a video_command <<< "$MPV_COMMAND $MPV_FLAGS"
|
||||||
|
|
||||||
js() {
|
js() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2017 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, 0)
|
||||||
__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."
|
||||||
|
|
||||||
|
@ -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)
|
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,9 @@ 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 Greasemonkey...")
|
||||||
|
greasemonkey.init()
|
||||||
|
|
||||||
log.init.debug("Misc initialization...")
|
log.init.debug("Misc initialization...")
|
||||||
macros.init()
|
macros.init()
|
||||||
# Init backend-specific stuff
|
# Init backend-specific stuff
|
||||||
@ -561,8 +564,8 @@ class Quitter:
|
|||||||
cwd = os.path.abspath(os.path.dirname(sys.executable))
|
cwd = os.path.abspath(os.path.dirname(sys.executable))
|
||||||
else:
|
else:
|
||||||
args = [sys.executable, '-m', 'qutebrowser']
|
args = [sys.executable, '-m', 'qutebrowser']
|
||||||
cwd = os.path.join(os.path.abspath(os.path.dirname(
|
cwd = os.path.join(
|
||||||
qutebrowser.__file__)), '..')
|
os.path.abspath(os.path.dirname(qutebrowser.__file__)), '..')
|
||||||
if not os.path.isdir(cwd):
|
if not os.path.isdir(cwd):
|
||||||
# Probably running from a python egg. Let's fallback to
|
# Probably running from a python egg. Let's fallback to
|
||||||
# cwd=None and see if that works out.
|
# cwd=None and see if that works out.
|
||||||
@ -869,10 +872,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,
|
||||||
}
|
}
|
||||||
@ -897,19 +896,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.
|
||||||
|
|
||||||
|
@ -487,6 +487,7 @@ class AbstractHistory:
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def back(self, count=1):
|
def back(self, count=1):
|
||||||
|
"""Go back in the tab's history."""
|
||||||
idx = self.current_idx() - count
|
idx = self.current_idx() - count
|
||||||
if idx >= 0:
|
if idx >= 0:
|
||||||
self._go_to_item(self._item_at(idx))
|
self._go_to_item(self._item_at(idx))
|
||||||
@ -495,6 +496,7 @@ class AbstractHistory:
|
|||||||
raise WebTabError("At beginning of history.")
|
raise WebTabError("At beginning of history.")
|
||||||
|
|
||||||
def forward(self, count=1):
|
def forward(self, count=1):
|
||||||
|
"""Go forward in the tab's history."""
|
||||||
idx = self.current_idx() + count
|
idx = self.current_idx() + count
|
||||||
if idx < len(self):
|
if idx < len(self):
|
||||||
self._go_to_item(self._item_at(idx))
|
self._go_to_item(self._item_at(idx))
|
||||||
@ -704,8 +706,8 @@ class AbstractTab(QWidget):
|
|||||||
# This only gives us some mild protection against re-using events, but
|
# This only gives us some mild protection against re-using events, but
|
||||||
# it's certainly better than a segfault.
|
# it's certainly better than a segfault.
|
||||||
if getattr(evt, 'posted', False):
|
if getattr(evt, 'posted', False):
|
||||||
raise AssertionError("Can't re-use an event which was already "
|
raise utils.Unreachable("Can't re-use an event which was already "
|
||||||
"posted!")
|
"posted!")
|
||||||
recipient = self.event_target()
|
recipient = self.event_target()
|
||||||
evt.posted = True
|
evt.posted = True
|
||||||
QApplication.postEvent(recipient, evt)
|
QApplication.postEvent(recipient, evt)
|
||||||
|
@ -39,7 +39,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
|
||||||
@ -518,7 +518,7 @@ class CommandDispatcher:
|
|||||||
return newtab
|
return newtab
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
@cmdutils.argument('index', completion=miscmodels.buffer)
|
@cmdutils.argument('index', completion=miscmodels.other_buffer)
|
||||||
def tab_take(self, index):
|
def tab_take(self, index):
|
||||||
"""Take a tab from another window.
|
"""Take a tab from another window.
|
||||||
|
|
||||||
@ -673,7 +673,7 @@ class CommandDispatcher:
|
|||||||
self._open(new_url, tab, bg, window, related=True)
|
self._open(new_url, tab, bg, window, related=True)
|
||||||
else: # pragma: no cover
|
else: # pragma: no cover
|
||||||
raise ValueError("Got called with invalid value {} for "
|
raise ValueError("Got called with invalid value {} for "
|
||||||
"`where'.".format(where))
|
"`where'.".format(where))
|
||||||
except navigate.Error as e:
|
except navigate.Error as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
|
|
||||||
@ -953,22 +953,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):
|
||||||
@ -1177,7 +1180,8 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||||
maxsplit=0, no_replace_variables=True)
|
maxsplit=0, no_replace_variables=True)
|
||||||
def spawn(self, cmdline, userscript=False, verbose=False, detach=False):
|
def spawn(self, cmdline, userscript=False, verbose=False,
|
||||||
|
output=False, detach=False):
|
||||||
"""Spawn a command in a shell.
|
"""Spawn a command in a shell.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1188,6 +1192,7 @@ class CommandDispatcher:
|
|||||||
(or `$XDG_DATA_DIR`)
|
(or `$XDG_DATA_DIR`)
|
||||||
- `/usr/share/qutebrowser/userscripts`
|
- `/usr/share/qutebrowser/userscripts`
|
||||||
verbose: Show notifications when the command started/exited.
|
verbose: Show notifications when the command started/exited.
|
||||||
|
output: Whether the output should be shown in a new tab.
|
||||||
detach: Whether the command should be detached from qutebrowser.
|
detach: Whether the command should be detached from qutebrowser.
|
||||||
cmdline: The commandline to execute.
|
cmdline: The commandline to execute.
|
||||||
"""
|
"""
|
||||||
@ -1214,6 +1219,11 @@ class CommandDispatcher:
|
|||||||
else:
|
else:
|
||||||
proc.start(cmd, args)
|
proc.start(cmd, args)
|
||||||
|
|
||||||
|
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."""
|
||||||
@ -1418,27 +1428,14 @@ class CommandDispatcher:
|
|||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
@cmdutils.argument('dest_old', hide=True)
|
def download(self, url=None, *, mhtml_=False, dest=None):
|
||||||
def download(self, url=None, dest_old=None, *, mhtml_=False, dest=None):
|
|
||||||
"""Download a given URL, or current page if no URL given.
|
"""Download a given URL, or current page if no URL given.
|
||||||
|
|
||||||
The form `:download [url] [dest]` is deprecated, use `:download --dest
|
|
||||||
[dest] [url]` instead.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The URL to download. If not given, download the current page.
|
url: The URL to download. If not given, download the current page.
|
||||||
dest_old: (deprecated) Same as dest.
|
|
||||||
dest: The file path to write the download to, or None to ask.
|
dest: The file path to write the download to, or None to ask.
|
||||||
mhtml_: Download the current page and all assets as mhtml file.
|
mhtml_: Download the current page and all assets as mhtml file.
|
||||||
"""
|
"""
|
||||||
if dest_old is not None:
|
|
||||||
message.warning(":download [url] [dest] is deprecated - use "
|
|
||||||
":download --dest [dest] [url]")
|
|
||||||
if dest is not None:
|
|
||||||
raise cmdexc.CommandError("Can't give two destinations for the"
|
|
||||||
" download.")
|
|
||||||
dest = dest_old
|
|
||||||
|
|
||||||
# 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)
|
scope='window', window=self._win_id)
|
||||||
@ -1528,6 +1525,7 @@ class CommandDispatcher:
|
|||||||
dest = os.path.expanduser(dest)
|
dest = os.path.expanduser(dest)
|
||||||
|
|
||||||
def callback(data):
|
def callback(data):
|
||||||
|
"""Write the data to disk."""
|
||||||
try:
|
try:
|
||||||
with open(dest, 'w', encoding='utf-8') as f:
|
with open(dest, 'w', encoding='utf-8') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
@ -1649,6 +1647,8 @@ class CommandDispatcher:
|
|||||||
except webelem.Error as e:
|
except webelem.Error as e:
|
||||||
raise cmdexc.CommandError(str(e))
|
raise cmdexc.CommandError(str(e))
|
||||||
|
|
||||||
|
mainwindow.raise_window(objreg.last_focused_window(), alert=False)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
|
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
|
||||||
scope='window')
|
scope='window')
|
||||||
def insert_text(self, text):
|
def insert_text(self, text):
|
||||||
@ -1742,7 +1742,8 @@ class CommandDispatcher:
|
|||||||
elif going_up and tab.scroller.pos_px().y() > old_scroll_pos.y():
|
elif going_up and tab.scroller.pos_px().y() > old_scroll_pos.y():
|
||||||
message.info("Search hit TOP, continuing at BOTTOM")
|
message.info("Search hit TOP, continuing at BOTTOM")
|
||||||
else:
|
else:
|
||||||
message.warning("Text '{}' not found on page!".format(text))
|
message.warning("Text '{}' not found on page!".format(text),
|
||||||
|
replace=True)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||||
maxsplit=0)
|
maxsplit=0)
|
||||||
@ -1762,7 +1763,7 @@ class CommandDispatcher:
|
|||||||
return
|
return
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
'ignore_case': config.val.ignore_case,
|
'ignore_case': config.val.search.ignore_case,
|
||||||
'reverse': reverse,
|
'reverse': reverse,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2045,6 +2046,7 @@ class CommandDispatcher:
|
|||||||
jseval_cb = None
|
jseval_cb = None
|
||||||
else:
|
else:
|
||||||
def jseval_cb(out):
|
def jseval_cb(out):
|
||||||
|
"""Show the data returned from JS."""
|
||||||
if out is None:
|
if out is None:
|
||||||
# Getting the actual error (if any) seems to be difficult.
|
# Getting the actual error (if any) seems to be difficult.
|
||||||
# The error does end up in
|
# The error does end up in
|
||||||
@ -2197,11 +2199,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)))
|
|
||||||
|
@ -103,6 +103,8 @@ def immediate_download_path(prompt_download_directory=None):
|
|||||||
if not prompt_download_directory:
|
if not prompt_download_directory:
|
||||||
return download_dir()
|
return download_dir()
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _path_suggestion(filename):
|
def _path_suggestion(filename):
|
||||||
"""Get the suggested file path.
|
"""Get the suggested file path.
|
||||||
@ -180,7 +182,7 @@ def transform_path(path):
|
|||||||
path = utils.expand_windows_drive(path)
|
path = utils.expand_windows_drive(path)
|
||||||
# Drive dependent working directories are not supported, e.g.
|
# Drive dependent working directories are not supported, e.g.
|
||||||
# E:filename is invalid
|
# E:filename is invalid
|
||||||
if re.match(r'[A-Z]:[^\\]', path, re.IGNORECASE):
|
if re.search(r'^[A-Z]:[^\\]', path, re.IGNORECASE):
|
||||||
return None
|
return None
|
||||||
# Paths like COM1, ...
|
# Paths like COM1, ...
|
||||||
# See https://github.com/qutebrowser/qutebrowser/issues/82
|
# See https://github.com/qutebrowser/qutebrowser/issues/82
|
||||||
@ -990,7 +992,7 @@ class DownloadModel(QAbstractListModel):
|
|||||||
if not count:
|
if not count:
|
||||||
count = len(self)
|
count = len(self)
|
||||||
raise cmdexc.CommandError("Download {} is already done!"
|
raise cmdexc.CommandError("Download {} is already done!"
|
||||||
.format(count))
|
.format(count))
|
||||||
download.cancel()
|
download.cancel()
|
||||||
|
|
||||||
@cmdutils.register(instance='download-model', scope='window')
|
@cmdutils.register(instance='download-model', scope='window')
|
||||||
|
224
qutebrowser/browser/greasemonkey.py
Normal file
224
qutebrowser/browser/greasemonkey.py
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||||
|
#
|
||||||
|
# This file is part of qutebrowser.
|
||||||
|
#
|
||||||
|
# qutebrowser is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# qutebrowser is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""Load, parse and make available Greasemonkey scripts."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import fnmatch
|
||||||
|
import functools
|
||||||
|
import glob
|
||||||
|
|
||||||
|
import attr
|
||||||
|
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
|
||||||
|
|
||||||
|
from qutebrowser.utils import log, standarddir, jinja, objreg
|
||||||
|
from qutebrowser.commands import cmdutils
|
||||||
|
|
||||||
|
|
||||||
|
def _scripts_dir():
|
||||||
|
"""Get the directory of the scripts."""
|
||||||
|
return os.path.join(standarddir.data(), 'greasemonkey')
|
||||||
|
|
||||||
|
|
||||||
|
class GreasemonkeyScript:
|
||||||
|
|
||||||
|
"""Container class for userscripts, parses metadata blocks."""
|
||||||
|
|
||||||
|
def __init__(self, properties, code):
|
||||||
|
self._code = code
|
||||||
|
self.includes = []
|
||||||
|
self.excludes = []
|
||||||
|
self.description = None
|
||||||
|
self.name = None
|
||||||
|
self.namespace = None
|
||||||
|
self.run_at = None
|
||||||
|
self.script_meta = None
|
||||||
|
self.runs_on_sub_frames = True
|
||||||
|
for name, value in properties:
|
||||||
|
if name == 'name':
|
||||||
|
self.name = value
|
||||||
|
elif name == 'namespace':
|
||||||
|
self.namespace = value
|
||||||
|
elif name == 'description':
|
||||||
|
self.description = value
|
||||||
|
elif name in ['include', 'match']:
|
||||||
|
self.includes.append(value)
|
||||||
|
elif name in ['exclude', 'exclude_match']:
|
||||||
|
self.excludes.append(value)
|
||||||
|
elif name == 'run-at':
|
||||||
|
self.run_at = value
|
||||||
|
elif name == 'noframes':
|
||||||
|
self.runs_on_sub_frames = False
|
||||||
|
|
||||||
|
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||||
|
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, source):
|
||||||
|
"""GreasemonkeyScript factory.
|
||||||
|
|
||||||
|
Takes a userscript source and returns a GreasemonkeyScript.
|
||||||
|
Parses the Greasemonkey metadata block, if present, to fill out
|
||||||
|
attributes.
|
||||||
|
"""
|
||||||
|
matches = re.split(cls.HEADER_REGEX, source, maxsplit=2)
|
||||||
|
try:
|
||||||
|
_head, props, _code = matches
|
||||||
|
except ValueError:
|
||||||
|
props = ""
|
||||||
|
script = cls(re.findall(cls.PROPS_REGEX, props), source)
|
||||||
|
script.script_meta = props
|
||||||
|
if not props:
|
||||||
|
script.includes = ['*']
|
||||||
|
return script
|
||||||
|
|
||||||
|
def code(self):
|
||||||
|
"""Return the processed JavaScript code of this script.
|
||||||
|
|
||||||
|
Adorns the source code with GM_* methods for Greasemonkey
|
||||||
|
compatibility and wraps it in an IFFE to hide it within a
|
||||||
|
lexical scope. Note that this means line numbers in your
|
||||||
|
browser's debugger/inspector will not match up to the line
|
||||||
|
numbers in the source script directly.
|
||||||
|
"""
|
||||||
|
return jinja.js_environment.get_template(
|
||||||
|
'greasemonkey_wrapper.js').render(
|
||||||
|
scriptName="/".join([self.namespace or '', self.name]),
|
||||||
|
scriptInfo=self._meta_json(),
|
||||||
|
scriptMeta=self.script_meta,
|
||||||
|
scriptSource=self._code)
|
||||||
|
|
||||||
|
def _meta_json(self):
|
||||||
|
return json.dumps({
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.description,
|
||||||
|
'matches': self.includes,
|
||||||
|
'includes': self.includes,
|
||||||
|
'excludes': self.excludes,
|
||||||
|
'run-at': self.run_at,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s
|
||||||
|
class MatchingScripts(object):
|
||||||
|
|
||||||
|
"""All userscripts registered to run on a particular url."""
|
||||||
|
|
||||||
|
url = attr.ib()
|
||||||
|
start = attr.ib(default=attr.Factory(list))
|
||||||
|
end = attr.ib(default=attr.Factory(list))
|
||||||
|
idle = attr.ib(default=attr.Factory(list))
|
||||||
|
|
||||||
|
|
||||||
|
class GreasemonkeyManager(QObject):
|
||||||
|
|
||||||
|
"""Manager of userscripts and a Greasemonkey compatible environment.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
scripts_reloaded: Emitted when scripts are reloaded from disk.
|
||||||
|
Any cached or already-injected scripts should be
|
||||||
|
considered obselete.
|
||||||
|
"""
|
||||||
|
|
||||||
|
scripts_reloaded = pyqtSignal()
|
||||||
|
# https://wiki.greasespot.net/Include_and_exclude_rules#Greaseable_schemes
|
||||||
|
# Limit the schemes scripts can run on due to unreasonable levels of
|
||||||
|
# exploitability
|
||||||
|
greaseable_schemes = ['http', 'https', 'ftp', 'file']
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.load_scripts()
|
||||||
|
|
||||||
|
@cmdutils.register(name='greasemonkey-reload',
|
||||||
|
instance='greasemonkey')
|
||||||
|
def load_scripts(self):
|
||||||
|
"""Re-read Greasemonkey scripts from disk.
|
||||||
|
|
||||||
|
The scripts are read from a 'greasemonkey' subdirectory in
|
||||||
|
qutebrowser's data directory (see `:version`).
|
||||||
|
"""
|
||||||
|
self._run_start = []
|
||||||
|
self._run_end = []
|
||||||
|
self._run_idle = []
|
||||||
|
|
||||||
|
scripts_dir = os.path.abspath(_scripts_dir())
|
||||||
|
log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
|
||||||
|
for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
|
||||||
|
if not os.path.isfile(script_filename):
|
||||||
|
continue
|
||||||
|
script_path = os.path.join(scripts_dir, script_filename)
|
||||||
|
with open(script_path, encoding='utf-8') as script_file:
|
||||||
|
script = GreasemonkeyScript.parse(script_file.read())
|
||||||
|
if not script.name:
|
||||||
|
script.name = script_filename
|
||||||
|
|
||||||
|
if script.run_at == 'document-start':
|
||||||
|
self._run_start.append(script)
|
||||||
|
elif script.run_at == 'document-end':
|
||||||
|
self._run_end.append(script)
|
||||||
|
elif script.run_at == 'document-idle':
|
||||||
|
self._run_idle.append(script)
|
||||||
|
else:
|
||||||
|
log.greasemonkey.warning("Script {} has invalid run-at "
|
||||||
|
"defined, defaulting to "
|
||||||
|
"document-end"
|
||||||
|
.format(script_path))
|
||||||
|
# Default as per
|
||||||
|
# https://wiki.greasespot.net/Metadata_Block#.40run-at
|
||||||
|
self._run_end.append(script)
|
||||||
|
log.greasemonkey.debug("Loaded script: {}".format(script.name))
|
||||||
|
self.scripts_reloaded.emit()
|
||||||
|
|
||||||
|
def scripts_for(self, url):
|
||||||
|
"""Fetch scripts that are registered to run for url.
|
||||||
|
|
||||||
|
returns a tuple of lists of scripts meant to run at (document-start,
|
||||||
|
document-end, document-idle)
|
||||||
|
"""
|
||||||
|
if url.scheme() not in self.greaseable_schemes:
|
||||||
|
return MatchingScripts(url, [], [], [])
|
||||||
|
match = functools.partial(fnmatch.fnmatch,
|
||||||
|
url.toString(QUrl.FullyEncoded))
|
||||||
|
tester = (lambda script:
|
||||||
|
any(match(pat) for pat in script.includes) and
|
||||||
|
not any(match(pat) for pat in script.excludes))
|
||||||
|
return MatchingScripts(
|
||||||
|
url,
|
||||||
|
[script for script in self._run_start if tester(script)],
|
||||||
|
[script for script in self._run_end if tester(script)],
|
||||||
|
[script for script in self._run_idle if tester(script)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def all_scripts(self):
|
||||||
|
"""Return all scripts found in the configured script directory."""
|
||||||
|
return self._run_start + self._run_end + self._run_idle
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
"""Initialize Greasemonkey support."""
|
||||||
|
gm_manager = GreasemonkeyManager()
|
||||||
|
objreg.register('greasemonkey', gm_manager)
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.mkdir(_scripts_dir())
|
||||||
|
except FileExistsError:
|
||||||
|
pass
|
@ -390,10 +390,8 @@ class HintManager(QObject):
|
|||||||
|
|
||||||
def _cleanup(self):
|
def _cleanup(self):
|
||||||
"""Clean up after hinting."""
|
"""Clean up after hinting."""
|
||||||
# pylint: disable=not-an-iterable
|
|
||||||
for label in self._context.all_labels:
|
for label in self._context.all_labels:
|
||||||
label.cleanup()
|
label.cleanup()
|
||||||
# pylint: enable=not-an-iterable
|
|
||||||
|
|
||||||
text = self._get_text()
|
text = self._get_text()
|
||||||
message_bridge = objreg.get('message-bridge', scope='window',
|
message_bridge = objreg.get('message-bridge', scope='window',
|
||||||
@ -621,8 +619,9 @@ class HintManager(QObject):
|
|||||||
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
|
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
|
||||||
star_args_optional=True, maxsplit=2)
|
star_args_optional=True, maxsplit=2)
|
||||||
@cmdutils.argument('win_id', win_id=True)
|
@cmdutils.argument('win_id', win_id=True)
|
||||||
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
|
def start(self, # pylint: disable=keyword-arg-before-vararg
|
||||||
*args, win_id, mode=None, add_history=False):
|
group=webelem.Group.all, target=Target.normal,
|
||||||
|
*args, win_id, mode=None, add_history=False, rapid=False):
|
||||||
"""Start hinting.
|
"""Start hinting.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -809,7 +808,6 @@ class HintManager(QObject):
|
|||||||
log.hints.debug("Filtering hints on {!r}".format(filterstr))
|
log.hints.debug("Filtering hints on {!r}".format(filterstr))
|
||||||
|
|
||||||
visible = []
|
visible = []
|
||||||
# pylint: disable=not-an-iterable
|
|
||||||
for label in self._context.all_labels:
|
for label in self._context.all_labels:
|
||||||
try:
|
try:
|
||||||
if self._filter_matches(filterstr, str(label.elem)):
|
if self._filter_matches(filterstr, str(label.elem)):
|
||||||
@ -821,7 +819,6 @@ class HintManager(QObject):
|
|||||||
label.hide()
|
label.hide()
|
||||||
except webelem.Error:
|
except webelem.Error:
|
||||||
pass
|
pass
|
||||||
# pylint: enable=not-an-iterable
|
|
||||||
|
|
||||||
if not visible:
|
if not visible:
|
||||||
# Whoops, filtered all hints
|
# Whoops, filtered all hints
|
||||||
|
@ -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)
|
||||||
@ -149,8 +150,8 @@ class WebHistory(sql.SqlTable):
|
|||||||
if force:
|
if force:
|
||||||
self._do_clear()
|
self._do_clear()
|
||||||
else:
|
else:
|
||||||
message.confirm_async(self._do_clear, title="Clear all browsing "
|
message.confirm_async(yes_action=self._do_clear,
|
||||||
"history?")
|
title="Clear all browsing history?")
|
||||||
|
|
||||||
def _do_clear(self):
|
def _do_clear(self):
|
||||||
with self._handle_sql_errors():
|
with self._handle_sql_errors():
|
||||||
@ -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() == 'data' 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
|
||||||
@ -268,6 +271,7 @@ class WebHistory(sql.SqlTable):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def action():
|
def action():
|
||||||
|
"""Actually run the import."""
|
||||||
with debug.log_time(log.init, 'Import old history file to sqlite'):
|
with debug.log_time(log.init, 'Import old history file to sqlite'):
|
||||||
try:
|
try:
|
||||||
self._read(path)
|
self._read(path)
|
||||||
@ -340,7 +344,7 @@ class WebHistory(sql.SqlTable):
|
|||||||
f.write('\n'.join(lines))
|
f.write('\n'.join(lines))
|
||||||
message.info("Dumped history to {}".format(dest))
|
message.info("Dumped history to {}".format(dest))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise cmdexc.CommandError('Could not write history: {}', e)
|
raise cmdexc.CommandError('Could not write history: {}'.format(e))
|
||||||
|
|
||||||
|
|
||||||
def init(parent=None):
|
def init(parent=None):
|
||||||
|
@ -94,6 +94,7 @@ class AbstractWebInspector(QWidget):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def toggle(self, page):
|
def toggle(self, page):
|
||||||
|
"""Show/hide the inspector."""
|
||||||
if self._widget.isVisible():
|
if self._widget.isVisible():
|
||||||
self.hide()
|
self.hide()
|
||||||
else:
|
else:
|
||||||
|
@ -19,15 +19,13 @@
|
|||||||
|
|
||||||
"""Mouse handling for a browser tab."""
|
"""Mouse handling for a browser tab."""
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import message, log, usertypes
|
from qutebrowser.utils import message, log, usertypes
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
|
||||||
|
|
||||||
|
|
||||||
class ChildEventFilter(QObject):
|
class ChildEventFilter(QObject):
|
||||||
|
|
||||||
"""An event filter re-adding MouseEventFilter on ChildEvent.
|
"""An event filter re-adding MouseEventFilter on ChildEvent.
|
||||||
|
@ -59,6 +59,7 @@ def _js_slot(*args):
|
|||||||
def _decorator(method):
|
def _decorator(method):
|
||||||
@functools.wraps(method)
|
@functools.wraps(method)
|
||||||
def new_method(self, *args, **kwargs):
|
def new_method(self, *args, **kwargs):
|
||||||
|
"""Call the underlying function."""
|
||||||
try:
|
try:
|
||||||
return method(self, *args, **kwargs)
|
return method(self, *args, **kwargs)
|
||||||
except:
|
except:
|
||||||
|
@ -82,7 +82,7 @@ def fix_urls(asset):
|
|||||||
('viewer.css', 'qute://pdfjs/web/viewer.css'),
|
('viewer.css', 'qute://pdfjs/web/viewer.css'),
|
||||||
('compatibility.js', 'qute://pdfjs/web/compatibility.js'),
|
('compatibility.js', 'qute://pdfjs/web/compatibility.js'),
|
||||||
('locale/locale.properties',
|
('locale/locale.properties',
|
||||||
'qute://pdfjs/web/locale/locale.properties'),
|
'qute://pdfjs/web/locale/locale.properties'),
|
||||||
('l10n.js', 'qute://pdfjs/web/l10n.js'),
|
('l10n.js', 'qute://pdfjs/web/l10n.js'),
|
||||||
('../build/pdf.js', 'qute://pdfjs/build/pdf.js'),
|
('../build/pdf.js', 'qute://pdfjs/build/pdf.js'),
|
||||||
('debugger.js', 'qute://pdfjs/web/debugger.js'),
|
('debugger.js', 'qute://pdfjs/web/debugger.js'),
|
||||||
|
@ -303,8 +303,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
|||||||
"""Handle QNetworkReply errors."""
|
"""Handle QNetworkReply errors."""
|
||||||
if code == QNetworkReply.OperationCanceledError:
|
if code == QNetworkReply.OperationCanceledError:
|
||||||
return
|
return
|
||||||
else:
|
self._die(self._reply.errorString())
|
||||||
self._die(self._reply.errorString())
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _on_read_timer_timeout(self):
|
def _on_read_timer_timeout(self):
|
||||||
@ -399,7 +398,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
|||||||
"""
|
"""
|
||||||
if not url.isValid():
|
if not url.isValid():
|
||||||
urlutils.invalid_url_error(url, "start download")
|
urlutils.invalid_url_error(url, "start download")
|
||||||
return
|
return None
|
||||||
req = QNetworkRequest(url)
|
req = QNetworkRequest(url)
|
||||||
if user_agent is not None:
|
if user_agent is not None:
|
||||||
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
||||||
|
@ -29,6 +29,7 @@ import os
|
|||||||
import time
|
import time
|
||||||
import textwrap
|
import textwrap
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
import urllib
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||||
@ -41,6 +42,7 @@ from qutebrowser.misc import objects
|
|||||||
|
|
||||||
|
|
||||||
pyeval_output = ":pyeval was never called"
|
pyeval_output = ":pyeval was never called"
|
||||||
|
spawn_output = ":spawn was never called"
|
||||||
|
|
||||||
|
|
||||||
_HANDLERS = {}
|
_HANDLERS = {}
|
||||||
@ -111,6 +113,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
|||||||
return function
|
return function
|
||||||
|
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
|
"""Call the underlying function."""
|
||||||
if self._backend is not None and objects.backend != self._backend:
|
if self._backend is not None and objects.backend != self._backend:
|
||||||
return self.wrong_backend_handler(*args, **kwargs)
|
return self.wrong_backend_handler(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
@ -136,7 +139,7 @@ def data_for_url(url):
|
|||||||
A (mimetype, data) tuple.
|
A (mimetype, data) tuple.
|
||||||
"""
|
"""
|
||||||
norm_url = url.adjusted(QUrl.NormalizePathSegments |
|
norm_url = url.adjusted(QUrl.NormalizePathSegments |
|
||||||
QUrl.StripTrailingSlash)
|
QUrl.StripTrailingSlash)
|
||||||
if norm_url != url:
|
if norm_url != url:
|
||||||
raise Redirect(norm_url)
|
raise Redirect(norm_url)
|
||||||
|
|
||||||
@ -267,6 +270,13 @@ def qute_pyeval(_url):
|
|||||||
return 'text/html', html
|
return 'text/html', html
|
||||||
|
|
||||||
|
|
||||||
|
@add_handler('spawn-output')
|
||||||
|
def qute_spawn_output(_url):
|
||||||
|
"""Handler for qute://spawn-output."""
|
||||||
|
html = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
||||||
|
return 'text/html', html
|
||||||
|
|
||||||
|
|
||||||
@add_handler('version')
|
@add_handler('version')
|
||||||
@add_handler('verizon')
|
@add_handler('verizon')
|
||||||
def qute_version(_url):
|
def qute_version(_url):
|
||||||
@ -425,6 +435,18 @@ def qute_settings(url):
|
|||||||
return 'text/html', html
|
return 'text/html', html
|
||||||
|
|
||||||
|
|
||||||
|
@add_handler('back')
|
||||||
|
def qute_back(url):
|
||||||
|
"""Handler for qute://back.
|
||||||
|
|
||||||
|
Simple page to free ram / lazy load a site, goes back on focusing the tab.
|
||||||
|
"""
|
||||||
|
html = jinja.render(
|
||||||
|
'back.html',
|
||||||
|
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
|
||||||
|
return 'text/html', html
|
||||||
|
|
||||||
|
|
||||||
@add_handler('configdiff')
|
@add_handler('configdiff')
|
||||||
def qute_configdiff(url):
|
def qute_configdiff(url):
|
||||||
"""Handler for qute://configdiff."""
|
"""Handler for qute://configdiff."""
|
||||||
@ -433,7 +455,7 @@ def qute_configdiff(url):
|
|||||||
return 'text/html', configdiff.get_diff()
|
return 'text/html', configdiff.get_diff()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
error = (b'Failed to read old config: ' +
|
error = (b'Failed to read old config: ' +
|
||||||
str(e.strerror).encode('utf-8'))
|
str(e.strerror).encode('utf-8'))
|
||||||
return 'text/plain', error
|
return 'text/plain', error
|
||||||
else:
|
else:
|
||||||
data = config.instance.dump_userconfig().encode('utf-8')
|
data = config.instance.dump_userconfig().encode('utf-8')
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import html
|
import html
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import usertypes, message, log, objreg, jinja
|
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
|
||||||
from qutebrowser.mainwindow import mainwindow
|
from qutebrowser.mainwindow import mainwindow
|
||||||
|
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
|
raise ValueError("Invalid ssl_strict value {!r}".format(ssl_strict))
|
||||||
raise AssertionError("Not reached")
|
raise utils.Unreachable
|
||||||
|
|
||||||
|
|
||||||
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
|
||||||
|
@ -26,8 +26,8 @@ to a file on shutdown, so it makes sense to keep them as strings here.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import html
|
|
||||||
import os.path
|
import os.path
|
||||||
|
import html
|
||||||
import functools
|
import functools
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
@ -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]',
|
||||||
@ -60,7 +62,7 @@ class Error(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class OrphanedError(Exception):
|
class OrphanedError(Error):
|
||||||
|
|
||||||
"""Raised when a webelement's parent has vanished."""
|
"""Raised when a webelement's parent has vanished."""
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ from qutebrowser.utils import log
|
|||||||
def version(filename):
|
def version(filename):
|
||||||
"""Extract the version number from the dictionary file name."""
|
"""Extract the version number from the dictionary file name."""
|
||||||
version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
||||||
match = version_re.match(filename)
|
match = version_re.fullmatch(filename)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise ValueError('the given dictionary file name is malformed: {}'
|
raise ValueError('the given dictionary file name is malformed: {}'
|
||||||
.format(filename))
|
.format(filename))
|
||||||
|
@ -211,11 +211,11 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
def _click_js(self, _click_target):
|
def _click_js(self, _click_target):
|
||||||
# FIXME:qtwebengine Have a proper API for this
|
# FIXME:qtwebengine Have a proper API for this
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
settings = self._tab._widget.settings()
|
view = self._tab._widget
|
||||||
# pylint: enable=protected-access
|
# pylint: enable=protected-access
|
||||||
attribute = QWebEngineSettings.JavascriptCanOpenWindows
|
attribute = QWebEngineSettings.JavascriptCanOpenWindows
|
||||||
could_open_windows = settings.testAttribute(attribute)
|
could_open_windows = view.settings().testAttribute(attribute)
|
||||||
settings.setAttribute(attribute, True)
|
view.settings().setAttribute(attribute, True)
|
||||||
|
|
||||||
# Get QtWebEngine do apply the settings
|
# Get QtWebEngine do apply the settings
|
||||||
# (it does so with a 0ms QTimer...)
|
# (it does so with a 0ms QTimer...)
|
||||||
@ -226,6 +226,12 @@ class WebEngineElement(webelem.AbstractWebElement):
|
|||||||
QEventLoop.ExcludeUserInputEvents)
|
QEventLoop.ExcludeUserInputEvents)
|
||||||
|
|
||||||
def reset_setting(_arg):
|
def reset_setting(_arg):
|
||||||
settings.setAttribute(attribute, could_open_windows)
|
"""Set the JavascriptCanOpenWindows setting to its old value."""
|
||||||
|
try:
|
||||||
|
view.settings().setAttribute(attribute, could_open_windows)
|
||||||
|
except RuntimeError:
|
||||||
|
# Happens if this callback gets called during QWebEnginePage
|
||||||
|
# destruction, i.e. if the tab was closed in the meantime.
|
||||||
|
pass
|
||||||
|
|
||||||
self._js_call('click', callback=reset_setting)
|
self._js_call('click', callback=reset_setting)
|
||||||
|
@ -244,6 +244,43 @@ def _init_profiles():
|
|||||||
private_profile.setSpellCheckEnabled(True)
|
private_profile.setSpellCheckEnabled(True)
|
||||||
|
|
||||||
|
|
||||||
|
def inject_userscripts():
|
||||||
|
"""Register user JavaScript files with the global profiles."""
|
||||||
|
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
||||||
|
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in response
|
||||||
|
# to urlChanged.
|
||||||
|
if not qtutils.version_check('5.8'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Since we are inserting scripts into profile.scripts they won't
|
||||||
|
# just get replaced by new gm scripts like if we were injecting them
|
||||||
|
# ourselves so we need to remove all gm scripts, while not removing
|
||||||
|
# any other stuff that might have been added. Like the one for
|
||||||
|
# stylesheets.
|
||||||
|
greasemonkey = objreg.get('greasemonkey')
|
||||||
|
for profile in [default_profile, private_profile]:
|
||||||
|
scripts = profile.scripts()
|
||||||
|
for script in scripts.toList():
|
||||||
|
if script.name().startswith("GM-"):
|
||||||
|
log.greasemonkey.debug('Removing script: {}'
|
||||||
|
.format(script.name()))
|
||||||
|
removed = scripts.remove(script)
|
||||||
|
assert removed, script.name()
|
||||||
|
|
||||||
|
# Then add the new scripts.
|
||||||
|
for script in greasemonkey.all_scripts():
|
||||||
|
# @run-at (and @include/@exclude/@match) is parsed by
|
||||||
|
# QWebEngineScript.
|
||||||
|
new_script = QWebEngineScript()
|
||||||
|
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||||
|
new_script.setSourceCode(script.code())
|
||||||
|
new_script.setName("GM-{}".format(script.name))
|
||||||
|
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||||
|
log.greasemonkey.debug('adding script: {}'
|
||||||
|
.format(new_script.name()))
|
||||||
|
scripts.insert(new_script)
|
||||||
|
|
||||||
|
|
||||||
def init(args):
|
def init(args):
|
||||||
"""Initialize the global QWebSettings."""
|
"""Initialize the global QWebSettings."""
|
||||||
if args.enable_webengine_inspector:
|
if args.enable_webengine_inspector:
|
||||||
|
@ -24,7 +24,8 @@ import functools
|
|||||||
import html as html_utils
|
import html as html_utils
|
||||||
|
|
||||||
import sip
|
import sip
|
||||||
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QPointF, QUrl, QTimer
|
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
|
||||||
|
QUrl, QTimer)
|
||||||
from PyQt5.QtGui import QKeyEvent
|
from PyQt5.QtGui import QKeyEvent
|
||||||
from PyQt5.QtNetwork import QAuthenticator
|
from PyQt5.QtNetwork import QAuthenticator
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
@ -69,6 +70,10 @@ def init():
|
|||||||
download_manager.install(webenginesettings.private_profile)
|
download_manager.install(webenginesettings.private_profile)
|
||||||
objreg.register('webengine-download-manager', download_manager)
|
objreg.register('webengine-download-manager', download_manager)
|
||||||
|
|
||||||
|
greasemonkey = objreg.get('greasemonkey')
|
||||||
|
greasemonkey.scripts_reloaded.connect(webenginesettings.inject_userscripts)
|
||||||
|
webenginesettings.inject_userscripts()
|
||||||
|
|
||||||
|
|
||||||
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
|
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
|
||||||
_JS_WORLD_MAP = {
|
_JS_WORLD_MAP = {
|
||||||
@ -121,18 +126,35 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
|||||||
|
|
||||||
class WebEngineSearch(browsertab.AbstractSearch):
|
class WebEngineSearch(browsertab.AbstractSearch):
|
||||||
|
|
||||||
"""QtWebEngine implementations related to searching on the page."""
|
"""QtWebEngine implementations related to searching on the page.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
_flags: The QWebEnginePage.FindFlags of the last search.
|
||||||
|
_pending_searches: How many searches have been started but not called
|
||||||
|
back yet.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._flags = QWebEnginePage.FindFlags(0)
|
self._flags = QWebEnginePage.FindFlags(0)
|
||||||
|
self._pending_searches = 0
|
||||||
|
|
||||||
def _find(self, text, flags, callback, caller):
|
def _find(self, text, flags, callback, caller):
|
||||||
"""Call findText on the widget."""
|
"""Call findText on the widget."""
|
||||||
self.search_displayed = True
|
self.search_displayed = True
|
||||||
|
self._pending_searches += 1
|
||||||
|
|
||||||
def wrapped_callback(found):
|
def wrapped_callback(found):
|
||||||
"""Wrap the callback to do debug logging."""
|
"""Wrap the callback to do debug logging."""
|
||||||
|
self._pending_searches -= 1
|
||||||
|
if self._pending_searches > 0:
|
||||||
|
# See https://github.com/qutebrowser/qutebrowser/issues/2442
|
||||||
|
# and https://github.com/qt/qtwebengine/blob/5.10/src/core/web_contents_adapter.cpp#L924-L934
|
||||||
|
log.webview.debug("Ignoring cancelled search callback with "
|
||||||
|
"{} pending searches".format(
|
||||||
|
self._pending_searches))
|
||||||
|
return
|
||||||
|
|
||||||
found_text = 'found' if found else "didn't find"
|
found_text = 'found' if found else "didn't find"
|
||||||
if flags:
|
if flags:
|
||||||
flag_text = 'with flags {}'.format(debug.qflags_key(
|
flag_text = 'with flags {}'.format(debug.qflags_key(
|
||||||
@ -518,7 +540,15 @@ class WebEngineElements(browsertab.AbstractElements):
|
|||||||
|
|
||||||
class WebEngineTab(browsertab.AbstractTab):
|
class WebEngineTab(browsertab.AbstractTab):
|
||||||
|
|
||||||
"""A QtWebEngine tab in the browser."""
|
"""A QtWebEngine tab in the browser.
|
||||||
|
|
||||||
|
Signals:
|
||||||
|
_load_finished_fake:
|
||||||
|
Used in place of unreliable loadFinished
|
||||||
|
"""
|
||||||
|
|
||||||
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||||
|
_load_finished_fake = pyqtSignal(bool)
|
||||||
|
|
||||||
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
def __init__(self, *, win_id, mode_manager, private, parent=None):
|
||||||
super().__init__(win_id=win_id, mode_manager=mode_manager,
|
super().__init__(win_id=win_id, mode_manager=mode_manager,
|
||||||
@ -772,6 +802,24 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
}
|
}
|
||||||
self.renderer_process_terminated.emit(status_map[status], exitcode)
|
self.renderer_process_terminated.emit(status_map[status], exitcode)
|
||||||
|
|
||||||
|
@pyqtSlot(int)
|
||||||
|
def _on_load_progress_workaround(self, perc):
|
||||||
|
"""Use loadProgress(100) to emit loadFinished(True).
|
||||||
|
|
||||||
|
See https://bugreports.qt.io/browse/QTBUG-65223
|
||||||
|
"""
|
||||||
|
if perc == 100 and self.load_status() != usertypes.LoadStatus.error:
|
||||||
|
self._load_finished_fake.emit(True)
|
||||||
|
|
||||||
|
@pyqtSlot(bool)
|
||||||
|
def _on_load_finished_workaround(self, ok):
|
||||||
|
"""Use only loadFinished(False).
|
||||||
|
|
||||||
|
See https://bugreports.qt.io/browse/QTBUG-65223
|
||||||
|
"""
|
||||||
|
if not ok:
|
||||||
|
self._load_finished_fake.emit(False)
|
||||||
|
|
||||||
def _connect_signals(self):
|
def _connect_signals(self):
|
||||||
view = self._widget
|
view = self._widget
|
||||||
page = view.page()
|
page = view.page()
|
||||||
@ -780,9 +828,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
page.linkHovered.connect(self.link_hovered)
|
page.linkHovered.connect(self.link_hovered)
|
||||||
page.loadProgress.connect(self._on_load_progress)
|
page.loadProgress.connect(self._on_load_progress)
|
||||||
page.loadStarted.connect(self._on_load_started)
|
page.loadStarted.connect(self._on_load_started)
|
||||||
page.loadFinished.connect(self._on_history_trigger)
|
|
||||||
page.loadFinished.connect(self._restore_zoom)
|
|
||||||
page.loadFinished.connect(self._on_load_finished)
|
|
||||||
page.certificate_error.connect(self._on_ssl_errors)
|
page.certificate_error.connect(self._on_ssl_errors)
|
||||||
page.authenticationRequired.connect(self._on_authentication_required)
|
page.authenticationRequired.connect(self._on_authentication_required)
|
||||||
page.proxyAuthenticationRequired.connect(
|
page.proxyAuthenticationRequired.connect(
|
||||||
@ -795,6 +840,19 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||||||
view.renderProcessTerminated.connect(
|
view.renderProcessTerminated.connect(
|
||||||
self._on_render_process_terminated)
|
self._on_render_process_terminated)
|
||||||
view.iconChanged.connect(self.icon_changed)
|
view.iconChanged.connect(self.icon_changed)
|
||||||
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
|
||||||
|
if qtutils.version_check('5.10', compiled=False):
|
||||||
|
page.loadProgress.connect(self._on_load_progress_workaround)
|
||||||
|
self._load_finished_fake.connect(self._on_history_trigger)
|
||||||
|
self._load_finished_fake.connect(self._restore_zoom)
|
||||||
|
self._load_finished_fake.connect(self._on_load_finished)
|
||||||
|
page.loadFinished.connect(self._on_load_finished_workaround)
|
||||||
|
else:
|
||||||
|
# for older Qt versions which break with the above
|
||||||
|
page.loadProgress.connect(self._on_load_progress)
|
||||||
|
page.loadFinished.connect(self._on_history_trigger)
|
||||||
|
page.loadFinished.connect(self._restore_zoom)
|
||||||
|
page.loadFinished.connect(self._on_load_finished)
|
||||||
|
|
||||||
def event_target(self):
|
def event_target(self):
|
||||||
return self._widget.focusProxy()
|
return self._widget.focusProxy()
|
||||||
|
@ -23,12 +23,14 @@ import functools
|
|||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QPalette
|
||||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
|
||||||
|
QWebEngineScript)
|
||||||
|
|
||||||
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, urlutils, message,
|
||||||
|
objreg, qtutils)
|
||||||
|
|
||||||
|
|
||||||
class WebEngineView(QWebEngineView):
|
class WebEngineView(QWebEngineView):
|
||||||
@ -135,6 +137,7 @@ class WebEnginePage(QWebEnginePage):
|
|||||||
self._theme_color = theme_color
|
self._theme_color = theme_color
|
||||||
self._set_bg_color()
|
self._set_bg_color()
|
||||||
config.instance.changed.connect(self._set_bg_color)
|
config.instance.changed.connect(self._set_bg_color)
|
||||||
|
self.urlChanged.connect(self._inject_userjs)
|
||||||
|
|
||||||
@config.change_filter('colors.webpage.bg')
|
@config.change_filter('colors.webpage.bg')
|
||||||
def _set_bg_color(self):
|
def _set_bg_color(self):
|
||||||
@ -300,3 +303,43 @@ class WebEnginePage(QWebEnginePage):
|
|||||||
message.error(msg)
|
message.error(msg)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@pyqtSlot('QUrl')
|
||||||
|
def _inject_userjs(self, url):
|
||||||
|
"""Inject userscripts registered for `url` into the current page."""
|
||||||
|
if qtutils.version_check('5.8'):
|
||||||
|
# Handled in webenginetab with the builtin Greasemonkey
|
||||||
|
# support.
|
||||||
|
return
|
||||||
|
|
||||||
|
# Using QWebEnginePage.scripts() to hold the user scripts means
|
||||||
|
# we don't have to worry ourselves about where to inject the
|
||||||
|
# page but also means scripts hang around for the tab lifecycle.
|
||||||
|
# So clear them here.
|
||||||
|
scripts = self.scripts()
|
||||||
|
for script in scripts.toList():
|
||||||
|
if script.name().startswith("GM-"):
|
||||||
|
log.greasemonkey.debug("Removing script: {}"
|
||||||
|
.format(script.name()))
|
||||||
|
removed = scripts.remove(script)
|
||||||
|
assert removed, script.name()
|
||||||
|
|
||||||
|
def _add_script(script, injection_point):
|
||||||
|
new_script = QWebEngineScript()
|
||||||
|
new_script.setInjectionPoint(injection_point)
|
||||||
|
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||||
|
new_script.setSourceCode(script.code())
|
||||||
|
new_script.setName("GM-{}".format(script.name))
|
||||||
|
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||||
|
log.greasemonkey.debug("Adding script: {}"
|
||||||
|
.format(new_script.name()))
|
||||||
|
scripts.insert(new_script)
|
||||||
|
|
||||||
|
greasemonkey = objreg.get('greasemonkey')
|
||||||
|
matching_scripts = greasemonkey.scripts_for(url)
|
||||||
|
for script in matching_scripts.start:
|
||||||
|
_add_script(script, QWebEngineScript.DocumentCreation)
|
||||||
|
for script in matching_scripts.end:
|
||||||
|
_add_script(script, QWebEngineScript.DocumentReady)
|
||||||
|
for script in matching_scripts.idle:
|
||||||
|
_add_script(script, QWebEngineScript.Deferred)
|
||||||
|
@ -22,11 +22,11 @@
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
|
from PyQt5.QtNetwork import QNetworkRequest
|
||||||
|
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log
|
||||||
from qutebrowser.browser.webkit import rfc6266
|
from qutebrowser.browser.webkit import rfc6266
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QNetworkRequest
|
|
||||||
|
|
||||||
|
|
||||||
def parse_content_disposition(reply):
|
def parse_content_disposition(reply):
|
||||||
"""Parse a content_disposition header.
|
"""Parse a content_disposition header.
|
||||||
@ -57,9 +57,7 @@ def parse_content_disposition(reply):
|
|||||||
is_inline = content_disposition.is_inline()
|
is_inline = content_disposition.is_inline()
|
||||||
# Then try to get filename from url
|
# Then try to get filename from url
|
||||||
if not filename:
|
if not filename:
|
||||||
path = reply.url().path()
|
filename = reply.url().path().rstrip('/')
|
||||||
if path is not None:
|
|
||||||
filename = path.rstrip('/')
|
|
||||||
# If that fails as well, use a fallback
|
# If that fails as well, use a fallback
|
||||||
if not filename:
|
if not filename:
|
||||||
filename = 'qutebrowser-download'
|
filename = 'qutebrowser-download'
|
||||||
|
@ -132,5 +132,6 @@ class FileSchemeHandler(schemehandler.SchemeHandler):
|
|||||||
data = dirbrowser_html(path)
|
data = dirbrowser_html(path)
|
||||||
return networkreply.FixedDataNetworkReply(
|
return networkreply.FixedDataNetworkReply(
|
||||||
request, data, 'text/html', self.parent())
|
request, data, 'text/html', self.parent())
|
||||||
|
return None
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
return None
|
return None
|
||||||
|
@ -270,6 +270,7 @@ class _ContentDisposition:
|
|||||||
elif 'filename' in self.assocs:
|
elif 'filename' in self.assocs:
|
||||||
# XXX Reject non-ascii (parsed via qdtext) here?
|
# XXX Reject non-ascii (parsed via qdtext) here?
|
||||||
return self.assocs['filename']
|
return self.assocs['filename']
|
||||||
|
return None
|
||||||
|
|
||||||
def is_inline(self):
|
def is_inline(self):
|
||||||
"""Return if the file should be handled inline.
|
"""Return if the file should be handled inline.
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
"""Wrapper over our (QtWebKit) WebView."""
|
"""Wrapper over our (QtWebKit) WebView."""
|
||||||
|
|
||||||
|
import re
|
||||||
import functools
|
import functools
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
@ -541,10 +542,15 @@ class WebKitElements(browsertab.AbstractElements):
|
|||||||
|
|
||||||
def find_id(self, elem_id, callback):
|
def find_id(self, elem_id, callback):
|
||||||
def find_id_cb(elems):
|
def find_id_cb(elems):
|
||||||
|
"""Call the real callback with the found elements."""
|
||||||
if not elems:
|
if not elems:
|
||||||
callback(None)
|
callback(None)
|
||||||
else:
|
else:
|
||||||
callback(elems[0])
|
callback(elems[0])
|
||||||
|
|
||||||
|
# Escape non-alphanumeric characters in the selector
|
||||||
|
# https://www.w3.org/TR/CSS2/syndata.html#value-def-identifier
|
||||||
|
elem_id = re.sub(r'[^a-zA-Z0-9_-]', r'\\\g<0>', elem_id)
|
||||||
self.find_css('#' + elem_id, find_id_cb)
|
self.find_css('#' + elem_id, find_id_cb)
|
||||||
|
|
||||||
def find_focused(self, callback):
|
def find_focused(self, callback):
|
||||||
|
@ -86,6 +86,21 @@ class BrowserPage(QWebPage):
|
|||||||
self.on_save_frame_state_requested)
|
self.on_save_frame_state_requested)
|
||||||
self.restoreFrameStateRequested.connect(
|
self.restoreFrameStateRequested.connect(
|
||||||
self.on_restore_frame_state_requested)
|
self.on_restore_frame_state_requested)
|
||||||
|
self.loadFinished.connect(
|
||||||
|
functools.partial(self._inject_userjs, self.mainFrame()))
|
||||||
|
self.frameCreated.connect(self._connect_userjs_signals)
|
||||||
|
|
||||||
|
@pyqtSlot('QWebFrame*')
|
||||||
|
def _connect_userjs_signals(self, frame):
|
||||||
|
"""Connect userjs related signals to `frame`.
|
||||||
|
|
||||||
|
Connect the signals used as triggers for injecting user
|
||||||
|
JavaScripts into the passed QWebFrame.
|
||||||
|
"""
|
||||||
|
log.greasemonkey.debug("Connecting to frame {} ({})"
|
||||||
|
.format(frame, frame.url().toDisplayString()))
|
||||||
|
frame.loadFinished.connect(
|
||||||
|
functools.partial(self._inject_userjs, frame))
|
||||||
|
|
||||||
def javaScriptPrompt(self, frame, js_msg, default):
|
def javaScriptPrompt(self, frame, js_msg, default):
|
||||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||||
@ -283,6 +298,38 @@ class BrowserPage(QWebPage):
|
|||||||
else:
|
else:
|
||||||
self.error_occurred = False
|
self.error_occurred = False
|
||||||
|
|
||||||
|
def _inject_userjs(self, frame):
|
||||||
|
"""Inject user JavaScripts into the page.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The QWebFrame to inject the user scripts into.
|
||||||
|
"""
|
||||||
|
url = frame.url()
|
||||||
|
if url.isEmpty():
|
||||||
|
url = frame.requestedUrl()
|
||||||
|
|
||||||
|
log.greasemonkey.debug("_inject_userjs called for {} ({})"
|
||||||
|
.format(frame, url.toDisplayString()))
|
||||||
|
|
||||||
|
greasemonkey = objreg.get('greasemonkey')
|
||||||
|
scripts = greasemonkey.scripts_for(url)
|
||||||
|
# QtWebKit has trouble providing us with signals representing
|
||||||
|
# page load progress at reasonable times, so we just load all
|
||||||
|
# scripts on the same event.
|
||||||
|
toload = scripts.start + scripts.end + scripts.idle
|
||||||
|
|
||||||
|
if url.isEmpty():
|
||||||
|
# This happens during normal usage like with view source but may
|
||||||
|
# also indicate a bug.
|
||||||
|
log.greasemonkey.debug("Not running scripts for frame with no "
|
||||||
|
"url: {}".format(frame))
|
||||||
|
assert not toload, toload
|
||||||
|
|
||||||
|
for script in toload:
|
||||||
|
if frame is self.mainFrame() or script.runs_on_sub_frames:
|
||||||
|
log.webview.debug('Running GM script: {}'.format(script.name))
|
||||||
|
frame.evaluateJavaScript(script.code())
|
||||||
|
|
||||||
@pyqtSlot('QWebFrame*', 'QWebPage::Feature')
|
@pyqtSlot('QWebFrame*', 'QWebPage::Feature')
|
||||||
def _on_feature_permission_requested(self, frame, feature):
|
def _on_feature_permission_requested(self, frame, feature):
|
||||||
"""Ask the user for approval for geolocation/notifications."""
|
"""Ask the user for approval for geolocation/notifications."""
|
||||||
|
@ -190,6 +190,7 @@ class Command:
|
|||||||
return True
|
return True
|
||||||
elif arg_info.win_id:
|
elif arg_info.win_id:
|
||||||
return True
|
return True
|
||||||
|
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 informations from it.
|
||||||
|
@ -126,7 +126,7 @@ class CommandParser:
|
|||||||
new_cmd += ' '
|
new_cmd += ' '
|
||||||
return new_cmd
|
return new_cmd
|
||||||
|
|
||||||
def _parse_all_gen(self, text, aliases=True, *args, **kwargs):
|
def _parse_all_gen(self, text, *args, aliases=True, **kwargs):
|
||||||
"""Split a command on ;; and parse all parts.
|
"""Split a command on ;; and parse all parts.
|
||||||
|
|
||||||
If the first command in the commandline is a non-split one, it only
|
If the first command in the commandline is a non-split one, it only
|
||||||
|
@ -35,6 +35,7 @@ class CompletionInfo:
|
|||||||
|
|
||||||
config = attr.ib()
|
config = attr.ib()
|
||||||
keyconf = attr.ib()
|
keyconf = attr.ib()
|
||||||
|
win_id = attr.ib()
|
||||||
|
|
||||||
|
|
||||||
class Completer(QObject):
|
class Completer(QObject):
|
||||||
@ -43,6 +44,7 @@ class Completer(QObject):
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
_cmd: The statusbar Command object this completer belongs to.
|
_cmd: The statusbar Command object this completer belongs to.
|
||||||
|
_win_id: The id of the window that owns this object.
|
||||||
_timer: The timer used to trigger the completion update.
|
_timer: The timer used to trigger the completion update.
|
||||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||||
updates.
|
updates.
|
||||||
@ -50,9 +52,10 @@ class Completer(QObject):
|
|||||||
_last_completion_func: The completion function used for the last text.
|
_last_completion_func: The completion function used for the last text.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cmd, parent=None):
|
def __init__(self, *, cmd, win_id, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._cmd = cmd
|
self._cmd = cmd
|
||||||
|
self._win_id = win_id
|
||||||
self._timer = QTimer()
|
self._timer = QTimer()
|
||||||
self._timer.setSingleShot(True)
|
self._timer.setSingleShot(True)
|
||||||
self._timer.setInterval(0)
|
self._timer.setInterval(0)
|
||||||
@ -84,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')
|
||||||
@ -96,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
|
||||||
@ -131,9 +135,7 @@ class Completer(QObject):
|
|||||||
return [], '', []
|
return [], '', []
|
||||||
parser = runners.CommandParser()
|
parser = runners.CommandParser()
|
||||||
result = parser.parse(text, fallback=True, keep=True)
|
result = parser.parse(text, fallback=True, keep=True)
|
||||||
# pylint: disable=not-an-iterable
|
|
||||||
parts = [x for x in result.cmdline if x]
|
parts = [x for x in result.cmdline if x]
|
||||||
# pylint: enable=not-an-iterable
|
|
||||||
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
|
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
|
||||||
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
|
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
|
||||||
log.completion.debug('partitioning {} around position {}'.format(parts,
|
log.completion.debug('partitioning {} around position {}'.format(parts,
|
||||||
@ -152,8 +154,7 @@ class Completer(QObject):
|
|||||||
"partitioned: {} '{}' {}".format(prefix, center, postfix))
|
"partitioned: {} '{}' {}".format(prefix, center, postfix))
|
||||||
return prefix, center, postfix
|
return prefix, center, postfix
|
||||||
|
|
||||||
# We should always return above
|
raise utils.Unreachable("Not all parts consumed: {}".format(parts))
|
||||||
assert False, parts
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def on_selection_changed(self, text):
|
def on_selection_changed(self, text):
|
||||||
@ -206,7 +207,7 @@ class Completer(QObject):
|
|||||||
log.completion.debug("Ignoring update because the length of "
|
log.completion.debug("Ignoring update because the length of "
|
||||||
"the text is less than completion.min_chars.")
|
"the text is less than completion.min_chars.")
|
||||||
elif (self._cmd.cursorPosition() == self._last_cursor_pos and
|
elif (self._cmd.cursorPosition() == self._last_cursor_pos and
|
||||||
self._cmd.text() == self._last_text):
|
self._cmd.text() == self._last_text):
|
||||||
log.completion.debug("Ignoring update because there were no "
|
log.completion.debug("Ignoring update because there were no "
|
||||||
"changes.")
|
"changes.")
|
||||||
else:
|
else:
|
||||||
@ -247,10 +248,11 @@ class Completer(QObject):
|
|||||||
if func != self._last_completion_func:
|
if func != self._last_completion_func:
|
||||||
self._last_completion_func = func
|
self._last_completion_func = func
|
||||||
args = (x for x in before_cursor[1:] if not x.startswith('-'))
|
args = (x for x in before_cursor[1:] if not x.startswith('-'))
|
||||||
with debug.log_time(log.completion,
|
with debug.log_time(log.completion, 'Starting {} completion'
|
||||||
'Starting {} completion'.format(func.__name__)):
|
.format(func.__name__)):
|
||||||
info = CompletionInfo(config=config.instance,
|
info = CompletionInfo(config=config.instance,
|
||||||
keyconf=config.key_instance)
|
keyconf=config.key_instance,
|
||||||
|
win_id=self._win_id)
|
||||||
model = func(*args, info=info)
|
model = func(*args, info=info)
|
||||||
with debug.log_time(log.completion, 'Set completion model'):
|
with debug.log_time(log.completion, 'Set completion model'):
|
||||||
completion.set_model(model)
|
completion.set_model(model)
|
||||||
|
@ -138,10 +138,10 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
|
|
||||||
self._painter.translate(text_rect.left(), text_rect.top())
|
self._painter.translate(text_rect.left(), text_rect.top())
|
||||||
self._get_textdoc(index)
|
self._get_textdoc(index)
|
||||||
self._draw_textdoc(text_rect)
|
self._draw_textdoc(text_rect, index.column())
|
||||||
self._painter.restore()
|
self._painter.restore()
|
||||||
|
|
||||||
def _draw_textdoc(self, rect):
|
def _draw_textdoc(self, rect, col):
|
||||||
"""Draw the QTextDocument of an item.
|
"""Draw the QTextDocument of an item.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -156,7 +156,9 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
|||||||
elif not self._opt.state & QStyle.State_Enabled:
|
elif not self._opt.state & QStyle.State_Enabled:
|
||||||
color = config.val.colors.completion.category.fg
|
color = config.val.colors.completion.category.fg
|
||||||
else:
|
else:
|
||||||
color = config.val.colors.completion.fg
|
colors = config.val.colors.completion.fg
|
||||||
|
# if multiple colors are set, use different colors per column
|
||||||
|
color = colors[col % len(colors)]
|
||||||
self._painter.setPen(color)
|
self._painter.setPen(color)
|
||||||
|
|
||||||
ctx = QAbstractTextDocumentLayout.PaintContext()
|
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||||
|
@ -23,7 +23,7 @@ Defines a CompletionView which uses CompletionFiterModel and CompletionModel
|
|||||||
subclasses to provide completions.
|
subclasses to provide completions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QStyle, QTreeView, QSizePolicy, QStyleFactory
|
from PyQt5.QtWidgets import QTreeView, QSizePolicy, QStyleFactory
|
||||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel, QSize
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
@ -152,12 +152,12 @@ class CompletionView(QTreeView):
|
|||||||
column_widths = self.model().column_widths
|
column_widths = self.model().column_widths
|
||||||
pixel_widths = [(width * perc // 100) for perc in column_widths]
|
pixel_widths = [(width * perc // 100) for perc in column_widths]
|
||||||
|
|
||||||
if self.verticalScrollBar().isVisible():
|
delta = self.verticalScrollBar().sizeHint().width()
|
||||||
delta = self.style().pixelMetric(QStyle.PM_ScrollBarExtent) + 5
|
if pixel_widths[-1] > delta:
|
||||||
if pixel_widths[-1] > delta:
|
pixel_widths[-1] -= delta
|
||||||
pixel_widths[-1] -= delta
|
else:
|
||||||
else:
|
pixel_widths[-2] -= delta
|
||||||
pixel_widths[-2] -= delta
|
|
||||||
for i, w in enumerate(pixel_widths):
|
for i, w in enumerate(pixel_widths):
|
||||||
assert w >= 0, i
|
assert w >= 0, i
|
||||||
self.setColumnWidth(i, w)
|
self.setColumnWidth(i, w)
|
||||||
@ -180,6 +180,7 @@ class CompletionView(QTreeView):
|
|||||||
return self.model().last_item()
|
return self.model().last_item()
|
||||||
else:
|
else:
|
||||||
return self.model().first_item()
|
return self.model().first_item()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
idx = self.indexAbove(idx) if upwards else self.indexBelow(idx)
|
idx = self.indexAbove(idx) if upwards else self.indexBelow(idx)
|
||||||
# wrap around if we arrived at beginning/end
|
# wrap around if we arrived at beginning/end
|
||||||
@ -193,6 +194,8 @@ class CompletionView(QTreeView):
|
|||||||
# Item is a real item, not a category header -> success
|
# Item is a real item, not a category header -> success
|
||||||
return idx
|
return idx
|
||||||
|
|
||||||
|
raise utils.Unreachable
|
||||||
|
|
||||||
def _next_category_idx(self, upwards):
|
def _next_category_idx(self, upwards):
|
||||||
"""Get the index of the previous/next category.
|
"""Get the index of the previous/next category.
|
||||||
|
|
||||||
@ -222,6 +225,8 @@ class CompletionView(QTreeView):
|
|||||||
self.scrollTo(idx)
|
self.scrollTo(idx)
|
||||||
return idx.child(0, 0)
|
return idx.child(0, 0)
|
||||||
|
|
||||||
|
raise utils.Unreachable
|
||||||
|
|
||||||
@cmdutils.register(instance='completion',
|
@cmdutils.register(instance='completion',
|
||||||
modes=[usertypes.KeyMode.command], scope='window')
|
modes=[usertypes.KeyMode.command], scope='window')
|
||||||
@cmdutils.argument('which', choices=['next', 'prev', 'next-category',
|
@cmdutils.argument('which', choices=['next', 'prev', 'next-category',
|
||||||
|
@ -60,7 +60,8 @@ def value(optname, *_values, info):
|
|||||||
|
|
||||||
opt = info.config.get_opt(optname)
|
opt = info.config.get_opt(optname)
|
||||||
default = opt.typ.to_str(opt.default)
|
default = opt.typ.to_str(opt.default)
|
||||||
cur_cat = listcategory.ListCategory("Current/Default",
|
cur_cat = listcategory.ListCategory(
|
||||||
|
"Current/Default",
|
||||||
[(current, "Current value"), (default, "Default value")])
|
[(current, "Current value"), (default, "Default value")])
|
||||||
model.add_category(cur_cat)
|
model.add_category(cur_cat)
|
||||||
|
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
"""A completion category that queries the SQL History store."""
|
"""A completion category that queries the SQL History store."""
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from PyQt5.QtSql import QSqlQueryModel
|
from PyQt5.QtSql import QSqlQueryModel
|
||||||
|
|
||||||
from qutebrowser.misc import sql
|
from qutebrowser.misc import sql
|
||||||
@ -36,21 +34,7 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
"""Create a new History completion category."""
|
"""Create a new History completion category."""
|
||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
self.name = "History"
|
self.name = "History"
|
||||||
|
self._query = None
|
||||||
# replace ' in timestamp-format to avoid breaking the query
|
|
||||||
timestamp_format = config.val.completion.timestamp_format
|
|
||||||
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
|
||||||
.format(timestamp_format.replace("'", "`")))
|
|
||||||
|
|
||||||
self._query = sql.Query(' '.join([
|
|
||||||
"SELECT url, title, {}".format(timefmt),
|
|
||||||
"FROM CompletionHistory",
|
|
||||||
# the incoming pattern will have literal % and _ escaped with '\'
|
|
||||||
# we need to tell sql to treat '\' as an escape character
|
|
||||||
"WHERE (url LIKE :pat escape '\\' or title LIKE :pat escape '\\')",
|
|
||||||
self._atime_expr(),
|
|
||||||
"ORDER BY last_atime DESC",
|
|
||||||
]), forward_only=False)
|
|
||||||
|
|
||||||
# advertise that this model filters by URL and title
|
# advertise that this model filters by URL and title
|
||||||
self.columns_to_filter = [0, 1]
|
self.columns_to_filter = [0, 1]
|
||||||
@ -86,11 +70,36 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
# escape to treat a user input % or _ as a literal, not a wildcard
|
# escape to treat a user input % or _ as a literal, not a wildcard
|
||||||
pattern = pattern.replace('%', '\\%')
|
pattern = pattern.replace('%', '\\%')
|
||||||
pattern = pattern.replace('_', '\\_')
|
pattern = pattern.replace('_', '\\_')
|
||||||
# treat spaces as wildcards to match any of the typed words
|
words = ['%{}%'.format(w) for w in pattern.split(' ')]
|
||||||
pattern = re.sub(r' +', '%', pattern)
|
|
||||||
pattern = '%{}%'.format(pattern)
|
# build a where clause to match all of the words in any order
|
||||||
|
# given the search term "a b", the WHERE clause would be:
|
||||||
|
# ((url || title) LIKE '%a%') AND ((url || title) LIKE '%b%')
|
||||||
|
where_clause = ' AND '.join(
|
||||||
|
"(url || title) LIKE :{} escape '\\'".format(i)
|
||||||
|
for i in range(len(words)))
|
||||||
|
|
||||||
|
# replace ' in timestamp-format to avoid breaking the query
|
||||||
|
timestamp_format = config.val.completion.timestamp_format
|
||||||
|
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
||||||
|
.format(timestamp_format.replace("'", "`")))
|
||||||
|
|
||||||
|
if not self._query or len(words) != len(self._query.boundValues()):
|
||||||
|
# if the number of words changed, we need to generate a new query
|
||||||
|
# otherwise, we can reuse the prepared query for performance
|
||||||
|
self._query = sql.Query(' '.join([
|
||||||
|
"SELECT url, title, {}".format(timefmt),
|
||||||
|
"FROM CompletionHistory",
|
||||||
|
# the incoming pattern will have literal % and _ escaped
|
||||||
|
# we need to tell sql to treat '\' as an escape character
|
||||||
|
'WHERE ({})'.format(where_clause),
|
||||||
|
self._atime_expr(),
|
||||||
|
"ORDER BY last_atime DESC",
|
||||||
|
]), forward_only=False)
|
||||||
|
|
||||||
with debug.log_time('sql', 'Running completion query'):
|
with debug.log_time('sql', 'Running completion query'):
|
||||||
self._query.run(pat=pattern)
|
self._query.run(**{
|
||||||
|
str(i): w for i, w in enumerate(words)})
|
||||||
self.setQuery(self._query)
|
self.setQuery(self._query)
|
||||||
|
|
||||||
def removeRows(self, row, _count, _parent=None):
|
def removeRows(self, row, _count, _parent=None):
|
||||||
|
@ -94,10 +94,11 @@ def session(*, info=None): # pylint: disable=unused-argument
|
|||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def buffer(*, info=None): # pylint: disable=unused-argument
|
def _buffer(skip_win_id=None):
|
||||||
"""A model to complete on open tabs across all windows.
|
"""Helper to get the completion model for buffer/other_buffer.
|
||||||
|
|
||||||
Used for switching the buffer command.
|
Args:
|
||||||
|
skip_win_id: The id of the window to skip, or None to include all.
|
||||||
"""
|
"""
|
||||||
def delete_buffer(data):
|
def delete_buffer(data):
|
||||||
"""Close the selected tab."""
|
"""Close the selected tab."""
|
||||||
@ -109,6 +110,8 @@ def buffer(*, info=None): # pylint: disable=unused-argument
|
|||||||
model = completionmodel.CompletionModel(column_widths=(6, 40, 54))
|
model = completionmodel.CompletionModel(column_widths=(6, 40, 54))
|
||||||
|
|
||||||
for win_id in objreg.window_registry:
|
for win_id in objreg.window_registry:
|
||||||
|
if skip_win_id and win_id == skip_win_id:
|
||||||
|
continue
|
||||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
window=win_id)
|
window=win_id)
|
||||||
if tabbed_browser.shutting_down:
|
if tabbed_browser.shutting_down:
|
||||||
@ -120,19 +123,37 @@ def buffer(*, info=None): # pylint: disable=unused-argument
|
|||||||
tab.url().toDisplayString(),
|
tab.url().toDisplayString(),
|
||||||
tabbed_browser.page_title(idx)))
|
tabbed_browser.page_title(idx)))
|
||||||
cat = listcategory.ListCategory("{}".format(win_id), tabs,
|
cat = listcategory.ListCategory("{}".format(win_id), tabs,
|
||||||
delete_func=delete_buffer)
|
delete_func=delete_buffer)
|
||||||
model.add_category(cat)
|
model.add_category(cat)
|
||||||
|
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def window(*, info=None): # pylint: disable=unused-argument
|
def buffer(*, info=None): # pylint: disable=unused-argument
|
||||||
|
"""A model to complete on open tabs across all windows.
|
||||||
|
|
||||||
|
Used for switching the buffer command.
|
||||||
|
"""
|
||||||
|
return _buffer()
|
||||||
|
|
||||||
|
|
||||||
|
def other_buffer(*, info):
|
||||||
|
"""A model to complete on open tabs across all windows except the current.
|
||||||
|
|
||||||
|
Used for the tab-take command.
|
||||||
|
"""
|
||||||
|
return _buffer(skip_win_id=info.win_id)
|
||||||
|
|
||||||
|
|
||||||
|
def window(*, info):
|
||||||
"""A model to complete on all open windows."""
|
"""A model to complete on all open windows."""
|
||||||
model = completionmodel.CompletionModel(column_widths=(6, 30, 64))
|
model = completionmodel.CompletionModel(column_widths=(6, 30, 64))
|
||||||
|
|
||||||
windows = []
|
windows = []
|
||||||
|
|
||||||
for win_id in objreg.window_registry:
|
for win_id in objreg.window_registry:
|
||||||
|
if win_id == info.win_id:
|
||||||
|
continue
|
||||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||||
window=win_id)
|
window=win_id)
|
||||||
tab_titles = (tab.title() for tab in tabbed_browser.widgets())
|
tab_titles = (tab.title() for tab in tabbed_browser.widgets())
|
||||||
|
@ -104,13 +104,17 @@ class change_filter: # noqa: N801,N806 pylint: disable=invalid-name
|
|||||||
if self._function:
|
if self._function:
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(option=None):
|
def wrapper(option=None):
|
||||||
|
"""Call the underlying function."""
|
||||||
if self._check_match(option):
|
if self._check_match(option):
|
||||||
return func()
|
return func()
|
||||||
|
return None
|
||||||
else:
|
else:
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(wrapper_self, option=None):
|
def wrapper(wrapper_self, option=None):
|
||||||
|
"""Call the underlying function."""
|
||||||
if self._check_match(option):
|
if self._check_match(option):
|
||||||
return func(wrapper_self)
|
return func(wrapper_self)
|
||||||
|
return None
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@ -461,7 +465,8 @@ class ConfigContainer:
|
|||||||
def __setattr__(self, attr, value):
|
def __setattr__(self, attr, value):
|
||||||
"""Set the given option in the config."""
|
"""Set the given option in the config."""
|
||||||
if attr.startswith('_'):
|
if attr.startswith('_'):
|
||||||
return super().__setattr__(attr, value)
|
super().__setattr__(attr, value)
|
||||||
|
return
|
||||||
|
|
||||||
name = self._join(attr)
|
name = self._join(attr)
|
||||||
with self._handle_error('setting', name):
|
with self._handle_error('setting', name):
|
||||||
|
@ -174,6 +174,7 @@ def _parse_yaml_backends(name, node):
|
|||||||
elif isinstance(node, dict):
|
elif isinstance(node, dict):
|
||||||
return _parse_yaml_backends_dict(name, node)
|
return _parse_yaml_backends_dict(name, node)
|
||||||
_raise_invalid_node(name, 'backends', node)
|
_raise_invalid_node(name, 'backends', node)
|
||||||
|
raise utils.Unreachable
|
||||||
|
|
||||||
|
|
||||||
def _read_yaml(yaml_data):
|
def _read_yaml(yaml_data):
|
||||||
|
@ -34,6 +34,9 @@ history_gap_interval:
|
|||||||
`:history`. Use -1 to disable separation.
|
`:history`. Use -1 to disable separation.
|
||||||
|
|
||||||
ignore_case:
|
ignore_case:
|
||||||
|
renamed: search.ignore_case
|
||||||
|
|
||||||
|
search.ignore_case:
|
||||||
type:
|
type:
|
||||||
name: String
|
name: String
|
||||||
valid_values:
|
valid_values:
|
||||||
@ -43,6 +46,11 @@ ignore_case:
|
|||||||
default: smart
|
default: smart
|
||||||
desc: When to find text on a page case-insensitively.
|
desc: When to find text on a page case-insensitively.
|
||||||
|
|
||||||
|
search.incremental:
|
||||||
|
type: Bool
|
||||||
|
default: True
|
||||||
|
desc: Find text on a page incrementally, renewing the search for each typed character.
|
||||||
|
|
||||||
new_instance_open_target:
|
new_instance_open_target:
|
||||||
type:
|
type:
|
||||||
name: String
|
name: String
|
||||||
@ -79,6 +87,9 @@ new_instance_open_target_window:
|
|||||||
When `new_instance_open_target` is not set to `window`, this is ignored.
|
When `new_instance_open_target` is not set to `window`, this is ignored.
|
||||||
|
|
||||||
session_default_name:
|
session_default_name:
|
||||||
|
renamed: session.default_name
|
||||||
|
|
||||||
|
session.default_name:
|
||||||
type:
|
type:
|
||||||
name: SessionName
|
name: SessionName
|
||||||
none_ok: true
|
none_ok: true
|
||||||
@ -88,6 +99,11 @@ session_default_name:
|
|||||||
|
|
||||||
If this is set to null, the session which was last loaded is saved.
|
If this is set to null, the session which was last loaded is saved.
|
||||||
|
|
||||||
|
session.lazy_restore:
|
||||||
|
type: Bool
|
||||||
|
default: false
|
||||||
|
desc: Load a restored tab as soon as it takes focus.
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
type:
|
type:
|
||||||
name: String
|
name: String
|
||||||
@ -1292,6 +1308,7 @@ tabs.title.format:
|
|||||||
- host
|
- host
|
||||||
- private
|
- private
|
||||||
- current_url
|
- current_url
|
||||||
|
- protocol
|
||||||
none_ok: true
|
none_ok: true
|
||||||
desc: |
|
desc: |
|
||||||
Format to use for the tab title.
|
Format to use for the tab title.
|
||||||
@ -1306,8 +1323,9 @@ tabs.title.format:
|
|||||||
* `{scroll_pos}`: Page scroll position.
|
* `{scroll_pos}`: Page scroll position.
|
||||||
* `{host}`: Host of the current web page.
|
* `{host}`: Host of the current web page.
|
||||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||||
* `{private}` : Indicates when private mode is enabled.
|
* `{private}`: Indicates when private mode is enabled.
|
||||||
* `{current_url}` : URL of the current web page.
|
* `{current_url}`: URL of the current web page.
|
||||||
|
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||||
|
|
||||||
tabs.title.format_pinned:
|
tabs.title.format_pinned:
|
||||||
default: '{index}'
|
default: '{index}'
|
||||||
@ -1324,6 +1342,7 @@ tabs.title.format_pinned:
|
|||||||
- host
|
- host
|
||||||
- private
|
- private
|
||||||
- current_url
|
- current_url
|
||||||
|
- protocol
|
||||||
none_ok: true
|
none_ok: true
|
||||||
desc: Format to use for the tab title for pinned tabs. The same placeholders
|
desc: Format to use for the tab title for pinned tabs. The same placeholders
|
||||||
like for `tabs.title.format` are defined.
|
like for `tabs.title.format` are defined.
|
||||||
@ -1467,6 +1486,7 @@ window.title_format:
|
|||||||
- backend
|
- backend
|
||||||
- private
|
- private
|
||||||
- current_url
|
- current_url
|
||||||
|
- protocol
|
||||||
default: '{perc}{title}{title_sep}qutebrowser'
|
default: '{perc}{title}{title_sep}qutebrowser'
|
||||||
desc: |
|
desc: |
|
||||||
Format to use for the window title. The same placeholders like for
|
Format to use for the window title. The same placeholders like for
|
||||||
@ -1520,9 +1540,15 @@ zoom.text_only:
|
|||||||
## colors
|
## colors
|
||||||
|
|
||||||
colors.completion.fg:
|
colors.completion.fg:
|
||||||
default: white
|
default: ["white", "white", "white"]
|
||||||
type: QtColor
|
type:
|
||||||
desc: Text color of the completion widget.
|
name: ListOrValue
|
||||||
|
valtype: QtColor
|
||||||
|
desc: >-
|
||||||
|
Text color of the completion widget.
|
||||||
|
|
||||||
|
May be a single color to use for all columns or a list of three colors,
|
||||||
|
one for each column.
|
||||||
|
|
||||||
colors.completion.odd.bg:
|
colors.completion.odd.bg:
|
||||||
default: '#444444'
|
default: '#444444'
|
||||||
@ -2287,6 +2313,7 @@ bindings.default:
|
|||||||
<Ctrl-C>: completion-item-yank
|
<Ctrl-C>: completion-item-yank
|
||||||
<Ctrl-Shift-C>: completion-item-yank --sel
|
<Ctrl-Shift-C>: completion-item-yank --sel
|
||||||
<Return>: command-accept
|
<Return>: command-accept
|
||||||
|
<Ctrl-Return>: command-accept --rapid
|
||||||
<Ctrl-B>: rl-backward-char
|
<Ctrl-B>: rl-backward-char
|
||||||
<Ctrl-F>: rl-forward-char
|
<Ctrl-F>: rl-forward-char
|
||||||
<Alt-B>: rl-backward-word
|
<Alt-B>: rl-backward-word
|
||||||
|
@ -136,7 +136,7 @@ class YamlConfig(QObject):
|
|||||||
with open(self._filename, 'r', encoding='utf-8') as f:
|
with open(self._filename, 'r', encoding='utf-8') as f:
|
||||||
yaml_data = utils.yaml_load(f)
|
yaml_data = utils.yaml_load(f)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return {}
|
return
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
desc = configexc.ConfigErrorDesc("While reading", e)
|
desc = configexc.ConfigErrorDesc("While reading", e)
|
||||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||||
|
@ -499,7 +499,7 @@ class ListOrValue(BaseType):
|
|||||||
|
|
||||||
_show_valtype = True
|
_show_valtype = True
|
||||||
|
|
||||||
def __init__(self, valtype, none_ok=False, *args, **kwargs):
|
def __init__(self, valtype, *args, none_ok=False, **kwargs):
|
||||||
super().__init__(none_ok)
|
super().__init__(none_ok)
|
||||||
assert not isinstance(valtype, (List, ListOrValue)), valtype
|
assert not isinstance(valtype, (List, ListOrValue)), valtype
|
||||||
self.listtype = List(valtype, none_ok=none_ok, *args, **kwargs)
|
self.listtype = List(valtype, none_ok=none_ok, *args, **kwargs)
|
||||||
@ -963,7 +963,7 @@ class Font(BaseType):
|
|||||||
# Gets set when the config is initialized.
|
# Gets set when the config is initialized.
|
||||||
monospace_fonts = None
|
monospace_fonts = None
|
||||||
font_regex = re.compile(r"""
|
font_regex = re.compile(r"""
|
||||||
^(
|
(
|
||||||
(
|
(
|
||||||
# style
|
# style
|
||||||
(?P<style>normal|italic|oblique) |
|
(?P<style>normal|italic|oblique) |
|
||||||
@ -976,14 +976,14 @@ class Font(BaseType):
|
|||||||
(?P<size>[0-9]+((\.[0-9]+)?[pP][tT]|[pP][xX]))
|
(?P<size>[0-9]+((\.[0-9]+)?[pP][tT]|[pP][xX]))
|
||||||
)\ # size/weight/style are space-separated
|
)\ # size/weight/style are space-separated
|
||||||
)* # 0-inf size/weight/style tags
|
)* # 0-inf size/weight/style tags
|
||||||
(?P<family>.+)$ # mandatory font family""", re.VERBOSE)
|
(?P<family>.+) # mandatory font family""", re.VERBOSE)
|
||||||
|
|
||||||
def to_py(self, value):
|
def to_py(self, value):
|
||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not self.font_regex.match(value): # pragma: no cover
|
if not self.font_regex.fullmatch(value): # pragma: no cover
|
||||||
# This should never happen, as the regex always matches everything
|
# This should never happen, as the regex always matches everything
|
||||||
# as family.
|
# as family.
|
||||||
raise configexc.ValidationError(value, "must be a valid font")
|
raise configexc.ValidationError(value, "must be a valid font")
|
||||||
@ -1002,7 +1002,7 @@ class FontFamily(Font):
|
|||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
match = self.font_regex.match(value)
|
match = self.font_regex.fullmatch(value)
|
||||||
if not match: # pragma: no cover
|
if not match: # pragma: no cover
|
||||||
# This should never happen, as the regex always matches everything
|
# This should never happen, as the regex always matches everything
|
||||||
# as family.
|
# as family.
|
||||||
@ -1039,7 +1039,7 @@ class QtFont(Font):
|
|||||||
font.setStyle(QFont.StyleNormal)
|
font.setStyle(QFont.StyleNormal)
|
||||||
font.setWeight(QFont.Normal)
|
font.setWeight(QFont.Normal)
|
||||||
|
|
||||||
match = self.font_regex.match(value)
|
match = self.font_regex.fullmatch(value)
|
||||||
if not match: # pragma: no cover
|
if not match: # pragma: no cover
|
||||||
# This should never happen, as the regex always matches everything
|
# This should never happen, as the regex always matches everything
|
||||||
# as family.
|
# as family.
|
||||||
|
60
qutebrowser/html/back.html
Normal file
60
qutebrowser/html/back.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
const STATE_BACK = "back";
|
||||||
|
const STATE_FORWARD = "forward";
|
||||||
|
|
||||||
|
function switch_state(new_state) {
|
||||||
|
history.replaceState(
|
||||||
|
new_state,
|
||||||
|
document.title,
|
||||||
|
location.pathname + location.hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
function go_back() {
|
||||||
|
switch_state(STATE_FORWARD);
|
||||||
|
history.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
function go_forward() {
|
||||||
|
switch_state(STATE_BACK);
|
||||||
|
history.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare_restore() {
|
||||||
|
if (!document.hidden) {
|
||||||
|
go_back();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", go_back);
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are three states
|
||||||
|
// default: register focus listener,
|
||||||
|
// on focus: go back and switch to the state forward
|
||||||
|
// back: user came from a later history entry
|
||||||
|
// -> switch to the state forward,
|
||||||
|
// forward him to the previous history entry
|
||||||
|
// forward: user came from a previous history entry
|
||||||
|
// -> switch to the state back,
|
||||||
|
// forward him to the next history entry
|
||||||
|
switch (history.state) {
|
||||||
|
case STATE_BACK:
|
||||||
|
go_back();
|
||||||
|
break;
|
||||||
|
case STATE_FORWARD:
|
||||||
|
go_forward();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setTimeout(prepare_restore, 1000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<noscript><p>Javascript isn't enabled. So you need to manually go back in history to restore this tab.</p></noscript>
|
||||||
|
<p>Loading suspended page...<br>
|
||||||
|
<br>
|
||||||
|
If nothing happens, something went wrong or you disabled JavaScript.</p>
|
||||||
|
{% endblock %}
|
@ -10,7 +10,6 @@ vim: ft=html fileencoding=utf-8 sts=4 sw=4 et:
|
|||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
{% block style %}
|
{% block style %}
|
||||||
body {
|
body {
|
||||||
background-color: #fff;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border: 1px solid grey;
|
border: 1px solid #222;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th, td {
|
||||||
border: 1px solid grey;
|
border: 1px solid #222;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,4 @@
|
|||||||
# Upstream Mozilla's code
|
# Upstream Mozilla's code
|
||||||
pac_utils.js
|
pac_utils.js
|
||||||
|
# Actually a jinja template so eslint chokes on the {{}} syntax.
|
||||||
|
greasemonkey_wrapper.js
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
# qutebrowser's way of using eslint is perhaps a bit untypical: We turn on *all*
|
||||||
|
# the checks eslint has to offer, and then selectively disable/reconfigure the
|
||||||
|
# ones which got in the way below.
|
||||||
|
#
|
||||||
|
# This makes eslint much stricter (which is good). However, it means you might
|
||||||
|
# run into a case where you totally disagree with what it says, because some
|
||||||
|
# check is not useful or desired for qutebrowser, but nobody did run into it
|
||||||
|
# yet.
|
||||||
|
#
|
||||||
|
# In those cases, it's absolutely okay to modify this config as part of your PR.
|
||||||
|
# See it as a way to fine-tune eslint rather than a rigid style guide.
|
||||||
|
|
||||||
env:
|
env:
|
||||||
browser: true
|
browser: true
|
||||||
|
|
||||||
@ -21,7 +33,7 @@ rules:
|
|||||||
no-extra-parens: off
|
no-extra-parens: off
|
||||||
id-length: ["error", {"exceptions": ["i", "k", "x", "y"]}]
|
id-length: ["error", {"exceptions": ["i", "k", "x", "y"]}]
|
||||||
object-shorthand: "off"
|
object-shorthand: "off"
|
||||||
max-statements: ["error", {"max": 30}]
|
max-statements: ["error", {"max": 40}]
|
||||||
quotes: ["error", "double", {"avoidEscape": true}]
|
quotes: ["error", "double", {"avoidEscape": true}]
|
||||||
object-property-newline: ["error", {"allowMultiplePropertiesPerLine": true}]
|
object-property-newline: ["error", {"allowMultiplePropertiesPerLine": true}]
|
||||||
comma-dangle: ["error", "always-multiline"]
|
comma-dangle: ["error", "always-multiline"]
|
||||||
|
118
qutebrowser/javascript/greasemonkey_wrapper.js
Normal file
118
qutebrowser/javascript/greasemonkey_wrapper.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
(function() {
|
||||||
|
const _qute_script_id = "__gm_" + {{ scriptName | tojson }};
|
||||||
|
|
||||||
|
function GM_log(text) {
|
||||||
|
console.log(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const GM_info = {
|
||||||
|
'script': {{ scriptInfo }},
|
||||||
|
'scriptMetaStr': {{ scriptMeta | tojson }},
|
||||||
|
'scriptWillUpdate': false,
|
||||||
|
'version': "0.0.1",
|
||||||
|
// so scripts don't expect exportFunction
|
||||||
|
'scriptHandler': 'Tampermonkey',
|
||||||
|
};
|
||||||
|
|
||||||
|
function checkKey(key, funcName) {
|
||||||
|
if (typeof key !== "string") {
|
||||||
|
throw new Error(`${funcName} requires the first parameter to be of type string, not '${typeof key}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function GM_setValue(key, value) {
|
||||||
|
checkKey(key, "GM_setValue");
|
||||||
|
if (typeof value !== "string" &&
|
||||||
|
typeof value !== "number" &&
|
||||||
|
typeof value !== "boolean") {
|
||||||
|
throw new Error(`GM_setValue requires the second parameter to be of type string, number or boolean, not '${typeof value}'`);
|
||||||
|
}
|
||||||
|
localStorage.setItem(_qute_script_id + key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GM_getValue(key, default_) {
|
||||||
|
checkKey(key, "GM_getValue");
|
||||||
|
return localStorage.getItem(_qute_script_id + key) || default_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GM_deleteValue(key) {
|
||||||
|
checkKey(key, "GM_deleteValue");
|
||||||
|
localStorage.removeItem(_qute_script_id + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GM_listValues() {
|
||||||
|
const keys = [];
|
||||||
|
for (let i = 0; i < localStorage.length; i++) {
|
||||||
|
if (localStorage.key(i).startsWith(_qute_script_id)) {
|
||||||
|
keys.push(localStorage.key(i).slice(_qute_script_id.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
function GM_openInTab(url) {
|
||||||
|
window.open(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Almost verbatim copy from Eric
|
||||||
|
function GM_xmlhttpRequest(/* object */ details) {
|
||||||
|
details.method = details.method ? details.method.toUpperCase() : "GET";
|
||||||
|
|
||||||
|
if (!details.url) {
|
||||||
|
throw new Error("GM_xmlhttpRequest requires an URL.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// build XMLHttpRequest object
|
||||||
|
const oXhr = new XMLHttpRequest();
|
||||||
|
// run it
|
||||||
|
if ("onreadystatechange" in details) {
|
||||||
|
oXhr.onreadystatechange = function() {
|
||||||
|
details.onreadystatechange(oXhr);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ("onload" in details) {
|
||||||
|
oXhr.onload = function() { details.onload(oXhr); };
|
||||||
|
}
|
||||||
|
if ("onerror" in details) {
|
||||||
|
oXhr.onerror = function () { details.onerror(oXhr); };
|
||||||
|
}
|
||||||
|
|
||||||
|
oXhr.open(details.method, details.url, true);
|
||||||
|
|
||||||
|
if ("headers" in details) {
|
||||||
|
for (const header in details.headers) {
|
||||||
|
oXhr.setRequestHeader(header, details.headers[header]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("data" in details) {
|
||||||
|
oXhr.send(details.data);
|
||||||
|
} else {
|
||||||
|
oXhr.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function GM_addStyle(/* String */ styles) {
|
||||||
|
const oStyle = document.createElement("style");
|
||||||
|
oStyle.setAttribute("type", "text/css");
|
||||||
|
oStyle.appendChild(document.createTextNode(styles));
|
||||||
|
|
||||||
|
const head = document.getElementsByTagName("head")[0];
|
||||||
|
if (head === undefined) {
|
||||||
|
document.onreadystatechange = function() {
|
||||||
|
if (document.readyState === "interactive") {
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(oStyle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
head.appendChild(oStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsafeWindow = window;
|
||||||
|
|
||||||
|
// ====== The actual user script source ====== //
|
||||||
|
{{ scriptSource }}
|
||||||
|
// ====== End User Script ====== //
|
||||||
|
})();
|
@ -99,12 +99,14 @@ window._qutebrowser.webelem = (function() {
|
|||||||
|
|
||||||
const out = {
|
const out = {
|
||||||
"id": id,
|
"id": id,
|
||||||
"value": elem.value,
|
|
||||||
"outer_xml": elem.outerHTML,
|
|
||||||
"rects": [], // Gets filled up later
|
"rects": [], // Gets filled up later
|
||||||
"caret_position": caret_position,
|
"caret_position": caret_position,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Deal with various fun things which can happen in form elements
|
||||||
// https://github.com/qutebrowser/qutebrowser/issues/2569
|
// https://github.com/qutebrowser/qutebrowser/issues/2569
|
||||||
|
// https://github.com/qutebrowser/qutebrowser/issues/2877
|
||||||
|
// https://stackoverflow.com/q/22942689/2085149
|
||||||
if (typeof elem.tagName === "string") {
|
if (typeof elem.tagName === "string") {
|
||||||
out.tag_name = elem.tagName;
|
out.tag_name = elem.tagName;
|
||||||
} else if (typeof elem.nodeName === "string") {
|
} else if (typeof elem.nodeName === "string") {
|
||||||
@ -120,6 +122,18 @@ window._qutebrowser.webelem = (function() {
|
|||||||
out.class_name = "";
|
out.class_name = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof elem.value === "string" || typeof elem.value === "number") {
|
||||||
|
out.value = elem.value;
|
||||||
|
} else {
|
||||||
|
out.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof elem.outerHTML === "string") {
|
||||||
|
out.outer_xml = elem.outerHTML;
|
||||||
|
} else {
|
||||||
|
out.outer_xml = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof elem.textContent === "string") {
|
if (typeof elem.textContent === "string") {
|
||||||
out.text = elem.textContent;
|
out.text = elem.textContent;
|
||||||
} else if (typeof elem.text === "string") {
|
} else if (typeof elem.text === "string") {
|
||||||
|
@ -150,7 +150,8 @@ class BaseKeyParser(QObject):
|
|||||||
A (count, command) tuple.
|
A (count, command) tuple.
|
||||||
"""
|
"""
|
||||||
if self._supports_count:
|
if self._supports_count:
|
||||||
(countstr, cmd_input) = re.match(r'^(\d*)(.*)', keystring).groups()
|
(countstr, cmd_input) = re.fullmatch(r'(\d*)(.*)',
|
||||||
|
keystring).groups()
|
||||||
count = int(countstr) if countstr else None
|
count = int(countstr) if countstr else None
|
||||||
if count == 0 and not cmd_input:
|
if count == 0 and not cmd_input:
|
||||||
cmd_input = keystring
|
cmd_input = keystring
|
||||||
@ -213,7 +214,7 @@ class BaseKeyParser(QObject):
|
|||||||
elif match == self.Match.other:
|
elif match == self.Match.other:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Invalid match value {!r}".format(match))
|
raise utils.Unreachable("Invalid match value {!r}".format(match))
|
||||||
return match
|
return match
|
||||||
|
|
||||||
def _match_key(self, cmd_input):
|
def _match_key(self, cmd_input):
|
||||||
|
@ -322,10 +322,13 @@ class ModeManager(QObject):
|
|||||||
if self.mode is None:
|
if self.mode is None:
|
||||||
# We got events before mode is set, so just pass them through.
|
# We got events before mode is set, so just pass them through.
|
||||||
return False
|
return False
|
||||||
if event.type() == QEvent.KeyPress:
|
|
||||||
return self._eventFilter_keypress(event)
|
handlers = {
|
||||||
else:
|
QEvent.KeyPress: self._eventFilter_keypress,
|
||||||
return self._eventFilter_keyrelease(event)
|
QEvent.KeyRelease: self._eventFilter_keyrelease,
|
||||||
|
}
|
||||||
|
handler = handlers[event.type()]
|
||||||
|
return handler(event)
|
||||||
|
|
||||||
@cmdutils.register(instance='mode-manager', scope='window')
|
@cmdutils.register(instance='mode-manager', scope='window')
|
||||||
def clear_keychain(self):
|
def clear_keychain(self):
|
||||||
|
@ -71,7 +71,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
|||||||
txt = e.text().strip()
|
txt = e.text().strip()
|
||||||
if self._inhibited:
|
if self._inhibited:
|
||||||
self._debug_log("Ignoring key '{}', because the normal mode is "
|
self._debug_log("Ignoring key '{}', because the normal mode is "
|
||||||
"currently inhibited.".format(txt))
|
"currently inhibited.".format(txt))
|
||||||
return self.Match.none
|
return self.Match.none
|
||||||
match = super()._handle_single_key(e)
|
match = super()._handle_single_key(e)
|
||||||
if match == self.Match.partial:
|
if match == self.Match.partial:
|
||||||
@ -83,6 +83,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
|||||||
return match
|
return match
|
||||||
|
|
||||||
def set_inhibited_timeout(self, timeout):
|
def set_inhibited_timeout(self, timeout):
|
||||||
|
"""Ignore keypresses for the given duration."""
|
||||||
if timeout != 0:
|
if timeout != 0:
|
||||||
self._debug_log("Inhibiting the normal mode for {}ms.".format(
|
self._debug_log("Inhibiting the normal mode for {}ms.".format(
|
||||||
timeout))
|
timeout))
|
||||||
@ -217,7 +218,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
|||||||
self._last_press = LastPress.none
|
self._last_press = LastPress.none
|
||||||
return True
|
return True
|
||||||
elif match == self.Match.other:
|
elif match == self.Match.other:
|
||||||
pass
|
return None
|
||||||
elif match == self.Match.none:
|
elif match == self.Match.none:
|
||||||
# We couldn't find a keychain so we check if it's a special key.
|
# We couldn't find a keychain so we check if it's a special key.
|
||||||
return self._handle_special_key(e)
|
return self._handle_special_key(e)
|
||||||
|
@ -30,7 +30,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
|
|||||||
from qutebrowser.commands import runners, cmdutils
|
from qutebrowser.commands import runners, cmdutils
|
||||||
from qutebrowser.config import config, configfiles
|
from qutebrowser.config import config, configfiles
|
||||||
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
|
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
|
||||||
jinja, debug)
|
jinja)
|
||||||
from qutebrowser.mainwindow import messageview, prompt
|
from qutebrowser.mainwindow import messageview, prompt
|
||||||
from qutebrowser.completion import completionwidget, completer
|
from qutebrowser.completion import completionwidget, completer
|
||||||
from qutebrowser.keyinput import modeman
|
from qutebrowser.keyinput import modeman
|
||||||
@ -94,16 +94,19 @@ def get_window(via_ipc, force_window=False, force_tab=False,
|
|||||||
return window.win_id
|
return window.win_id
|
||||||
|
|
||||||
|
|
||||||
def raise_window(window):
|
def raise_window(window, alert=True):
|
||||||
"""Raise the given MainWindow object."""
|
"""Raise the given MainWindow object."""
|
||||||
window.setWindowState(window.windowState() & ~Qt.WindowMinimized)
|
window.setWindowState(window.windowState() & ~Qt.WindowMinimized)
|
||||||
window.setWindowState(window.windowState() | Qt.WindowActive)
|
window.setWindowState(window.windowState() | Qt.WindowActive)
|
||||||
window.raise_()
|
window.raise_()
|
||||||
window.activateWindow()
|
window.activateWindow()
|
||||||
QApplication.instance().alert(window)
|
|
||||||
|
if alert:
|
||||||
|
QApplication.instance().alert(window)
|
||||||
|
|
||||||
|
|
||||||
def get_target_window():
|
# WORKAROUND for https://github.com/PyCQA/pylint/issues/1770
|
||||||
|
def get_target_window(): # pylint: disable=inconsistent-return-statements
|
||||||
"""Get the target window for new tabs, or None if none exist."""
|
"""Get the target window for new tabs, or None if none exist."""
|
||||||
try:
|
try:
|
||||||
win_mode = config.val.new_instance_open_target_window
|
win_mode = config.val.new_instance_open_target_window
|
||||||
@ -131,7 +134,6 @@ class MainWindow(QWidget):
|
|||||||
Attributes:
|
Attributes:
|
||||||
status: The StatusBar widget.
|
status: The StatusBar widget.
|
||||||
tabbed_browser: The TabbedBrowser widget.
|
tabbed_browser: The TabbedBrowser widget.
|
||||||
state_before_fullscreen: window state before activation of fullscreen.
|
|
||||||
_downloadview: The DownloadView widget.
|
_downloadview: The DownloadView widget.
|
||||||
_vbox: The main QVBoxLayout.
|
_vbox: The main QVBoxLayout.
|
||||||
_commandrunner: The main CommandRunner instance.
|
_commandrunner: The main CommandRunner instance.
|
||||||
@ -231,8 +233,6 @@ class MainWindow(QWidget):
|
|||||||
|
|
||||||
objreg.get("app").new_window.emit(self)
|
objreg.get("app").new_window.emit(self)
|
||||||
|
|
||||||
self.state_before_fullscreen = self.windowState()
|
|
||||||
|
|
||||||
def _init_geometry(self, geometry):
|
def _init_geometry(self, geometry):
|
||||||
"""Initialize the window geometry or load it from disk."""
|
"""Initialize the window geometry or load it from disk."""
|
||||||
if geometry is not None:
|
if geometry is not None:
|
||||||
@ -320,7 +320,8 @@ class MainWindow(QWidget):
|
|||||||
def _init_completion(self):
|
def _init_completion(self):
|
||||||
self._completion = completionwidget.CompletionView(self.win_id, self)
|
self._completion = completionwidget.CompletionView(self.win_id, self)
|
||||||
cmd = objreg.get('status-command', scope='window', window=self.win_id)
|
cmd = objreg.get('status-command', scope='window', window=self.win_id)
|
||||||
completer_obj = completer.Completer(cmd, self._completion)
|
completer_obj = completer.Completer(cmd=cmd, win_id=self.win_id,
|
||||||
|
parent=self._completion)
|
||||||
self._completion.selection_changed.connect(
|
self._completion.selection_changed.connect(
|
||||||
completer_obj.on_selection_changed)
|
completer_obj.on_selection_changed)
|
||||||
objreg.register('completion', self._completion, scope='window',
|
objreg.register('completion', self._completion, scope='window',
|
||||||
@ -493,12 +494,9 @@ class MainWindow(QWidget):
|
|||||||
def _on_fullscreen_requested(self, on):
|
def _on_fullscreen_requested(self, on):
|
||||||
if not config.val.content.windowed_fullscreen:
|
if not config.val.content.windowed_fullscreen:
|
||||||
if on:
|
if on:
|
||||||
self.state_before_fullscreen = self.windowState()
|
self.setWindowState(self.windowState() | Qt.WindowFullScreen)
|
||||||
self.showFullScreen()
|
|
||||||
elif self.isFullScreen():
|
elif self.isFullScreen():
|
||||||
self.setWindowState(self.state_before_fullscreen)
|
self.setWindowState(self.windowState() & ~Qt.WindowFullScreen)
|
||||||
log.misc.debug('on: {}, state before fullscreen: {}'.format(
|
|
||||||
on, debug.qflags_key(Qt, self.state_before_fullscreen)))
|
|
||||||
|
|
||||||
@cmdutils.register(instance='main-window', scope='window')
|
@cmdutils.register(instance='main-window', scope='window')
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
@ -171,7 +171,7 @@ class PromptQueue(QObject):
|
|||||||
# just queue it up for later.
|
# just queue it up for later.
|
||||||
log.prompt.debug("Adding {} to queue.".format(question))
|
log.prompt.debug("Adding {} to queue.".format(question))
|
||||||
self._queue.append(question)
|
self._queue.append(question)
|
||||||
return
|
return None
|
||||||
|
|
||||||
if blocking:
|
if blocking:
|
||||||
# If we're blocking we save the old question on the stack, so we
|
# If we're blocking we save the old question on the stack, so we
|
||||||
@ -207,6 +207,7 @@ class PromptQueue(QObject):
|
|||||||
return question.answer
|
return question.answer
|
||||||
else:
|
else:
|
||||||
question.completed.connect(self._pop_later)
|
question.completed.connect(self._pop_later)
|
||||||
|
return None
|
||||||
|
|
||||||
@pyqtSlot(usertypes.KeyMode)
|
@pyqtSlot(usertypes.KeyMode)
|
||||||
def _on_mode_left(self, mode):
|
def _on_mode_left(self, mode):
|
||||||
|
@ -26,7 +26,8 @@ from qutebrowser.keyinput import modeman, modeparsers
|
|||||||
from qutebrowser.commands import cmdexc, cmdutils
|
from qutebrowser.commands import cmdexc, cmdutils
|
||||||
from qutebrowser.misc import cmdhistory, editor
|
from qutebrowser.misc import cmdhistory, editor
|
||||||
from qutebrowser.misc import miscwidgets as misc
|
from qutebrowser.misc import miscwidgets as misc
|
||||||
from qutebrowser.utils import usertypes, log, objreg, message
|
from qutebrowser.utils import usertypes, log, objreg, message, utils
|
||||||
|
from qutebrowser.config import config
|
||||||
|
|
||||||
|
|
||||||
class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||||
@ -66,6 +67,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
self.cursorPositionChanged.connect(self.update_completion)
|
self.cursorPositionChanged.connect(self.update_completion)
|
||||||
self.textChanged.connect(self.update_completion)
|
self.textChanged.connect(self.update_completion)
|
||||||
self.textChanged.connect(self.updateGeometry)
|
self.textChanged.connect(self.updateGeometry)
|
||||||
|
self.textChanged.connect(self._incremental_search)
|
||||||
|
|
||||||
def prefix(self):
|
def prefix(self):
|
||||||
"""Get the currently entered command prefix."""
|
"""Get the currently entered command prefix."""
|
||||||
@ -154,8 +156,12 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
|
|
||||||
@cmdutils.register(instance='status-command',
|
@cmdutils.register(instance='status-command',
|
||||||
modes=[usertypes.KeyMode.command], scope='window')
|
modes=[usertypes.KeyMode.command], scope='window')
|
||||||
def command_accept(self):
|
def command_accept(self, rapid=False):
|
||||||
"""Execute the command currently in the commandline."""
|
"""Execute the command currently in the commandline.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rapid: Run the command without closing or clearing the command bar.
|
||||||
|
"""
|
||||||
prefixes = {
|
prefixes = {
|
||||||
':': '',
|
':': '',
|
||||||
'/': 'search -- ',
|
'/': 'search -- ',
|
||||||
@ -163,7 +169,9 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
}
|
}
|
||||||
text = self.text()
|
text = self.text()
|
||||||
self.history.append(text)
|
self.history.append(text)
|
||||||
modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept')
|
if not rapid:
|
||||||
|
modeman.leave(self._win_id, usertypes.KeyMode.command,
|
||||||
|
'cmd accept')
|
||||||
self.got_cmd[str].emit(prefixes[text[0]] + text[1:])
|
self.got_cmd[str].emit(prefixes[text[0]] + text[1:])
|
||||||
|
|
||||||
@cmdutils.register(instance='status-command', scope='window')
|
@cmdutils.register(instance='status-command', scope='window')
|
||||||
@ -213,8 +221,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
elif text[0] in modeparsers.STARTCHARS:
|
elif text[0] in modeparsers.STARTCHARS:
|
||||||
super().set_prompt(text[0])
|
super().set_prompt(text[0])
|
||||||
else:
|
else:
|
||||||
raise AssertionError("setText got called with invalid text "
|
raise utils.Unreachable("setText got called with invalid text "
|
||||||
"'{}'!".format(text))
|
"'{}'!".format(text))
|
||||||
super().setText(text)
|
super().setText(text)
|
||||||
|
|
||||||
def keyPressEvent(self, e):
|
def keyPressEvent(self, e):
|
||||||
@ -224,6 +232,12 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
Enter/Shift+Enter/etc. will cause QLineEdit to think it's finished
|
Enter/Shift+Enter/etc. will cause QLineEdit to think it's finished
|
||||||
without command_accept to be called.
|
without command_accept to be called.
|
||||||
"""
|
"""
|
||||||
|
text = self.text()
|
||||||
|
if text in modeparsers.STARTCHARS and e.key() == Qt.Key_Backspace:
|
||||||
|
e.accept()
|
||||||
|
modeman.leave(self._win_id, usertypes.KeyMode.command,
|
||||||
|
'prefix deleted')
|
||||||
|
return
|
||||||
if e.key() == Qt.Key_Return:
|
if e.key() == Qt.Key_Return:
|
||||||
e.ignore()
|
e.ignore()
|
||||||
return
|
return
|
||||||
@ -238,3 +252,16 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
|||||||
text = 'x'
|
text = 'x'
|
||||||
width = self.fontMetrics().width(text)
|
width = self.fontMetrics().width(text)
|
||||||
return QSize(width, height)
|
return QSize(width, height)
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def _incremental_search(self, text):
|
||||||
|
if not config.val.search.incremental:
|
||||||
|
return
|
||||||
|
|
||||||
|
search_prefixes = {
|
||||||
|
'/': 'search -- ',
|
||||||
|
'?': 'search -r -- ',
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.prefix() in ['/', '?']:
|
||||||
|
self.got_cmd[str].emit(search_prefixes[text[0]] + text[1:])
|
||||||
|
@ -256,7 +256,9 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
for tab in self.widgets():
|
for tab in self.widgets():
|
||||||
self._remove_tab(tab)
|
self._remove_tab(tab)
|
||||||
|
|
||||||
def tab_close_prompt_if_pinned(self, tab, force, yes_action):
|
def tab_close_prompt_if_pinned(
|
||||||
|
self, tab, force, yes_action,
|
||||||
|
text="Are you sure you want to close a pinned tab?"):
|
||||||
"""Helper method for tab_close.
|
"""Helper method for tab_close.
|
||||||
|
|
||||||
If tab is pinned, prompt. If not, run yes_action.
|
If tab is pinned, prompt. If not, run yes_action.
|
||||||
@ -265,7 +267,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
if tab.data.pinned and not force:
|
if tab.data.pinned and not force:
|
||||||
message.confirm_async(
|
message.confirm_async(
|
||||||
title='Pinned Tab',
|
title='Pinned Tab',
|
||||||
text="Are you sure you want to close a pinned tab?",
|
text=text,
|
||||||
yes_action=yes_action, default=False, abort_on=[tab.destroyed])
|
yes_action=yes_action, default=False, abort_on=[tab.destroyed])
|
||||||
else:
|
else:
|
||||||
yes_action()
|
yes_action()
|
||||||
@ -818,6 +820,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
point, url = self._global_marks[key]
|
point, url = self._global_marks[key]
|
||||||
|
|
||||||
def callback(ok):
|
def callback(ok):
|
||||||
|
"""Scroll once loading finished."""
|
||||||
if ok:
|
if ok:
|
||||||
self.cur_load_finished.disconnect(callback)
|
self.cur_load_finished.disconnect(callback)
|
||||||
tab.scroller.to_point(point)
|
tab.scroller.to_point(point)
|
||||||
|
@ -172,14 +172,15 @@ class TabWidget(QTabWidget):
|
|||||||
fields['perc'] = ''
|
fields['perc'] = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fields['host'] = self.tab_url(idx).host()
|
url = self.tab_url(idx)
|
||||||
except qtutils.QtValueError:
|
except qtutils.QtValueError:
|
||||||
fields['host'] = ''
|
fields['host'] = ''
|
||||||
|
|
||||||
try:
|
|
||||||
fields['current_url'] = self.tab_url(idx).url()
|
|
||||||
except qtutils.QtValueError:
|
|
||||||
fields['current_url'] = ''
|
fields['current_url'] = ''
|
||||||
|
fields['protocol'] = ''
|
||||||
|
else:
|
||||||
|
fields['host'] = url.host()
|
||||||
|
fields['current_url'] = url.toDisplayString()
|
||||||
|
fields['protocol'] = url.scheme()
|
||||||
|
|
||||||
y = tab.scroller.pos_perc()[1]
|
y = tab.scroller.pos_perc()[1]
|
||||||
if y is None:
|
if y is None:
|
||||||
|
@ -152,7 +152,7 @@ def _show_dialog(*args, **kwargs):
|
|||||||
elif status == _Result.restart:
|
elif status == _Result.restart:
|
||||||
quitter.restart()
|
quitter.restart()
|
||||||
else:
|
else:
|
||||||
assert False, status
|
raise utils.Unreachable(status)
|
||||||
|
|
||||||
sys.exit(usertypes.Exit.err_init)
|
sys.exit(usertypes.Exit.err_init)
|
||||||
|
|
||||||
@ -199,8 +199,7 @@ def _handle_nouveau_graphics():
|
|||||||
buttons=[button],
|
buttons=[button],
|
||||||
)
|
)
|
||||||
|
|
||||||
# Should never be reached
|
raise utils.Unreachable
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
||||||
def _handle_wayland():
|
def _handle_wayland():
|
||||||
@ -239,8 +238,7 @@ def _handle_wayland():
|
|||||||
"(based on Chromium). "
|
"(based on Chromium). "
|
||||||
)
|
)
|
||||||
|
|
||||||
# Should never be reached
|
raise utils.Unreachable
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
@ -359,11 +357,11 @@ def _check_backend_modules():
|
|||||||
html.escape(imports.webengine_error))
|
html.escape(imports.webengine_error))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Should never be reached
|
raise utils.Unreachable
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
|
"""Check for various issues related to QtWebKit/QtWebEngine."""
|
||||||
_check_backend_modules()
|
_check_backend_modules()
|
||||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||||
_handle_ssl_support()
|
_handle_ssl_support()
|
||||||
|
@ -62,7 +62,7 @@ def parse_fatal_stacktrace(text):
|
|||||||
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
|
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
|
||||||
r' File ".*", line \d+ in (?P<func>.*)',
|
r' File ".*", line \d+ in (?P<func>.*)',
|
||||||
]
|
]
|
||||||
m = re.match('\n'.join(lines), text)
|
m = re.search('\n'.join(lines), text)
|
||||||
if m is None:
|
if m is None:
|
||||||
# We got some invalid text.
|
# We got some invalid text.
|
||||||
return ('', '')
|
return ('', '')
|
||||||
@ -348,7 +348,6 @@ class _CrashDialog(QDialog):
|
|||||||
"but you're currently running v{} - please "
|
"but you're currently running v{} - please "
|
||||||
"update!".format(newest, qutebrowser.__version__))
|
"update!".format(newest, qutebrowser.__version__))
|
||||||
text = '<br/><br/>'.join(lines)
|
text = '<br/><br/>'.join(lines)
|
||||||
self.finish()
|
|
||||||
msgbox.information(self, "Report successfully sent!", text,
|
msgbox.information(self, "Report successfully sent!", text,
|
||||||
on_finished=self.finish, plain_text=False)
|
on_finished=self.finish, plain_text=False)
|
||||||
|
|
||||||
@ -365,7 +364,6 @@ class _CrashDialog(QDialog):
|
|||||||
"<a href=https://www.qutebrowser.org/>qutebrowser.org</a> "
|
"<a href=https://www.qutebrowser.org/>qutebrowser.org</a> "
|
||||||
"by yourself.".format(msg))
|
"by yourself.".format(msg))
|
||||||
text = '<br/><br/>'.join(lines)
|
text = '<br/><br/>'.join(lines)
|
||||||
self.finish()
|
|
||||||
msgbox.information(self, "Report successfully sent!", text,
|
msgbox.information(self, "Report successfully sent!", text,
|
||||||
on_finished=self.finish, plain_text=False)
|
on_finished=self.finish, plain_text=False)
|
||||||
|
|
||||||
|
@ -20,13 +20,13 @@
|
|||||||
"""Handlers for crashes and OS signals."""
|
"""Handlers for crashes and OS signals."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import os.path
|
||||||
import sys
|
import sys
|
||||||
import bdb
|
import bdb
|
||||||
import pdb # noqa: T002
|
import pdb # noqa: T002
|
||||||
import signal
|
import signal
|
||||||
import functools
|
import functools
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import os.path
|
|
||||||
try:
|
try:
|
||||||
# WORKAROUND for segfaults when using pdb in pytest for some reason...
|
# WORKAROUND for segfaults when using pdb in pytest for some reason...
|
||||||
import readline # pylint: disable=unused-import
|
import readline # pylint: disable=unused-import
|
||||||
|
@ -202,14 +202,14 @@ def _check_modules(modules):
|
|||||||
messages = ['invalid escape sequence',
|
messages = ['invalid escape sequence',
|
||||||
'Flags not at the start of the expression']
|
'Flags not at the start of the expression']
|
||||||
with log.ignore_py_warnings(
|
with log.ignore_py_warnings(
|
||||||
category=DeprecationWarning,
|
category=DeprecationWarning,
|
||||||
message=r'({})'.format('|'.join(messages))
|
message=r'({})'.format('|'.join(messages))
|
||||||
), log.ignore_py_warnings(
|
), log.ignore_py_warnings(
|
||||||
category=PendingDeprecationWarning,
|
category=PendingDeprecationWarning,
|
||||||
module='imp'
|
module='imp'
|
||||||
), log.ignore_py_warnings(
|
), log.ignore_py_warnings(
|
||||||
category=ImportWarning,
|
category=ImportWarning,
|
||||||
message=r'Not importing directory .*: missing __init__'
|
message=r'Not importing directory .*: missing __init__'
|
||||||
):
|
):
|
||||||
importlib.import_module(name)
|
importlib.import_module(name)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
@ -261,6 +261,7 @@ def init_log(args):
|
|||||||
|
|
||||||
|
|
||||||
def check_optimize_flag():
|
def check_optimize_flag():
|
||||||
|
"""Check whether qutebrowser is running with -OO."""
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log
|
||||||
if sys.flags.optimize >= 2:
|
if sys.flags.optimize >= 2:
|
||||||
log.init.warning("Running on optimize level higher than 1, "
|
log.init.warning("Running on optimize level higher than 1, "
|
||||||
|
@ -111,9 +111,11 @@ class ExternalEditor(QObject):
|
|||||||
# the file from the external editor, see
|
# the file from the external editor, see
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/1767
|
# https://github.com/qutebrowser/qutebrowser/issues/1767
|
||||||
with tempfile.NamedTemporaryFile(
|
with tempfile.NamedTemporaryFile(
|
||||||
|
# pylint: disable=bad-continuation
|
||||||
mode='w', prefix='qutebrowser-editor-',
|
mode='w', prefix='qutebrowser-editor-',
|
||||||
encoding=config.val.editor.encoding,
|
encoding=config.val.editor.encoding,
|
||||||
delete=False) as fobj:
|
delete=False) as fobj:
|
||||||
|
# pylint: enable=bad-continuation
|
||||||
if text:
|
if text:
|
||||||
fobj.write(text)
|
fobj.write(text)
|
||||||
self._filename = fobj.name
|
self._filename = fobj.name
|
||||||
|
@ -19,12 +19,14 @@
|
|||||||
|
|
||||||
"""A QProcess which shows notifications in the GUI."""
|
"""A QProcess which shows notifications in the GUI."""
|
||||||
|
|
||||||
|
import locale
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess,
|
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess,
|
||||||
QProcessEnvironment)
|
QProcessEnvironment)
|
||||||
|
|
||||||
from qutebrowser.utils import message, log
|
from qutebrowser.utils import message, log
|
||||||
|
from qutebrowser.browser import qutescheme
|
||||||
|
|
||||||
# A mapping of QProcess::ErrorCode's to human-readable strings.
|
# A mapping of QProcess::ErrorCode's to human-readable strings.
|
||||||
|
|
||||||
@ -96,6 +98,16 @@ class GUIProcess(QObject):
|
|||||||
self._started = False
|
self._started = False
|
||||||
log.procs.debug("Process finished with code {}, status {}.".format(
|
log.procs.debug("Process finished with code {}, status {}.".format(
|
||||||
code, status))
|
code, status))
|
||||||
|
|
||||||
|
encoding = locale.getpreferredencoding(do_setlocale=False)
|
||||||
|
stderr = bytes(self._proc.readAllStandardError()).decode(
|
||||||
|
encoding, 'replace')
|
||||||
|
stdout = bytes(self._proc.readAllStandardOutput()).decode(
|
||||||
|
encoding, 'replace')
|
||||||
|
|
||||||
|
qutescheme.spawn_output = self._spawn_format(code, status,
|
||||||
|
stdout, stderr)
|
||||||
|
|
||||||
if status == QProcess.CrashExit:
|
if status == QProcess.CrashExit:
|
||||||
message.error("{} crashed!".format(self._what.capitalize()))
|
message.error("{} crashed!".format(self._what.capitalize()))
|
||||||
elif status == QProcess.NormalExit and code == 0:
|
elif status == QProcess.NormalExit and code == 0:
|
||||||
@ -109,13 +121,22 @@ class GUIProcess(QObject):
|
|||||||
message.error("{} exited with status {}, see :messages for "
|
message.error("{} exited with status {}, see :messages for "
|
||||||
"details.".format(self._what.capitalize(), code))
|
"details.".format(self._what.capitalize(), code))
|
||||||
|
|
||||||
stderr = bytes(self._proc.readAllStandardError()).decode('utf-8')
|
|
||||||
stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8')
|
|
||||||
if stdout:
|
if stdout:
|
||||||
log.procs.error("Process stdout:\n" + stdout.strip())
|
log.procs.error("Process stdout:\n" + stdout.strip())
|
||||||
if stderr:
|
if stderr:
|
||||||
log.procs.error("Process stderr:\n" + stderr.strip())
|
log.procs.error("Process stderr:\n" + stderr.strip())
|
||||||
|
|
||||||
|
def _spawn_format(self, code=0, status=0, stdout="", stderr=""):
|
||||||
|
"""Produce a formatted string for spawn output."""
|
||||||
|
stdout = (stdout or "(No output)").strip()
|
||||||
|
stderr = (stderr or "(No output)").strip()
|
||||||
|
|
||||||
|
spawn_string = ("Process finished with code {}, status {}\n"
|
||||||
|
"\nProcess stdout:\n {}"
|
||||||
|
"\nProcess stderr:\n {}").format(code, status,
|
||||||
|
stdout, stderr)
|
||||||
|
return spawn_string
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def on_started(self):
|
def on_started(self):
|
||||||
"""Called when the process started successfully."""
|
"""Called when the process started successfully."""
|
||||||
|
@ -87,7 +87,7 @@ class KeyHintView(QLabel):
|
|||||||
Args:
|
Args:
|
||||||
prefix: The current partial keystring.
|
prefix: The current partial keystring.
|
||||||
"""
|
"""
|
||||||
countstr, prefix = re.match(r'^(\d*)(.*)', prefix).groups()
|
countstr, prefix = re.fullmatch(r'(\d*)(.*)', prefix).groups()
|
||||||
if not prefix:
|
if not prefix:
|
||||||
self._show_timer.stop()
|
self._show_timer.stop()
|
||||||
self.hide()
|
self.hide()
|
||||||
|
@ -241,7 +241,7 @@ class WrapperLayout(QLayout):
|
|||||||
self._widget = None
|
self._widget = None
|
||||||
|
|
||||||
def addItem(self, _widget):
|
def addItem(self, _widget):
|
||||||
raise AssertionError("Should never be called!")
|
raise utils.Unreachable
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
return self._widget.sizeHint()
|
return self._widget.sizeHint()
|
||||||
@ -250,7 +250,7 @@ class WrapperLayout(QLayout):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def takeAt(self, _index):
|
def takeAt(self, _index):
|
||||||
raise AssertionError("Should never be called!")
|
raise utils.Unreachable
|
||||||
|
|
||||||
def setGeometry(self, rect):
|
def setGeometry(self, rect):
|
||||||
self._widget.setGeometry(rect)
|
self._widget.setGeometry(rect)
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
import itertools
|
||||||
|
import urllib
|
||||||
|
|
||||||
import sip
|
import sip
|
||||||
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
|
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
|
||||||
@ -205,7 +207,13 @@ class SessionManager(QObject):
|
|||||||
for idx, item in enumerate(tab.history):
|
for idx, item in enumerate(tab.history):
|
||||||
qtutils.ensure_valid(item)
|
qtutils.ensure_valid(item)
|
||||||
item_data = self._save_tab_item(tab, idx, item)
|
item_data = self._save_tab_item(tab, idx, item)
|
||||||
data['history'].append(item_data)
|
if item.url().scheme() == 'qute' and item.url().host() == 'back':
|
||||||
|
# don't add qute://back to the session file
|
||||||
|
if item_data.get('active', False) and data['history']:
|
||||||
|
# mark entry before qute://back as active
|
||||||
|
data['history'][-1]['active'] = True
|
||||||
|
else:
|
||||||
|
data['history'].append(item_data)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _save_all(self, *, only_window=None, with_private=False):
|
def _save_all(self, *, only_window=None, with_private=False):
|
||||||
@ -251,7 +259,7 @@ class SessionManager(QObject):
|
|||||||
object.
|
object.
|
||||||
"""
|
"""
|
||||||
if name is default:
|
if name is default:
|
||||||
name = config.val.session_default_name
|
name = config.val.session.default_name
|
||||||
if name is None:
|
if name is None:
|
||||||
if self._current is not None:
|
if self._current is not None:
|
||||||
name = self._current
|
name = self._current
|
||||||
@ -283,7 +291,7 @@ class SessionManager(QObject):
|
|||||||
data = self._last_window_session
|
data = self._last_window_session
|
||||||
if data is None:
|
if data is None:
|
||||||
log.sessions.error("last_window_session is None while saving!")
|
log.sessions.error("last_window_session is None while saving!")
|
||||||
return
|
return None
|
||||||
else:
|
else:
|
||||||
data = self._save_all(only_window=only_window,
|
data = self._save_all(only_window=only_window,
|
||||||
with_private=with_private)
|
with_private=with_private)
|
||||||
@ -323,7 +331,18 @@ class SessionManager(QObject):
|
|||||||
def _load_tab(self, new_tab, data):
|
def _load_tab(self, new_tab, data):
|
||||||
"""Load yaml data into a newly opened tab."""
|
"""Load yaml data into a newly opened tab."""
|
||||||
entries = []
|
entries = []
|
||||||
for histentry in data['history']:
|
lazy_load = []
|
||||||
|
# use len(data['history'])
|
||||||
|
# -> dropwhile empty if not session.lazy_session
|
||||||
|
lazy_index = len(data['history'])
|
||||||
|
gen = itertools.chain(
|
||||||
|
itertools.takewhile(lambda _: not lazy_load,
|
||||||
|
enumerate(data['history'])),
|
||||||
|
enumerate(lazy_load),
|
||||||
|
itertools.dropwhile(lambda i: i[0] < lazy_index,
|
||||||
|
enumerate(data['history'])))
|
||||||
|
|
||||||
|
for i, histentry in gen:
|
||||||
user_data = {}
|
user_data = {}
|
||||||
|
|
||||||
if 'zoom' in data:
|
if 'zoom' in data:
|
||||||
@ -347,6 +366,20 @@ class SessionManager(QObject):
|
|||||||
if 'pinned' in histentry:
|
if 'pinned' in histentry:
|
||||||
new_tab.data.pinned = histentry['pinned']
|
new_tab.data.pinned = histentry['pinned']
|
||||||
|
|
||||||
|
if (config.val.session.lazy_restore and
|
||||||
|
histentry.get('active', False) and
|
||||||
|
not histentry['url'].startswith('qute://back')):
|
||||||
|
# remove "active" mark and insert back page marked as active
|
||||||
|
lazy_index = i + 1
|
||||||
|
lazy_load.append({
|
||||||
|
'title': histentry['title'],
|
||||||
|
'url':
|
||||||
|
'qute://back#' +
|
||||||
|
urllib.parse.quote(histentry['title']),
|
||||||
|
'active': True
|
||||||
|
})
|
||||||
|
histentry['active'] = False
|
||||||
|
|
||||||
active = histentry.get('active', False)
|
active = histentry.get('active', False)
|
||||||
url = QUrl.fromEncoded(histentry['url'].encode('ascii'))
|
url = QUrl.fromEncoded(histentry['url'].encode('ascii'))
|
||||||
if 'original-url' in histentry:
|
if 'original-url' in histentry:
|
||||||
@ -360,6 +393,7 @@ class SessionManager(QObject):
|
|||||||
entries.append(entry)
|
entries.append(entry)
|
||||||
if active:
|
if active:
|
||||||
new_tab.title_changed.emit(histentry['title'])
|
new_tab.title_changed.emit(histentry['title'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_tab.history.load_items(entries)
|
new_tab.history.load_items(entries)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@ -388,7 +422,7 @@ class SessionManager(QObject):
|
|||||||
window=window.win_id)
|
window=window.win_id)
|
||||||
tab_to_focus = None
|
tab_to_focus = None
|
||||||
for i, tab in enumerate(win['tabs']):
|
for i, tab in enumerate(win['tabs']):
|
||||||
new_tab = tabbed_browser.tabopen()
|
new_tab = tabbed_browser.tabopen(background=False)
|
||||||
self._load_tab(new_tab, tab)
|
self._load_tab(new_tab, tab)
|
||||||
if tab.get('active', False):
|
if tab.get('active', False):
|
||||||
tab_to_focus = i
|
tab_to_focus = i
|
||||||
@ -460,7 +494,7 @@ class SessionManager(QObject):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: The name of the session. If not given, the session configured
|
name: The name of the session. If not given, the session configured
|
||||||
in session_default_name is saved.
|
in session.default_name is saved.
|
||||||
current: Save the current session instead of the default.
|
current: Save the current session instead of the default.
|
||||||
quiet: Don't show confirmation message.
|
quiet: Don't show confirmation message.
|
||||||
force: Force saving internal sessions (starting with an underline).
|
force: Force saving internal sessions (starting with an underline).
|
||||||
@ -485,7 +519,9 @@ class SessionManager(QObject):
|
|||||||
raise cmdexc.CommandError("Error while saving session: {}"
|
raise cmdexc.CommandError("Error while saving session: {}"
|
||||||
.format(e))
|
.format(e))
|
||||||
else:
|
else:
|
||||||
if not quiet:
|
if quiet:
|
||||||
|
log.sessions.debug("Saved session {}.".format(name))
|
||||||
|
else:
|
||||||
message.info("Saved session {}.".format(name))
|
message.info("Saved session {}.".format(name))
|
||||||
|
|
||||||
@cmdutils.register(instance='session-manager')
|
@cmdutils.register(instance='session-manager')
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log, utils
|
||||||
|
|
||||||
|
|
||||||
class ShellLexer:
|
class ShellLexer:
|
||||||
@ -117,7 +117,8 @@ class ShellLexer:
|
|||||||
else:
|
else:
|
||||||
self.token += nextchar
|
self.token += nextchar
|
||||||
else:
|
else:
|
||||||
raise AssertionError("Invalid state {!r}!".format(self.state))
|
raise utils.Unreachable(
|
||||||
|
"Invalid state {!r}!".format(self.state))
|
||||||
if self.state in self.escape and not self.keep:
|
if self.state in self.escape and not self.keep:
|
||||||
self.token += self.state
|
self.token += self.state
|
||||||
if self.token or self.quoted:
|
if self.token or self.quoted:
|
||||||
|
@ -74,7 +74,8 @@ def get_argparser():
|
|||||||
"session even if one would be restored.",
|
"session even if one would be restored.",
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser.add_argument('--target', choices=['auto', 'tab', 'tab-bg',
|
parser.add_argument('--target', choices=['auto', 'tab', 'tab-bg',
|
||||||
'tab-silent', 'tab-bg-silent', 'window'],
|
'tab-silent', 'tab-bg-silent',
|
||||||
|
'window'],
|
||||||
help="How URLs should be opened if there is already a "
|
help="How URLs should be opened if there is already a "
|
||||||
"qutebrowser instance running.")
|
"qutebrowser instance running.")
|
||||||
parser.add_argument('--backend', choices=['webkit', 'webengine'],
|
parser.add_argument('--backend', choices=['webkit', 'webengine'],
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user