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__
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
raise utils\.Unreachable
|
||||
if __name__ == ["']__main__["']:
|
||||
|
||||
[xml]
|
||||
|
3
.flake8
3
.flake8
@ -1,6 +1,7 @@
|
||||
[flake8]
|
||||
exclude = .*,__pycache__,resources.py
|
||||
# B001: bare except
|
||||
# B008: Do not perform calls in argument defaults. (fine with some Qt stuff)
|
||||
# B305: .next() (false-positives)
|
||||
# E128: continuation line under-indented for visual indent
|
||||
# E226: missing whitespace around arithmetic operator
|
||||
@ -33,7 +34,7 @@ exclude = .*,__pycache__,resources.py
|
||||
# D413: Missing blank line after last section (not in pep257?)
|
||||
# A003: Builtin name for class attribute (needed for attrs)
|
||||
ignore =
|
||||
B001,B305,
|
||||
B001,B008,B305,
|
||||
E128,E226,E265,E501,E402,E266,E722,E731,
|
||||
F401,
|
||||
N802,
|
||||
|
16
.gitignore
vendored
16
.gitignore
vendored
@ -31,12 +31,12 @@ __pycache__
|
||||
/prof
|
||||
/venv
|
||||
TODO
|
||||
/scripts/testbrowser_cpp/webkit/Makefile
|
||||
/scripts/testbrowser_cpp/webkit/main.o
|
||||
/scripts/testbrowser_cpp/webkit/testbrowser
|
||||
/scripts/testbrowser_cpp/webkit/.qmake.stash
|
||||
/scripts/testbrowser_cpp/webengine/Makefile
|
||||
/scripts/testbrowser_cpp/webengine/main.o
|
||||
/scripts/testbrowser_cpp/webengine/testbrowser
|
||||
/scripts/testbrowser_cpp/webengine/.qmake.stash
|
||||
/scripts/testbrowser/cpp/webkit/Makefile
|
||||
/scripts/testbrowser/cpp/webkit/main.o
|
||||
/scripts/testbrowser/cpp/webkit/testbrowser
|
||||
/scripts/testbrowser/cpp/webkit/.qmake.stash
|
||||
/scripts/testbrowser/cpp/webengine/Makefile
|
||||
/scripts/testbrowser/cpp/webengine/main.o
|
||||
/scripts/testbrowser/cpp/webengine/testbrowser
|
||||
/scripts/testbrowser/cpp/webengine/.qmake.stash
|
||||
/scripts/dev/pylint_checkers/qute_pylint.egg-info
|
||||
|
35
.pylintrc
35
.pylintrc
@ -13,38 +13,33 @@ persistent=n
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
enable=all
|
||||
disable=no-self-use,
|
||||
fixme,
|
||||
global-statement,
|
||||
locally-disabled,
|
||||
disable=locally-disabled,
|
||||
locally-enabled,
|
||||
too-many-ancestors,
|
||||
too-few-public-methods,
|
||||
too-many-public-methods,
|
||||
suppressed-message,
|
||||
fixme,
|
||||
no-self-use,
|
||||
cyclic-import,
|
||||
bad-continuation,
|
||||
too-many-instance-attributes,
|
||||
blacklisted-name,
|
||||
too-many-lines,
|
||||
logging-format-interpolation,
|
||||
logging-not-lazy,
|
||||
broad-except,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used,
|
||||
ungrouped-imports,
|
||||
suppressed-message,
|
||||
too-many-return-statements,
|
||||
duplicate-code,
|
||||
global-statement,
|
||||
wrong-import-position,
|
||||
duplicate-code,
|
||||
no-else-return,
|
||||
# https://github.com/PyCQA/pylint/issues/1698
|
||||
unsupported-membership-test,
|
||||
unsupported-assignment-operation,
|
||||
unsubscriptable-object,
|
||||
too-many-ancestors,
|
||||
too-many-public-methods,
|
||||
too-many-instance-attributes,
|
||||
too-many-lines,
|
||||
too-many-return-statements,
|
||||
too-many-boolean-expressions,
|
||||
too-many-locals,
|
||||
too-many-branches,
|
||||
too-many-statements
|
||||
too-many-statements,
|
||||
too-few-public-methods
|
||||
|
||||
[BASIC]
|
||||
function-rgx=[a-z_][a-z0-9_]{2,50}$
|
||||
@ -73,10 +68,10 @@ valid-metaclass-classmethod-first-arg=cls
|
||||
|
||||
[TYPECHECK]
|
||||
ignored-modules=PyQt5,PyQt5.QtWebKit
|
||||
ignored-classes=_CountingAttr
|
||||
|
||||
[IMPORTS]
|
||||
# WORKAROUND
|
||||
# For some reason, pylint doesn't know about some Python 3 modules on
|
||||
# AppVeyor...
|
||||
known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types
|
||||
known-third-party=sip
|
||||
|
@ -23,7 +23,7 @@ matrix:
|
||||
env: TESTENV=py36-pyqt59-cov
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=sierra
|
||||
osx_image: xcode8.3
|
||||
osx_image: xcode9.2
|
||||
language: generic
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||
# - os: osx
|
||||
@ -52,6 +52,10 @@ matrix:
|
||||
language: node_js
|
||||
python: null
|
||||
node_js: "lts/*"
|
||||
- os: linux
|
||||
language: generic
|
||||
env: TESTENV=shellcheck
|
||||
services: docker
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
|
@ -23,7 +23,7 @@ include qutebrowser/config/configdata.yml
|
||||
|
||||
prune www
|
||||
prune scripts/dev
|
||||
prune scripts/testbrowser_cpp
|
||||
prune scripts/testbrowser/cpp
|
||||
prune .github
|
||||
exclude scripts/asciidoc2html.py
|
||||
exclude doc/notes
|
||||
|
@ -15,57 +15,105 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.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
|
||||
~~~~~~~
|
||||
|
||||
- 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.indicator` -> `tabs.indicator.width`
|
||||
* `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.
|
||||
- The `asciidoc2html.py` script now uses Pygments (which is already a dependency
|
||||
of qutebrowser) instead of `source-highlight` for syntax highlighting.
|
||||
- The `:buffer` command now doesn't require quoting anymore, similar to `:open`.
|
||||
- The `importer.py` script was largely rewritten and now also supports importing
|
||||
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
|
||||
`QT_WAYLAND_DISABLE_WINDOWDECORATION` is set in the environment,
|
||||
the decorations are still hidden.
|
||||
@ -77,36 +125,46 @@ Changed
|
||||
- The `qute://version` page now also shows the uptime of qutebrowser.
|
||||
- qutebrowser now prompts to create a non-existing directory when starting a
|
||||
download.
|
||||
- Much improved user stylesheet handling which reduces flickering
|
||||
and updates immediately after setting a stylesheet.
|
||||
- `:completion-item-focus` now has a `--history` flag 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.
|
||||
- `: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.
|
||||
- `: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.
|
||||
- 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
|
||||
~~~~~
|
||||
|
||||
- More consistent sizing for favicons with vertical tabs.
|
||||
- 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.
|
||||
- Sites like `qute://help` now redirect to `qute://help/` to make sure links
|
||||
work properly.
|
||||
- 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
|
||||
:yank or qute:// URLs.
|
||||
- Fixed crash when opening `qute://help/img`
|
||||
`:yank` or `qute://` URLs.
|
||||
- Fixed crash when opening `qute://help/img`.
|
||||
- Fixed `gU` (`:navigate up`) on `qute://help` and webservers not handling `..`
|
||||
in a URL.
|
||||
- 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.
|
||||
- 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
|
||||
~~~~~~~~~~
|
||||
@ -119,6 +177,8 @@ Removed
|
||||
|
||||
- The long-deprecated `:prompt-yes`, `:prompt-no`, `:paste-primary` and `:paste`
|
||||
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 `x[xtb]` default bindings got removed again as many users accidentally
|
||||
triggered them.
|
||||
|
@ -221,5 +221,5 @@ My issue is not listed.::
|
||||
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
||||
using the `:report` command.
|
||||
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.
|
||||
|
@ -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.
|
||||
|<<forward,forward>>|Go forward in the history of the current tab.
|
||||
|<<fullscreen,fullscreen>>|Toggle fullscreen mode.
|
||||
|<<greasemonkey-reload,greasemonkey-reload>>|Re-read Greasemonkey scripts from disk.
|
||||
|<<help,help>>|Show help about a command or setting.
|
||||
|<<hint,hint>>|Start hinting.
|
||||
|<<history,history>>|Show browsing history.
|
||||
@ -333,12 +334,10 @@ Write the current configuration to a config.py file.
|
||||
|
||||
[[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.
|
||||
|
||||
The form `:download [url] [dest]` is deprecated, use `:download --dest [dest] [url]` instead.
|
||||
|
||||
==== positional arguments
|
||||
* +'url'+: The URL to download. If not given, download the current page.
|
||||
|
||||
@ -491,6 +490,12 @@ Toggle fullscreen mode.
|
||||
==== optional arguments
|
||||
* +*-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
|
||||
Syntax: +:help [*--tab*] [*--bg*] [*--window*] ['topic']+
|
||||
@ -511,7 +516,7 @@ Show help about a command or setting.
|
||||
|
||||
[[hint]]
|
||||
=== hint
|
||||
Syntax: +:hint [*--rapid*] [*--mode* 'mode'] [*--add-history*]
|
||||
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*]
|
||||
['group'] ['target'] ['args' ['args' ...]]+
|
||||
|
||||
Start hinting.
|
||||
@ -565,11 +570,6 @@ Start hinting.
|
||||
|
||||
|
||||
==== 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.
|
||||
|
||||
- `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.
|
||||
|
||||
* +*-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
|
||||
* 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.
|
||||
|
||||
==== 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
|
||||
@ -1141,7 +1146,7 @@ Set a mark at the current scroll position in the current tab.
|
||||
|
||||
[[spawn]]
|
||||
=== spawn
|
||||
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+
|
||||
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--detach*] 'cmdline'+
|
||||
|
||||
Spawn a command in a shell.
|
||||
|
||||
@ -1156,6 +1161,7 @@ Spawn a command in a shell.
|
||||
- `/usr/share/qutebrowser/userscripts`
|
||||
|
||||
* +*-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.
|
||||
|
||||
==== note
|
||||
@ -1415,8 +1421,13 @@ How many steps to zoom out.
|
||||
|==============
|
||||
[[command-accept]]
|
||||
=== command-accept
|
||||
Syntax: +:command-accept [*--rapid*]+
|
||||
|
||||
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
|
||||
Go forward in the commandline history.
|
||||
|
@ -264,7 +264,7 @@ get a string:
|
||||
.config.py:
|
||||
[source,python]
|
||||
----
|
||||
print(str(config.configdir / 'config.py')
|
||||
print(str(config.configdir / 'config.py'))
|
||||
----
|
||||
|
||||
Handling errors
|
||||
|
@ -199,7 +199,6 @@
|
||||
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|
||||
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|
||||
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|
||||
|<<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.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.
|
||||
@ -222,7 +221,10 @@
|
||||
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|
||||
|<<scrolling.bar,scrolling.bar>>|Show a scrollbar.
|
||||
|<<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.
|
||||
|<<statusbar.hide,statusbar.hide>>|Hide the statusbar unless a message is shown.
|
||||
|<<statusbar.padding,statusbar.padding>>|Padding (in pixels) for the statusbar.
|
||||
@ -425,6 +427,7 @@ Default:
|
||||
* +pass:[<Ctrl-K>]+: +pass:[rl-kill-line]+
|
||||
* +pass:[<Ctrl-N>]+: +pass:[command-history-next]+
|
||||
* +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-Tab>]+: +pass:[completion-item-focus prev-category]+
|
||||
* +pass:[<Ctrl-Tab>]+: +pass:[completion-item-focus next-category]+
|
||||
@ -698,10 +701,15 @@ Default: +pass:[#333333]+
|
||||
[[colors.completion.fg]]
|
||||
=== colors.completion.fg
|
||||
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
|
||||
@ -2325,20 +2333,6 @@ Type: <<types,Int>>
|
||||
|
||||
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
|
||||
Which unbound keys to forward to the webview in normal mode.
|
||||
@ -2556,8 +2550,30 @@ Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
[[session_default_name]]
|
||||
=== session_default_name
|
||||
[[search.ignore_case]]
|
||||
=== 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.
|
||||
If this is set to null, the session which was last loaded is saved.
|
||||
|
||||
@ -2565,6 +2581,14 @@ Type: <<types,SessionName>>
|
||||
|
||||
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
|
||||
Languages to use for spell checking.
|
||||
@ -2904,8 +2928,9 @@ The following placeholders are defined:
|
||||
* `{scroll_pos}`: Page scroll position.
|
||||
* `{host}`: Host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{private}` : Indicates when private mode is enabled.
|
||||
* `{current_url}` : URL of the current web page.
|
||||
* `{private}`: Indicates when private mode is enabled.
|
||||
* `{current_url}`: URL of the current web page.
|
||||
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
<<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
|
||||
@ -54,7 +54,18 @@ Install the packages:
|
||||
# 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
|
||||
QtWebEngine version.
|
||||
@ -67,8 +78,7 @@ $ python3 scripts/asciidoc2html.py
|
||||
----
|
||||
|
||||
- If you prefer using QtWebKit, there's an up-to-date version available in
|
||||
Debian experimental, or from http://repo.paretje.be/unstable/[this repository]
|
||||
for Debian Stretch.
|
||||
https://packages.debian.org/buster/libqt5webkit5[Debian Testing].
|
||||
- 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
|
||||
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
|
||||
------------
|
||||
|
||||
@ -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
|
||||
also typically means you'll be using an older release of QtWebEngine.
|
||||
|
||||
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
|
||||
Python3 is in your PATH before running tox.
|
||||
On Windows, run `set PYTHON=C:\path\to\python.exe` (CMD) or ``$Env:PYTHON =
|
||||
"..."` (Powershell) first.
|
||||
|
||||
Creating a wrapper script
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -1,207 +1,302 @@
|
||||
/* XPM */
|
||||
static char *qutebrowser[] = {
|
||||
/* columns rows colors chars-per-pixel */
|
||||
"32 32 169 2 ",
|
||||
" c #0A396E",
|
||||
". c #0B3C72",
|
||||
"X c #0B4077",
|
||||
"o c #0C437B",
|
||||
"O c #134175",
|
||||
"+ c #15467C",
|
||||
"@ c #18477B",
|
||||
"# c #1A497D",
|
||||
"$ c #0D4B86",
|
||||
"% c #0F4E8D",
|
||||
"& c #124A80",
|
||||
"* c #1F4F83",
|
||||
"= c #0E518C",
|
||||
"- c #1F5084",
|
||||
"; c #11508C",
|
||||
": c #0F5193",
|
||||
"> c #115799",
|
||||
", c #115B9C",
|
||||
"< c #204F83",
|
||||
"1 c #245287",
|
||||
"2 c #2A598C",
|
||||
"3 c #325E8F",
|
||||
"4 c #11609F",
|
||||
"5 c #346496",
|
||||
"6 c #3B6898",
|
||||
"7 c #115CA1",
|
||||
"8 c #115EAC",
|
||||
"9 c #1263A3",
|
||||
"0 c #1260AD",
|
||||
"q c #136BAC",
|
||||
"w c #136BB2",
|
||||
"e c #1366BA",
|
||||
"r c #196BB2",
|
||||
"t c #157ABB",
|
||||
"y c #1577BB",
|
||||
"u c #2E6DB0",
|
||||
"i c #387FB1",
|
||||
"p c #456E9A",
|
||||
"a c #4873A1",
|
||||
"s c #4375AA",
|
||||
"d c #507AA6",
|
||||
"f c #597EA4",
|
||||
"g c #4D7EB3",
|
||||
"h c #156FCB",
|
||||
"j c #167AC5",
|
||||
"k c #1675CA",
|
||||
"l c #177BCE",
|
||||
"z c #1777D8",
|
||||
"x c #1476E4",
|
||||
"c c #167BE6",
|
||||
"v c #167DE8",
|
||||
"b c #197EEF",
|
||||
"n c #1A7FF0",
|
||||
"m c #1A80BE",
|
||||
"M c #5F87AF",
|
||||
"N c #5D8BBA",
|
||||
"B c #5A84B1",
|
||||
"V c #6C8FB3",
|
||||
"C c #6F96BE",
|
||||
"Z c #1886CC",
|
||||
"A c #1883D7",
|
||||
"S c #198DD5",
|
||||
"D c #1987D9",
|
||||
"F c #198ADC",
|
||||
"G c #1A96DC",
|
||||
"H c #3090D9",
|
||||
"J c #1682E9",
|
||||
"K c #1983ED",
|
||||
"L c #1689E9",
|
||||
"P c #1A8DEE",
|
||||
"I c #1B95ED",
|
||||
"U c #1C9EEA",
|
||||
"Y c #1B97E4",
|
||||
"T c #1A84F2",
|
||||
"R c #1A8BF2",
|
||||
"E c #1C94F4",
|
||||
"W c #1D9CF5",
|
||||
"Q c #3388E6",
|
||||
"! c #3D90E9",
|
||||
"~ c #228EF3",
|
||||
"^ c #229FF6",
|
||||
"/ c #3294F4",
|
||||
"( c #3D9FF6",
|
||||
") c #339CF4",
|
||||
"_ c #1CA2E5",
|
||||
"` c #1DABEE",
|
||||
"' c #1DA4F6",
|
||||
"] c #1EA9F7",
|
||||
"[ c #1EADF8",
|
||||
"{ c #1FB4F9",
|
||||
"} c #1FB9FA",
|
||||
"| c #20ACF8",
|
||||
" . c #27A4F6",
|
||||
".. c #3DA9F6",
|
||||
"X. c #20B9FA",
|
||||
"o. c #2EB6F9",
|
||||
"O. c #458DC9",
|
||||
"+. c #5C8DC1",
|
||||
"@. c #5795C6",
|
||||
"#. c #709DCB",
|
||||
"$. c #74A8DD",
|
||||
"%. c #4A97EA",
|
||||
"&. c #4896EA",
|
||||
"*. c #559EEA",
|
||||
"=. c #439AF5",
|
||||
"-. c #46A3F6",
|
||||
";. c #5FA9F6",
|
||||
":. c #5EA6F3",
|
||||
">. c #47BCF9",
|
||||
",. c #51B5F8",
|
||||
"<. c #58BDF8",
|
||||
"1. c #68ABEF",
|
||||
"2. c #7DB9E7",
|
||||
"3. c #63AEF7",
|
||||
"4. c #6FB1F7",
|
||||
"5. c #66B9F8",
|
||||
"6. c #61B2F6",
|
||||
"7. c #71B4F7",
|
||||
"8. c #78B7F4",
|
||||
"9. c #72BFF9",
|
||||
"0. c #3BC0FA",
|
||||
"q. c #6FCEFB",
|
||||
"w. c #6CC5FA",
|
||||
"e. c #7BCAF9",
|
||||
"r. c #89A7C3",
|
||||
"t. c #83A2C1",
|
||||
"y. c #98B6D3",
|
||||
"u. c #9DB9D3",
|
||||
"i. c #89B6E4",
|
||||
"p. c #83B6E9",
|
||||
"a. c #81BDF7",
|
||||
"s. c #83BFF8",
|
||||
"d. c #9EC4E9",
|
||||
"f. c #8CC2F9",
|
||||
"g. c #85CDFB",
|
||||
"h. c #87C4F9",
|
||||
"j. c #92C6F9",
|
||||
"k. c #95CAFA",
|
||||
"l. c #9CCBFA",
|
||||
"z. c #89D7FC",
|
||||
"x. c #91D9FC",
|
||||
"c. c #9CDEFD",
|
||||
"v. c #9ED2FB",
|
||||
"b. c #A7CAEC",
|
||||
"n. c #B5CEE3",
|
||||
"m. c #A1CEFA",
|
||||
"M. c #AED0F0",
|
||||
"N. c #ACD6FA",
|
||||
"B. c #A0DFFC",
|
||||
"V. c #AFD8FC",
|
||||
"C. c #B5D9FB",
|
||||
"Z. c #BCDDFC",
|
||||
"A. c #BFDCF5",
|
||||
"S. c #ACE3FD",
|
||||
"D. c #B5E5FE",
|
||||
"F. c #BBE2FC",
|
||||
"G. c #CFE5F5",
|
||||
"H. c #C3E1FC",
|
||||
"J. c #CAE6FD",
|
||||
"K. c #CCEBFD",
|
||||
"L. c #C4EBFE",
|
||||
"P. c #D6EDFE",
|
||||
"I. c #DAEEFD",
|
||||
"U. c #DEF1FE",
|
||||
"Y. c #D6F2FE",
|
||||
"T. c #E4F4FE",
|
||||
"R. c #E9F6FE",
|
||||
"E. c #EBF8FF",
|
||||
"W. c None",
|
||||
/* pixels */
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"W.W.W.W.W.W.x.x.K.T.T.T.L.P.q.o.{ } } ` _ { { { { { W.W.W.W.W.W.",
|
||||
"W.W.W.W.W.c.P.D.G.u.r.i 9 Z _ { { G 4 X t { { { { { { W.W.W.W.W.",
|
||||
"W.W.W.W.K.U.n.f O { = t { { { { [ { { W.W.W.W.",
|
||||
"W.W.W.F.I.t.. ' t { { [ [ [ [ [ >.W.W.W.",
|
||||
"W.W.x.P.V ' X t ` [ [ [ [ [ [ o.e.W.W.",
|
||||
"W.W.J.y. X t S Y Z $ ' . y [ [ [ ] [ [ | Z.J.W.W.",
|
||||
"W.<.e.& , _ ] ] [ ] U . ' . y [ ' [ ] ] ] w.K.J.g.W.",
|
||||
"W.' S o ' ' [ ' [ ' ] o ' . y Y 9 = = 9 @.J.J.J.F.W.",
|
||||
"W.| , j ' ' ' ' ' ' ' o ' . $ p A.J.J.g.",
|
||||
"' .. G ' ' ' ' ' ' ' o ' . M H.H.h.",
|
||||
",.2. . W ' W ' ' ' ' W . ' . M.A.x.",
|
||||
"N.M.. . W W W ' W W W W .w 9 I U 0 #.Z.m.",
|
||||
" .9.O D W W W W ' W j $ % F W W W .5 d Z.C.",
|
||||
"W W ; 9 9.h.5...Q % o j W W W W W W O. 3 C.N.",
|
||||
"E W 7 B b.d.a . w E E W W W E W E A @ C.l.",
|
||||
"I E l u W E W E W E E E E A . - k.6.",
|
||||
"P E E 7 m.o E E E E E E E E l . = E P ",
|
||||
"L E E E > . O s.o E E E E E E E E 7 , E L ",
|
||||
"W.R E R ) #.5 1 6 N i.2 s.+ E E E E E E R L . k R W.",
|
||||
"W.L R E -.m.m.m.m.m.m.2 m.@ N m.m.s.( R R % X E J W.",
|
||||
"W.W.K R ~ a.m.l.l.l.l.2 s.+ < i.l.m.j.h % e K W.W.",
|
||||
"W.W.J R R / l.l.l.l.k.2 s.+ * 5 + 8 R J W.W.",
|
||||
"W.W.W.v T R 3.k.k.j.k.2 2 j.& . 8 R v W.W.W.",
|
||||
"W.W.W.W.J T ~ 7.j.j.j.g +.p.j.s.+. . . : z T v W.W.W.W.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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."
|
||||
};
|
||||
static char * qutebrowser_xpm[] = {
|
||||
"32 32 267 2",
|
||||
" c None",
|
||||
". c #9FD4FD",
|
||||
"+ c #99CBFE",
|
||||
"@ c #90C3FE",
|
||||
"# c #89BFFE",
|
||||
"$ c #81BCFF",
|
||||
"% c #80BBFF",
|
||||
"& c #9BCAFD",
|
||||
"* c #A9DBFB",
|
||||
"= c #88D3FB",
|
||||
"- c #98CBFE",
|
||||
"; c #81BBFF",
|
||||
"> c #7EBAFF",
|
||||
", c #84BDFF",
|
||||
"' c #8DC2FF",
|
||||
") c #96C7FE",
|
||||
"! c #A0CCFE",
|
||||
"~ c #A9D1FE",
|
||||
"{ c #CEE5FD",
|
||||
"] c #C7E3FC",
|
||||
"^ c #8AD3FB",
|
||||
"/ c #9DCFFD",
|
||||
"( c #C3DFFD",
|
||||
"_ c #CDE4FD",
|
||||
": c #A3CEFE",
|
||||
"< c #94C6FE",
|
||||
"[ c #CAE5FC",
|
||||
"} c #7DD0FB",
|
||||
"| c #9ECDFD",
|
||||
"1 c #A1CDFE",
|
||||
"2 c #8BC1FF",
|
||||
"3 c #87BFFF",
|
||||
"4 c #ADD4FE",
|
||||
"5 c #C6E1FD",
|
||||
"6 c #CCE3FC",
|
||||
"7 c #A7DAFB",
|
||||
"8 c #9DCBFE",
|
||||
"9 c #78AFF1",
|
||||
"0 c #6096D4",
|
||||
"a c #4B82C0",
|
||||
"b c #5A84B3",
|
||||
"c c #6589B1",
|
||||
"d c #6F92B9",
|
||||
"e c #90AED0",
|
||||
"f c #C4DBF5",
|
||||
"g c #6286AE",
|
||||
"h c #7D9EC2",
|
||||
"i c #BADFFC",
|
||||
"j c #85BDFE",
|
||||
"k c #78B4F8",
|
||||
"l c #4C83C0",
|
||||
"m c #1E4F87",
|
||||
"n c #0A396E",
|
||||
"o c #345D8D",
|
||||
"p c #CDE4FC",
|
||||
"q c #88A7CA",
|
||||
"r c #1D497C",
|
||||
"s c #799BBF",
|
||||
"t c #8AC1FD",
|
||||
"u c #5E97D7",
|
||||
"v c #14457B",
|
||||
"w c #4F76A0",
|
||||
"x c #A9D5FC",
|
||||
"y c #95C9FD",
|
||||
"z c #4C82C1",
|
||||
"A c #0A3A6F",
|
||||
"B c #C9E3FD",
|
||||
"C c #95CCFC",
|
||||
"D c #629BDB",
|
||||
"E c #0B3A6F",
|
||||
"F c #0C3B6F",
|
||||
"G c #4E749F",
|
||||
"H c #8CACCE",
|
||||
"I c #6185AD",
|
||||
"J c #CBE4FD",
|
||||
"K c #89C0FF",
|
||||
"L c #98CDFA",
|
||||
"M c #27558A",
|
||||
"N c #144175",
|
||||
"O c #9BB8D8",
|
||||
"P c #335D8C",
|
||||
"Q c #AFC9E6",
|
||||
"R c #AFD4FE",
|
||||
"S c #91C7FD",
|
||||
"T c #A0C0DE",
|
||||
"U c #194779",
|
||||
"V c #80A1C5",
|
||||
"W c #C8E1F9",
|
||||
"X c #9CB9D8",
|
||||
"Y c #7799BE",
|
||||
"Z c #6489B0",
|
||||
"` c #7092B9",
|
||||
" . c #6E9DCF",
|
||||
".. c #79B5F9",
|
||||
"+. c #83BDFE",
|
||||
"@. c #7395BA",
|
||||
"#. c #315C8B",
|
||||
"$. c #7C9EC2",
|
||||
"%. c #C0D9F3",
|
||||
"&. c #7294BA",
|
||||
"*. c #5C94D4",
|
||||
"=. c #91CCFC",
|
||||
"-. c #88CBFA",
|
||||
";. c #5179A3",
|
||||
">. c #6E91B7",
|
||||
",. c #6084AC",
|
||||
"'. c #96B3D4",
|
||||
"). c #275283",
|
||||
"!. c #0C3C71",
|
||||
"~. c #629CDC",
|
||||
"{. c #94C6FD",
|
||||
"]. c #A7D2FC",
|
||||
"^. c #36659A",
|
||||
"/. c #2C5788",
|
||||
"(. c #9DBAD9",
|
||||
"_. c #B4CEEA",
|
||||
":. c #476E9A",
|
||||
"<. c #7EB9FE",
|
||||
"[. c #8DC3FD",
|
||||
"}. c #8CC2FE",
|
||||
"|. c #2F619B",
|
||||
"1. c #87A6C9",
|
||||
"2. c #7A9BC0",
|
||||
"3. c #CBE2FB",
|
||||
"4. c #C7DFF8",
|
||||
"5. c #6C8FB5",
|
||||
"6. c #113F73",
|
||||
"7. c #0F3D71",
|
||||
"8. c #547AA4",
|
||||
"9. c #9CBAD9",
|
||||
"0. c #B9D3EE",
|
||||
"a. c #A3C0DE",
|
||||
"b. c #31629A",
|
||||
"c. c #659EE0",
|
||||
"d. c #87BFFE",
|
||||
"e. c #C3E0FD",
|
||||
"f. c #4371A4",
|
||||
"g. c #7496BB",
|
||||
"h. c #90AFD1",
|
||||
"i. c #245081",
|
||||
"j. c #416A96",
|
||||
"k. c #B0CBE7",
|
||||
"l. c #CCE4FD",
|
||||
"m. c #7DB8FD",
|
||||
"n. c #1E5088",
|
||||
"o. c #497EBC",
|
||||
"p. c #C9E3FC",
|
||||
"q. c #7193B9",
|
||||
"r. c #C6E0FB",
|
||||
"s. c #A2CDFE",
|
||||
"t. c #97C8FE",
|
||||
"u. c #A7D0FE",
|
||||
"v. c #BDDCFD",
|
||||
"w. c #9EC2E8",
|
||||
"x. c #416996",
|
||||
"y. c #366AA6",
|
||||
"z. c #C0DEFC",
|
||||
"A. c #A2BFDD",
|
||||
"B. c #326299",
|
||||
"C. c #649DDF",
|
||||
"D. c #71ABED",
|
||||
"E. c #3569A4",
|
||||
"F. c #0D3C71",
|
||||
"G. c #6998CD",
|
||||
"H. c #30639D",
|
||||
"I. c #A8D3F8",
|
||||
"J. c #2B5686",
|
||||
"K. c #3A679B",
|
||||
"L. c #ADCAEA",
|
||||
"M. c #85A6C9",
|
||||
"N. c #33639B",
|
||||
"O. c #9CCBFD",
|
||||
"P. c #86C2F7",
|
||||
"Q. c #0E3C71",
|
||||
"R. c #1B4C83",
|
||||
"S. c #5D95D5",
|
||||
"T. c #557BA5",
|
||||
"U. c #85C0F6",
|
||||
"V. c #55A8EF",
|
||||
"W. c #94B3D3",
|
||||
"X. c #1C497C",
|
||||
"Y. c #13437A",
|
||||
"Z. c #487DBB",
|
||||
"`. c #7BB7FB",
|
||||
" + c #76B1F5",
|
||||
".+ c #4E85C3",
|
||||
"++ c #ACD3FE",
|
||||
"@+ c #2F5989",
|
||||
"#+ c #7597BC",
|
||||
"$+ c #53A7EF",
|
||||
"%+ c #C6E1FC",
|
||||
"&+ c #B6D5F7",
|
||||
"*+ c #5890D0",
|
||||
"=+ c #4076B2",
|
||||
"-+ c #619ADB",
|
||||
";+ c #7CB7FC",
|
||||
">+ c #7DB9FE",
|
||||
",+ c #5087C6",
|
||||
"'+ c #134479",
|
||||
")+ c #23548D",
|
||||
"!+ c #24558D",
|
||||
"~+ c #8AAACC",
|
||||
"{+ 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/*)
|
||||
install -Dm755 -t "$(DESTDIR)/usr/share/qutebrowser/scripts/" \
|
||||
$(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/*))
|
||||
|
@ -1,5 +1,5 @@
|
||||
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.
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
certifi==2017.11.5
|
||||
chardet==3.0.4
|
||||
codecov==2.0.9
|
||||
codecov==2.0.13
|
||||
coverage==4.4.2
|
||||
idna==2.6
|
||||
requests==2.18.4
|
||||
|
@ -1,23 +1,23 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
attrs==17.3.0
|
||||
attrs==17.4.0
|
||||
flake8==3.5.0
|
||||
flake8-bugbear==17.4.0
|
||||
flake8-builtins==1.0
|
||||
flake8-bugbear==17.12.0
|
||||
flake8-builtins==1.0.post0
|
||||
flake8-comprehensions==1.4.1
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==3.0.0
|
||||
flake8-deprecated==1.3
|
||||
flake8-docstrings==1.1.0
|
||||
flake8-future-import==0.4.3
|
||||
flake8-docstrings==1.3.0
|
||||
flake8-future-import==0.4.4
|
||||
flake8-mock==0.3
|
||||
flake8-per-file-ignores==0.4
|
||||
flake8-polyfill==1.0.1
|
||||
flake8-polyfill==1.0.2
|
||||
flake8-string-format==0.2.3
|
||||
flake8-tidy-imports==1.1.0
|
||||
flake8-tuple==0.2.13
|
||||
mccabe==0.6.1
|
||||
pep8-naming==0.4.1
|
||||
pep8-naming==0.5.0
|
||||
pycodestyle==2.3.1
|
||||
pydocstyle==2.1.1
|
||||
pyflakes==1.6.0
|
||||
|
@ -3,6 +3,6 @@
|
||||
appdirs==1.4.3
|
||||
packaging==16.8
|
||||
pyparsing==2.2.0
|
||||
setuptools==38.2.1
|
||||
setuptools==38.4.0
|
||||
six==1.11.0
|
||||
wheel==0.30.0
|
||||
|
@ -1,7 +1,7 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
altgraph==0.14
|
||||
altgraph==0.15
|
||||
future==0.16.0
|
||||
macholib==1.8
|
||||
macholib==1.9
|
||||
pefile==2017.11.5
|
||||
-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
|
||||
|
||||
astroid==1.5.3
|
||||
astroid==1.6.0
|
||||
certifi==2017.11.5
|
||||
chardet==3.0.4
|
||||
github3.py==0.9.6
|
||||
@ -8,7 +8,7 @@ idna==2.6
|
||||
isort==4.2.15
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.7.4
|
||||
pylint==1.8.1
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.4
|
||||
six==1.11.0
|
||||
|
@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
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
|
||||
git+https://github.com/pytest-dev/pytest.git@features
|
||||
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-faulthandler.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
|
||||
|
||||
attrs==17.3.0
|
||||
attrs==17.4.0
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==5.10.0
|
||||
cheroot==6.0.0
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.4.2
|
||||
@ -11,29 +11,29 @@ fields==5.0.0
|
||||
Flask==0.12.2
|
||||
glob2==0.6
|
||||
hunter==2.0.2
|
||||
hypothesis==3.38.5
|
||||
hypothesis==3.44.16
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.9.6
|
||||
# Jinja2==2.10
|
||||
Mako==1.0.7
|
||||
# MarkupSafe==1.0
|
||||
parse==1.8.2
|
||||
parse-type==0.4.2
|
||||
pluggy==0.6.0
|
||||
py==1.5.2
|
||||
py-cpuinfo==3.3.0
|
||||
pytest==3.2.5
|
||||
pytest==3.3.1 # rq.filter: != 3.3.2
|
||||
pytest-bdd==2.19.0
|
||||
pytest-benchmark==3.1.1
|
||||
pytest-catchlog==1.2.2
|
||||
pytest-cov==2.5.1
|
||||
pytest-faulthandler==1.3.1
|
||||
pytest-instafail==0.3.0
|
||||
pytest-mock==1.6.3
|
||||
pytest-qt==2.3.0
|
||||
pytest-qt==2.3.1
|
||||
pytest-repeat==0.4.1
|
||||
pytest-rerunfailures==3.1
|
||||
pytest-travis-fold==1.2.0
|
||||
pytest-rerunfailures==4.0
|
||||
pytest-travis-fold==1.3.0
|
||||
pytest-xvfb==1.0.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.11.0
|
||||
vulture==0.26
|
||||
Werkzeug==0.12.2
|
||||
Werkzeug==0.14.1
|
||||
|
@ -4,10 +4,9 @@ coverage
|
||||
Flask
|
||||
hunter
|
||||
hypothesis
|
||||
pytest
|
||||
pytest==3.3.1
|
||||
pytest-bdd
|
||||
pytest-benchmark
|
||||
pytest-catchlog
|
||||
pytest-cov
|
||||
pytest-faulthandler
|
||||
pytest-instafail
|
||||
@ -20,3 +19,4 @@ pytest-xvfb
|
||||
vulture
|
||||
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
#@ filter: pytest != 3.3.2
|
||||
|
@ -2,5 +2,6 @@
|
||||
|
||||
pluggy==0.6.0
|
||||
py==1.5.2
|
||||
six==1.11.0
|
||||
tox==2.9.1
|
||||
virtualenv==15.1.0
|
||||
|
@ -144,7 +144,7 @@ fi
|
||||
pkill -f "${program_}"
|
||||
|
||||
# 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=$!
|
||||
|
||||
msg info "Casting $1" >> "$QUTE_FIFO"
|
||||
@ -153,4 +153,4 @@ tail -F "${file_to_cast}" | ${program_} -
|
||||
|
||||
# cleanup remaining background process and file on disk
|
||||
kill ${ytdl_pid}
|
||||
rm -rf ${tmpdir}
|
||||
rm -rf "${tmpdir}"
|
||||
|
@ -41,7 +41,7 @@
|
||||
[ -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 "$url" | sed -E 's/[^ ]+ +//g' | egrep "https?:" || echo "$url")
|
||||
url=$(echo "$url" | sed -E 's/[^ ]+ +//g' | grep -E "https?:" || echo "$url")
|
||||
|
||||
[ -z "${url// }" ] && exit
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -euo pipefail
|
||||
#
|
||||
# Behavior:
|
||||
# Userscript for qutebrowser which will take the raw JSON text of the current
|
||||
@ -19,29 +20,23 @@
|
||||
#
|
||||
# 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
|
||||
STYLE=${1:-monokai}
|
||||
# format json using jq
|
||||
FORMATTED_JSON="$(cat "$QUTE_TEXT" | jq '.')"
|
||||
|
||||
# if jq command failed or formatted json is empty, assume failure and terminate
|
||||
if [ $? -ne 0 ] || [ -z "$FORMATTED_JSON" ]; then
|
||||
echo "Invalid json, aborting..."
|
||||
exit 1
|
||||
TEMP_FILE="$(mktemp)"
|
||||
jq . "$QUTE_TEXT" >"$TEMP_FILE"
|
||||
|
||||
# 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
|
||||
|
||||
# calculate the filesize of the json document
|
||||
FILE_SIZE=$(ls -s --block-size=1048576 "$QUTE_TEXT" | cut -d' ' -f1)
|
||||
|
||||
# use pygments to pretty-up the json (syntax highlight) if file is less than 10MB
|
||||
if [ "$FILE_SIZE" -lt "10" ]; then
|
||||
FORMATTED_JSON="$(echo "$FORMATTED_JSON" | pygmentize -l json -f html -O full,style=$STYLE)"
|
||||
fi
|
||||
|
||||
# create a temp file and write the formatted json to that file
|
||||
TEMP_FILE="$(mktemp --suffix '.html')"
|
||||
echo "$FORMATTED_JSON" > $TEMP_FILE
|
||||
|
||||
|
||||
# send the command to qutebrowser to open the new file containing the formatted json
|
||||
echo "open -t file://$TEMP_FILE" >> "$QUTE_FIFO"
|
||||
|
@ -76,6 +76,7 @@ crop-first-column() {
|
||||
ls-files() {
|
||||
# add the slash at the end of the download dir enforces to follow the
|
||||
# symlink, if the DOWNLOAD_DIR itself is a symlink
|
||||
# shellcheck disable=SC2010
|
||||
ls -Q --quoting-style escape -h -o -1 -A -t "${DOWNLOAD_DIR}/" \
|
||||
| grep '^[-]' \
|
||||
| cut -d' ' -f3- \
|
||||
@ -91,10 +92,10 @@ if [ "${#entries[@]}" -eq 0 ] ; then
|
||||
die "Download directory »${DOWNLOAD_DIR}« empty"
|
||||
fi
|
||||
|
||||
line=$(printf "%s\n" "${entries[@]}" \
|
||||
line=$(printf '%s\n' "${entries[@]}" \
|
||||
| crop-first-column 55 \
|
||||
| column -s $'\t' -t \
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" $ROFI_ARGS) || true
|
||||
| $ROFI_CMD "${rofi_default_args[@]}" "$ROFI_ARGS") || true
|
||||
if [ -z "$line" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
@ -64,7 +64,7 @@ die() {
|
||||
javascript_escape() {
|
||||
# print the first argument in an escaped way, such that it can safely
|
||||
# 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
|
||||
file="${files[0]}"
|
||||
else
|
||||
file=$( printf "%s\n" "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||
file=$( printf '%s\n' "${files[@]}" | "${MENU_COMMAND[@]}" )
|
||||
fi
|
||||
}
|
||||
|
||||
@ -236,7 +236,7 @@ pass_backend() {
|
||||
if ((match_line)) ; then
|
||||
# add entries with matching URL-tag
|
||||
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
|
||||
then
|
||||
passfile="${passfile#$PREFIX}"
|
||||
@ -269,7 +269,7 @@ pass_backend() {
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done < <($GPG "${GPG_OPTS}" -d "$path" )
|
||||
done < <($GPG "${GPG_OPTS[@]}" -d "$path" )
|
||||
}
|
||||
}
|
||||
# =======================================================
|
||||
@ -283,8 +283,8 @@ secret_backend() {
|
||||
query_entries() {
|
||||
local domain="$1"
|
||||
while read -r line ; do
|
||||
if [[ "$line" =~ "attribute.username = " ]] ; then
|
||||
files+=("$domain ${line#${BASH_REMATCH[0]}}")
|
||||
if [[ "$line" == "attribute.username = "* ]] ; then
|
||||
files+=("$domain ${line:21}")
|
||||
fi
|
||||
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/}
|
||||
PWFILL_CONFIG=${PWFILL_CONFIG:-${QUTE_CONFIG_DIR}/password_fill_rc}
|
||||
if [ -f "$PWFILL_CONFIG" ] ; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$PWFILL_CONFIG"
|
||||
fi
|
||||
init
|
||||
@ -311,7 +312,7 @@ simplify_url "$QUTE_URL"
|
||||
query_entries "${simple_url}"
|
||||
no_entries_found
|
||||
# remove duplicates
|
||||
mapfile -t files < <(printf "%s\n" "${files[@]}" | sort | uniq )
|
||||
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort | uniq )
|
||||
choose_entry
|
||||
if [ -z "$file" ] ; then
|
||||
# choose_entry didn't want any of these entries
|
||||
|
@ -35,17 +35,12 @@ get_selection() {
|
||||
|
||||
# Main
|
||||
# https://github.com/halfwit/dotfiles/blob/master/.config/dmenu/font
|
||||
if [[ -s $confdir/dmenu/font ]]; then
|
||||
read -r font < "$confdir"/dmenu/font
|
||||
fi
|
||||
[[ -s $confdir/dmenu/font ]] && read -r font < "$confdir"/dmenu/font
|
||||
|
||||
if [[ $font ]]; then
|
||||
opts+=(-fn "$font")
|
||||
fi
|
||||
[[ $font ]] && opts+=(-fn "$font")
|
||||
|
||||
if [[ -s $optsfile ]]; then
|
||||
source "$optsfile"
|
||||
fi
|
||||
# shellcheck source=/dev/null
|
||||
[[ -s $optsfile ]] && source "$optsfile"
|
||||
|
||||
url=$(get_selection)
|
||||
url=${url/*http/http}
|
||||
|
@ -32,7 +32,7 @@ add_feed () {
|
||||
if grep -Fq "$1" "feeds"; then
|
||||
notice "$1 is saved already."
|
||||
else
|
||||
printf "%s\n" "$1" >> "feeds"
|
||||
printf '%s\n' "$1" >> "feeds"
|
||||
fi
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ notice () {
|
||||
|
||||
# Update a database of a feed and open new URLs
|
||||
read_items () {
|
||||
cd read_urls
|
||||
cd read_urls || return 1
|
||||
feed_file="$(echo "$1" | tr -d /)"
|
||||
feed_temp_file="$(mktemp "$feed_file.tmp.XXXXXXXXXX")"
|
||||
feed_new_items="$(mktemp "$feed_file.new.XXXXXXXXXX")"
|
||||
@ -75,7 +75,7 @@ read_items () {
|
||||
cat "$feed_new_items" >> "$feed_file"
|
||||
sort -o "$feed_file" "$feed_file"
|
||||
rm "$feed_temp_file" "$feed_new_items"
|
||||
fi | while read item; do
|
||||
fi | while read -r item; do
|
||||
echo "open -t $item" > "$QUTE_FIFO"
|
||||
done
|
||||
}
|
||||
@ -85,7 +85,7 @@ if [ ! -d "$config_dir/read_urls" ]; then
|
||||
mkdir -p "$config_dir/read_urls"
|
||||
fi
|
||||
|
||||
cd "$config_dir"
|
||||
cd "$config_dir" || exit 1
|
||||
|
||||
if [ $# != 0 ]; then
|
||||
for arg in "$@"; do
|
||||
@ -115,7 +115,7 @@ if < /dev/null grep --help 2>&1 | grep -q -- -a; then
|
||||
text_only="-a"
|
||||
fi
|
||||
|
||||
while read feed_url; do
|
||||
while read -r feed_url; do
|
||||
read_items "$feed_url" &
|
||||
done < "$config_dir/feeds"
|
||||
|
||||
|
@ -25,12 +25,10 @@
|
||||
[[ $QUTE_MODE == 'hints' ]] && title=$QUTE_SELECTED_TEXT || title=$QUTE_TITLE
|
||||
|
||||
# try to add the task and grab the output
|
||||
msg="$(task add $title $@ 2>&1)"
|
||||
|
||||
if [[ $? == 0 ]]; then
|
||||
if msg="$(task add "$title" "$*" 2>&1)"; then
|
||||
# annotate the new task with the url, send the output back to the browser
|
||||
task +LATEST annotate "$QUTE_URL"
|
||||
echo "message-info '$msg'" >> $QUTE_FIFO
|
||||
echo "message-info '$msg'" >> "$QUTE_FIFO"
|
||||
else
|
||||
echo "message-error '$msg'" >> $QUTE_FIFO
|
||||
echo "message-error '$msg'" >> "$QUTE_FIFO"
|
||||
fi
|
||||
|
@ -50,7 +50,7 @@ msg() {
|
||||
MPV_COMMAND=${MPV_COMMAND:-mpv}
|
||||
# Warning: spaces in single flags are not supported
|
||||
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl --ytdl-raw-options=yes-playlist=}
|
||||
video_command=( "$MPV_COMMAND" $MPV_FLAGS )
|
||||
IFS=" " read -r -a video_command <<< "$MPV_COMMAND $MPV_FLAGS"
|
||||
|
||||
js() {
|
||||
cat <<EOF
|
||||
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 0, 4)
|
||||
__version_info__ = (1, 1, 0)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__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.config import config, websettings, configfiles, configinit
|
||||
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
|
||||
downloads)
|
||||
downloads, greasemonkey)
|
||||
from qutebrowser.browser.network import proxy
|
||||
from qutebrowser.browser.webkit import cookies, cache
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
@ -491,6 +491,9 @@ def _init_modules(args, crash_handler):
|
||||
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
|
||||
objreg.register('cache', diskcache)
|
||||
|
||||
log.init.debug("Initializing Greasemonkey...")
|
||||
greasemonkey.init()
|
||||
|
||||
log.init.debug("Misc initialization...")
|
||||
macros.init()
|
||||
# Init backend-specific stuff
|
||||
@ -561,8 +564,8 @@ class Quitter:
|
||||
cwd = os.path.abspath(os.path.dirname(sys.executable))
|
||||
else:
|
||||
args = [sys.executable, '-m', 'qutebrowser']
|
||||
cwd = os.path.join(os.path.abspath(os.path.dirname(
|
||||
qutebrowser.__file__)), '..')
|
||||
cwd = os.path.join(
|
||||
os.path.abspath(os.path.dirname(qutebrowser.__file__)), '..')
|
||||
if not os.path.isdir(cwd):
|
||||
# Probably running from a python egg. Let's fallback to
|
||||
# cwd=None and see if that works out.
|
||||
@ -869,10 +872,6 @@ class EventFilter(QObject):
|
||||
super().__init__(parent)
|
||||
self._activated = True
|
||||
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.KeyRelease: self._handle_key_event,
|
||||
}
|
||||
@ -897,19 +896,6 @@ class EventFilter(QObject):
|
||||
# No window available yet, or not a MainWindow
|
||||
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):
|
||||
"""Handle an event.
|
||||
|
||||
|
@ -487,6 +487,7 @@ class AbstractHistory:
|
||||
raise NotImplementedError
|
||||
|
||||
def back(self, count=1):
|
||||
"""Go back in the tab's history."""
|
||||
idx = self.current_idx() - count
|
||||
if idx >= 0:
|
||||
self._go_to_item(self._item_at(idx))
|
||||
@ -495,6 +496,7 @@ class AbstractHistory:
|
||||
raise WebTabError("At beginning of history.")
|
||||
|
||||
def forward(self, count=1):
|
||||
"""Go forward in the tab's history."""
|
||||
idx = self.current_idx() + count
|
||||
if idx < len(self):
|
||||
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
|
||||
# it's certainly better than a segfault.
|
||||
if getattr(evt, 'posted', False):
|
||||
raise AssertionError("Can't re-use an event which was already "
|
||||
"posted!")
|
||||
raise utils.Unreachable("Can't re-use an event which was already "
|
||||
"posted!")
|
||||
recipient = self.event_target()
|
||||
evt.posted = True
|
||||
QApplication.postEvent(recipient, evt)
|
||||
|
@ -39,7 +39,7 @@ from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
|
||||
webelem, downloads)
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
|
||||
objreg, utils, debug, standarddir)
|
||||
objreg, utils, standarddir)
|
||||
from qutebrowser.utils.usertypes import KeyMode
|
||||
from qutebrowser.misc import editor, guiprocess
|
||||
from qutebrowser.completion.models import urlmodel, miscmodels
|
||||
@ -518,7 +518,7 @@ class CommandDispatcher:
|
||||
return newtab
|
||||
|
||||
@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):
|
||||
"""Take a tab from another window.
|
||||
|
||||
@ -673,7 +673,7 @@ class CommandDispatcher:
|
||||
self._open(new_url, tab, bg, window, related=True)
|
||||
else: # pragma: no cover
|
||||
raise ValueError("Got called with invalid value {} for "
|
||||
"`where'.".format(where))
|
||||
"`where'.".format(where))
|
||||
except navigate.Error as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
@ -953,22 +953,25 @@ class CommandDispatcher:
|
||||
(prev and i < cur_idx) or
|
||||
(next_ and i > cur_idx))
|
||||
|
||||
# Check to see if we are closing any pinned tabs
|
||||
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
|
||||
|
||||
# close as many tabs as we can
|
||||
first_tab = True
|
||||
pinned_tabs_cleanup = False
|
||||
for i, tab in enumerate(self._tabbed_browser.widgets()):
|
||||
if _to_close(i):
|
||||
self._tabbed_browser.close_tab(tab, new_undo=first_tab)
|
||||
first_tab = False
|
||||
if force or not tab.data.pinned:
|
||||
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')
|
||||
def undo(self):
|
||||
@ -1177,7 +1180,8 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
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.
|
||||
|
||||
Args:
|
||||
@ -1188,6 +1192,7 @@ class CommandDispatcher:
|
||||
(or `$XDG_DATA_DIR`)
|
||||
- `/usr/share/qutebrowser/userscripts`
|
||||
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.
|
||||
cmdline: The commandline to execute.
|
||||
"""
|
||||
@ -1214,6 +1219,11 @@ class CommandDispatcher:
|
||||
else:
|
||||
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')
|
||||
def home(self):
|
||||
"""Open main startpage in current tab."""
|
||||
@ -1418,27 +1428,14 @@ class CommandDispatcher:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
@cmdutils.argument('dest_old', hide=True)
|
||||
def download(self, url=None, dest_old=None, *, mhtml_=False, dest=None):
|
||||
def download(self, url=None, *, mhtml_=False, dest=None):
|
||||
"""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:
|
||||
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.
|
||||
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?
|
||||
download_manager = objreg.get('qtnetwork-download-manager',
|
||||
scope='window', window=self._win_id)
|
||||
@ -1528,6 +1525,7 @@ class CommandDispatcher:
|
||||
dest = os.path.expanduser(dest)
|
||||
|
||||
def callback(data):
|
||||
"""Write the data to disk."""
|
||||
try:
|
||||
with open(dest, 'w', encoding='utf-8') as f:
|
||||
f.write(data)
|
||||
@ -1649,6 +1647,8 @@ class CommandDispatcher:
|
||||
except webelem.Error as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
|
||||
mainwindow.raise_window(objreg.last_focused_window(), alert=False)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', maxsplit=0,
|
||||
scope='window')
|
||||
def insert_text(self, text):
|
||||
@ -1742,7 +1742,8 @@ class CommandDispatcher:
|
||||
elif going_up and tab.scroller.pos_px().y() > old_scroll_pos.y():
|
||||
message.info("Search hit TOP, continuing at BOTTOM")
|
||||
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',
|
||||
maxsplit=0)
|
||||
@ -1762,7 +1763,7 @@ class CommandDispatcher:
|
||||
return
|
||||
|
||||
options = {
|
||||
'ignore_case': config.val.ignore_case,
|
||||
'ignore_case': config.val.search.ignore_case,
|
||||
'reverse': reverse,
|
||||
}
|
||||
|
||||
@ -2045,6 +2046,7 @@ class CommandDispatcher:
|
||||
jseval_cb = None
|
||||
else:
|
||||
def jseval_cb(out):
|
||||
"""Show the data returned from JS."""
|
||||
if out is None:
|
||||
# Getting the actual error (if any) seems to be difficult.
|
||||
# The error does end up in
|
||||
@ -2197,11 +2199,4 @@ class CommandDispatcher:
|
||||
return
|
||||
|
||||
window = self._tabbed_browser.window()
|
||||
if window.isFullScreen():
|
||||
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)))
|
||||
window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)
|
||||
|
@ -103,6 +103,8 @@ def immediate_download_path(prompt_download_directory=None):
|
||||
if not prompt_download_directory:
|
||||
return download_dir()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _path_suggestion(filename):
|
||||
"""Get the suggested file path.
|
||||
@ -180,7 +182,7 @@ def transform_path(path):
|
||||
path = utils.expand_windows_drive(path)
|
||||
# Drive dependent working directories are not supported, e.g.
|
||||
# E:filename is invalid
|
||||
if re.match(r'[A-Z]:[^\\]', path, re.IGNORECASE):
|
||||
if re.search(r'^[A-Z]:[^\\]', path, re.IGNORECASE):
|
||||
return None
|
||||
# Paths like COM1, ...
|
||||
# See https://github.com/qutebrowser/qutebrowser/issues/82
|
||||
@ -990,7 +992,7 @@ class DownloadModel(QAbstractListModel):
|
||||
if not count:
|
||||
count = len(self)
|
||||
raise cmdexc.CommandError("Download {} is already done!"
|
||||
.format(count))
|
||||
.format(count))
|
||||
download.cancel()
|
||||
|
||||
@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):
|
||||
"""Clean up after hinting."""
|
||||
# pylint: disable=not-an-iterable
|
||||
for label in self._context.all_labels:
|
||||
label.cleanup()
|
||||
# pylint: enable=not-an-iterable
|
||||
|
||||
text = self._get_text()
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
@ -621,8 +619,9 @@ class HintManager(QObject):
|
||||
@cmdutils.register(instance='hintmanager', scope='tab', name='hint',
|
||||
star_args_optional=True, maxsplit=2)
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def start(self, rapid=False, group=webelem.Group.all, target=Target.normal,
|
||||
*args, win_id, mode=None, add_history=False):
|
||||
def start(self, # pylint: disable=keyword-arg-before-vararg
|
||||
group=webelem.Group.all, target=Target.normal,
|
||||
*args, win_id, mode=None, add_history=False, rapid=False):
|
||||
"""Start hinting.
|
||||
|
||||
Args:
|
||||
@ -809,7 +808,6 @@ class HintManager(QObject):
|
||||
log.hints.debug("Filtering hints on {!r}".format(filterstr))
|
||||
|
||||
visible = []
|
||||
# pylint: disable=not-an-iterable
|
||||
for label in self._context.all_labels:
|
||||
try:
|
||||
if self._filter_matches(filterstr, str(label.elem)):
|
||||
@ -821,7 +819,6 @@ class HintManager(QObject):
|
||||
label.hide()
|
||||
except webelem.Error:
|
||||
pass
|
||||
# pylint: enable=not-an-iterable
|
||||
|
||||
if not visible:
|
||||
# Whoops, filtered all hints
|
||||
|
@ -32,7 +32,7 @@ from qutebrowser.misc import objects, sql
|
||||
|
||||
|
||||
# increment to indicate that HistoryCompletion must be regenerated
|
||||
_USER_VERSION = 1
|
||||
_USER_VERSION = 2
|
||||
|
||||
|
||||
class CompletionHistory(sql.SqlTable):
|
||||
@ -102,7 +102,8 @@ class WebHistory(sql.SqlTable):
|
||||
data = {'url': [], 'title': [], 'last_atime': []}
|
||||
# select the latest entry for each url
|
||||
q = sql.Query('SELECT url, title, max(atime) AS atime FROM History '
|
||||
'WHERE NOT redirect GROUP BY url ORDER BY atime asc')
|
||||
'WHERE NOT redirect and url NOT LIKE "qute://back%" '
|
||||
'GROUP BY url ORDER BY atime asc')
|
||||
for entry in q.run():
|
||||
data['url'].append(self._format_completion_url(QUrl(entry.url)))
|
||||
data['title'].append(entry.title)
|
||||
@ -149,8 +150,8 @@ class WebHistory(sql.SqlTable):
|
||||
if force:
|
||||
self._do_clear()
|
||||
else:
|
||||
message.confirm_async(self._do_clear, title="Clear all browsing "
|
||||
"history?")
|
||||
message.confirm_async(yes_action=self._do_clear,
|
||||
title="Clear all browsing history?")
|
||||
|
||||
def _do_clear(self):
|
||||
with self._handle_sql_errors():
|
||||
@ -171,7 +172,9 @@ class WebHistory(sql.SqlTable):
|
||||
@pyqtSlot(QUrl, QUrl, str)
|
||||
def add_from_tab(self, url, requested_url, title):
|
||||
"""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
|
||||
if url.isEmpty():
|
||||
# things set via setHtml
|
||||
@ -268,6 +271,7 @@ class WebHistory(sql.SqlTable):
|
||||
return
|
||||
|
||||
def action():
|
||||
"""Actually run the import."""
|
||||
with debug.log_time(log.init, 'Import old history file to sqlite'):
|
||||
try:
|
||||
self._read(path)
|
||||
@ -340,7 +344,7 @@ class WebHistory(sql.SqlTable):
|
||||
f.write('\n'.join(lines))
|
||||
message.info("Dumped history to {}".format(dest))
|
||||
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):
|
||||
|
@ -94,6 +94,7 @@ class AbstractWebInspector(QWidget):
|
||||
raise NotImplementedError
|
||||
|
||||
def toggle(self, page):
|
||||
"""Show/hide the inspector."""
|
||||
if self._widget.isVisible():
|
||||
self.hide()
|
||||
else:
|
||||
|
@ -19,15 +19,13 @@
|
||||
|
||||
"""Mouse handling for a browser tab."""
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes
|
||||
from qutebrowser.keyinput import modeman
|
||||
|
||||
|
||||
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
|
||||
|
||||
|
||||
class ChildEventFilter(QObject):
|
||||
|
||||
"""An event filter re-adding MouseEventFilter on ChildEvent.
|
||||
|
@ -59,6 +59,7 @@ def _js_slot(*args):
|
||||
def _decorator(method):
|
||||
@functools.wraps(method)
|
||||
def new_method(self, *args, **kwargs):
|
||||
"""Call the underlying function."""
|
||||
try:
|
||||
return method(self, *args, **kwargs)
|
||||
except:
|
||||
|
@ -82,7 +82,7 @@ def fix_urls(asset):
|
||||
('viewer.css', 'qute://pdfjs/web/viewer.css'),
|
||||
('compatibility.js', 'qute://pdfjs/web/compatibility.js'),
|
||||
('locale/locale.properties',
|
||||
'qute://pdfjs/web/locale/locale.properties'),
|
||||
'qute://pdfjs/web/locale/locale.properties'),
|
||||
('l10n.js', 'qute://pdfjs/web/l10n.js'),
|
||||
('../build/pdf.js', 'qute://pdfjs/build/pdf.js'),
|
||||
('debugger.js', 'qute://pdfjs/web/debugger.js'),
|
||||
|
@ -303,8 +303,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
"""Handle QNetworkReply errors."""
|
||||
if code == QNetworkReply.OperationCanceledError:
|
||||
return
|
||||
else:
|
||||
self._die(self._reply.errorString())
|
||||
self._die(self._reply.errorString())
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_read_timer_timeout(self):
|
||||
@ -399,7 +398,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
|
||||
"""
|
||||
if not url.isValid():
|
||||
urlutils.invalid_url_error(url, "start download")
|
||||
return
|
||||
return None
|
||||
req = QNetworkRequest(url)
|
||||
if user_agent is not None:
|
||||
req.setHeader(QNetworkRequest.UserAgentHeader, user_agent)
|
||||
|
@ -29,6 +29,7 @@ import os
|
||||
import time
|
||||
import textwrap
|
||||
import mimetypes
|
||||
import urllib
|
||||
|
||||
import pkg_resources
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
@ -41,6 +42,7 @@ from qutebrowser.misc import objects
|
||||
|
||||
|
||||
pyeval_output = ":pyeval was never called"
|
||||
spawn_output = ":spawn was never called"
|
||||
|
||||
|
||||
_HANDLERS = {}
|
||||
@ -111,6 +113,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
return function
|
||||
|
||||
def wrapper(self, *args, **kwargs):
|
||||
"""Call the underlying function."""
|
||||
if self._backend is not None and objects.backend != self._backend:
|
||||
return self.wrong_backend_handler(*args, **kwargs)
|
||||
else:
|
||||
@ -136,7 +139,7 @@ def data_for_url(url):
|
||||
A (mimetype, data) tuple.
|
||||
"""
|
||||
norm_url = url.adjusted(QUrl.NormalizePathSegments |
|
||||
QUrl.StripTrailingSlash)
|
||||
QUrl.StripTrailingSlash)
|
||||
if norm_url != url:
|
||||
raise Redirect(norm_url)
|
||||
|
||||
@ -267,6 +270,13 @@ def qute_pyeval(_url):
|
||||
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('verizon')
|
||||
def qute_version(_url):
|
||||
@ -425,6 +435,18 @@ def qute_settings(url):
|
||||
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')
|
||||
def qute_configdiff(url):
|
||||
"""Handler for qute://configdiff."""
|
||||
@ -433,7 +455,7 @@ def qute_configdiff(url):
|
||||
return 'text/html', configdiff.get_diff()
|
||||
except OSError as e:
|
||||
error = (b'Failed to read old config: ' +
|
||||
str(e.strerror).encode('utf-8'))
|
||||
str(e.strerror).encode('utf-8'))
|
||||
return 'text/plain', error
|
||||
else:
|
||||
data = config.instance.dump_userconfig().encode('utf-8')
|
||||
|
@ -22,7 +22,7 @@
|
||||
import html
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -182,7 +182,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
return False
|
||||
else:
|
||||
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):
|
||||
|
@ -26,8 +26,8 @@ to a file on shutdown, so it makes sense to keep them as strings here.
|
||||
"""
|
||||
|
||||
import os
|
||||
import html
|
||||
import os.path
|
||||
import html
|
||||
import functools
|
||||
import collections
|
||||
|
||||
|
@ -42,7 +42,9 @@ Group = enum.Enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
|
||||
SELECTORS = {
|
||||
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
|
||||
'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.images: 'img',
|
||||
Group.url: '[src], [href]',
|
||||
@ -60,7 +62,7 @@ class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OrphanedError(Exception):
|
||||
class OrphanedError(Error):
|
||||
|
||||
"""Raised when a webelement's parent has vanished."""
|
||||
|
||||
|
@ -30,7 +30,7 @@ from qutebrowser.utils import log
|
||||
def version(filename):
|
||||
"""Extract the version number from the dictionary file name."""
|
||||
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:
|
||||
raise ValueError('the given dictionary file name is malformed: {}'
|
||||
.format(filename))
|
||||
|
@ -211,11 +211,11 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
def _click_js(self, _click_target):
|
||||
# FIXME:qtwebengine Have a proper API for this
|
||||
# pylint: disable=protected-access
|
||||
settings = self._tab._widget.settings()
|
||||
view = self._tab._widget
|
||||
# pylint: enable=protected-access
|
||||
attribute = QWebEngineSettings.JavascriptCanOpenWindows
|
||||
could_open_windows = settings.testAttribute(attribute)
|
||||
settings.setAttribute(attribute, True)
|
||||
could_open_windows = view.settings().testAttribute(attribute)
|
||||
view.settings().setAttribute(attribute, True)
|
||||
|
||||
# Get QtWebEngine do apply the settings
|
||||
# (it does so with a 0ms QTimer...)
|
||||
@ -226,6 +226,12 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
QEventLoop.ExcludeUserInputEvents)
|
||||
|
||||
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)
|
||||
|
@ -244,6 +244,43 @@ def _init_profiles():
|
||||
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):
|
||||
"""Initialize the global QWebSettings."""
|
||||
if args.enable_webengine_inspector:
|
||||
|
@ -24,7 +24,8 @@ import functools
|
||||
import html as html_utils
|
||||
|
||||
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.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
@ -69,6 +70,10 @@ def init():
|
||||
download_manager.install(webenginesettings.private_profile)
|
||||
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.
|
||||
_JS_WORLD_MAP = {
|
||||
@ -121,18 +126,35 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
|
||||
|
||||
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):
|
||||
super().__init__(parent)
|
||||
self._flags = QWebEnginePage.FindFlags(0)
|
||||
self._pending_searches = 0
|
||||
|
||||
def _find(self, text, flags, callback, caller):
|
||||
"""Call findText on the widget."""
|
||||
self.search_displayed = True
|
||||
self._pending_searches += 1
|
||||
|
||||
def wrapped_callback(found):
|
||||
"""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"
|
||||
if flags:
|
||||
flag_text = 'with flags {}'.format(debug.qflags_key(
|
||||
@ -518,7 +540,15 @@ class WebEngineElements(browsertab.AbstractElements):
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
@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):
|
||||
view = self._widget
|
||||
page = view.page()
|
||||
@ -780,9 +828,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
page.linkHovered.connect(self.link_hovered)
|
||||
page.loadProgress.connect(self._on_load_progress)
|
||||
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.authenticationRequired.connect(self._on_authentication_required)
|
||||
page.proxyAuthenticationRequired.connect(
|
||||
@ -795,6 +840,19 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
view.renderProcessTerminated.connect(
|
||||
self._on_render_process_terminated)
|
||||
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):
|
||||
return self._widget.focusProxy()
|
||||
|
@ -23,12 +23,14 @@ import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
||||
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.webengine import certificateerror, webenginesettings
|
||||
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):
|
||||
@ -135,6 +137,7 @@ class WebEnginePage(QWebEnginePage):
|
||||
self._theme_color = theme_color
|
||||
self._set_bg_color()
|
||||
config.instance.changed.connect(self._set_bg_color)
|
||||
self.urlChanged.connect(self._inject_userjs)
|
||||
|
||||
@config.change_filter('colors.webpage.bg')
|
||||
def _set_bg_color(self):
|
||||
@ -300,3 +303,43 @@ class WebEnginePage(QWebEnginePage):
|
||||
message.error(msg)
|
||||
return False
|
||||
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
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.browser.webkit import rfc6266
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
|
||||
|
||||
def parse_content_disposition(reply):
|
||||
"""Parse a content_disposition header.
|
||||
@ -57,9 +57,7 @@ def parse_content_disposition(reply):
|
||||
is_inline = content_disposition.is_inline()
|
||||
# Then try to get filename from url
|
||||
if not filename:
|
||||
path = reply.url().path()
|
||||
if path is not None:
|
||||
filename = path.rstrip('/')
|
||||
filename = reply.url().path().rstrip('/')
|
||||
# If that fails as well, use a fallback
|
||||
if not filename:
|
||||
filename = 'qutebrowser-download'
|
||||
|
@ -132,5 +132,6 @@ class FileSchemeHandler(schemehandler.SchemeHandler):
|
||||
data = dirbrowser_html(path)
|
||||
return networkreply.FixedDataNetworkReply(
|
||||
request, data, 'text/html', self.parent())
|
||||
return None
|
||||
except UnicodeEncodeError:
|
||||
return None
|
||||
|
@ -270,6 +270,7 @@ class _ContentDisposition:
|
||||
elif 'filename' in self.assocs:
|
||||
# XXX Reject non-ascii (parsed via qdtext) here?
|
||||
return self.assocs['filename']
|
||||
return None
|
||||
|
||||
def is_inline(self):
|
||||
"""Return if the file should be handled inline.
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
"""Wrapper over our (QtWebKit) WebView."""
|
||||
|
||||
import re
|
||||
import functools
|
||||
import xml.etree.ElementTree
|
||||
|
||||
@ -541,10 +542,15 @@ class WebKitElements(browsertab.AbstractElements):
|
||||
|
||||
def find_id(self, elem_id, callback):
|
||||
def find_id_cb(elems):
|
||||
"""Call the real callback with the found elements."""
|
||||
if not elems:
|
||||
callback(None)
|
||||
else:
|
||||
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)
|
||||
|
||||
def find_focused(self, callback):
|
||||
|
@ -86,6 +86,21 @@ class BrowserPage(QWebPage):
|
||||
self.on_save_frame_state_requested)
|
||||
self.restoreFrameStateRequested.connect(
|
||||
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):
|
||||
"""Override javaScriptPrompt to use qutebrowser prompts."""
|
||||
@ -283,6 +298,38 @@ class BrowserPage(QWebPage):
|
||||
else:
|
||||
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')
|
||||
def _on_feature_permission_requested(self, frame, feature):
|
||||
"""Ask the user for approval for geolocation/notifications."""
|
||||
|
@ -190,6 +190,7 @@ class Command:
|
||||
return True
|
||||
elif arg_info.win_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _inspect_func(self):
|
||||
"""Inspect the function to get useful informations from it.
|
||||
|
@ -126,7 +126,7 @@ class CommandParser:
|
||||
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.
|
||||
|
||||
If the first command in the commandline is a non-split one, it only
|
||||
|
@ -35,6 +35,7 @@ class CompletionInfo:
|
||||
|
||||
config = attr.ib()
|
||||
keyconf = attr.ib()
|
||||
win_id = attr.ib()
|
||||
|
||||
|
||||
class Completer(QObject):
|
||||
@ -43,6 +44,7 @@ class Completer(QObject):
|
||||
|
||||
Attributes:
|
||||
_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.
|
||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||
updates.
|
||||
@ -50,9 +52,10 @@ class Completer(QObject):
|
||||
_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)
|
||||
self._cmd = cmd
|
||||
self._win_id = win_id
|
||||
self._timer = QTimer()
|
||||
self._timer.setSingleShot(True)
|
||||
self._timer.setInterval(0)
|
||||
@ -84,8 +87,6 @@ class Completer(QObject):
|
||||
# cursor on a flag or after an explicit split (--)
|
||||
return None
|
||||
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:
|
||||
# '|' or 'set|'
|
||||
log.completion.debug('Starting command completion')
|
||||
@ -96,6 +97,9 @@ class Completer(QObject):
|
||||
log.completion.debug("No completion for unknown command: {}"
|
||||
.format(before_cursor[0]))
|
||||
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
|
||||
try:
|
||||
func = cmd.get_pos_arg_info(argpos).completion
|
||||
@ -131,9 +135,7 @@ class Completer(QObject):
|
||||
return [], '', []
|
||||
parser = runners.CommandParser()
|
||||
result = parser.parse(text, fallback=True, keep=True)
|
||||
# pylint: disable=not-an-iterable
|
||||
parts = [x for x in result.cmdline if x]
|
||||
# pylint: enable=not-an-iterable
|
||||
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
|
||||
pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
|
||||
log.completion.debug('partitioning {} around position {}'.format(parts,
|
||||
@ -152,8 +154,7 @@ class Completer(QObject):
|
||||
"partitioned: {} '{}' {}".format(prefix, center, postfix))
|
||||
return prefix, center, postfix
|
||||
|
||||
# We should always return above
|
||||
assert False, parts
|
||||
raise utils.Unreachable("Not all parts consumed: {}".format(parts))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_selection_changed(self, text):
|
||||
@ -206,7 +207,7 @@ class Completer(QObject):
|
||||
log.completion.debug("Ignoring update because the length of "
|
||||
"the text is less than completion.min_chars.")
|
||||
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 "
|
||||
"changes.")
|
||||
else:
|
||||
@ -247,10 +248,11 @@ class Completer(QObject):
|
||||
if func != self._last_completion_func:
|
||||
self._last_completion_func = func
|
||||
args = (x for x in before_cursor[1:] if not x.startswith('-'))
|
||||
with debug.log_time(log.completion,
|
||||
'Starting {} completion'.format(func.__name__)):
|
||||
with debug.log_time(log.completion, 'Starting {} completion'
|
||||
.format(func.__name__)):
|
||||
info = CompletionInfo(config=config.instance,
|
||||
keyconf=config.key_instance)
|
||||
keyconf=config.key_instance,
|
||||
win_id=self._win_id)
|
||||
model = func(*args, info=info)
|
||||
with debug.log_time(log.completion, 'Set completion model'):
|
||||
completion.set_model(model)
|
||||
|
@ -138,10 +138,10 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
|
||||
self._painter.translate(text_rect.left(), text_rect.top())
|
||||
self._get_textdoc(index)
|
||||
self._draw_textdoc(text_rect)
|
||||
self._draw_textdoc(text_rect, index.column())
|
||||
self._painter.restore()
|
||||
|
||||
def _draw_textdoc(self, rect):
|
||||
def _draw_textdoc(self, rect, col):
|
||||
"""Draw the QTextDocument of an item.
|
||||
|
||||
Args:
|
||||
@ -156,7 +156,9 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
elif not self._opt.state & QStyle.State_Enabled:
|
||||
color = config.val.colors.completion.category.fg
|
||||
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)
|
||||
|
||||
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||
|
@ -23,7 +23,7 @@ Defines a CompletionView which uses CompletionFiterModel and CompletionModel
|
||||
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 qutebrowser.config import config
|
||||
@ -152,12 +152,12 @@ class CompletionView(QTreeView):
|
||||
column_widths = self.model().column_widths
|
||||
pixel_widths = [(width * perc // 100) for perc in column_widths]
|
||||
|
||||
if self.verticalScrollBar().isVisible():
|
||||
delta = self.style().pixelMetric(QStyle.PM_ScrollBarExtent) + 5
|
||||
if pixel_widths[-1] > delta:
|
||||
pixel_widths[-1] -= delta
|
||||
else:
|
||||
pixel_widths[-2] -= delta
|
||||
delta = self.verticalScrollBar().sizeHint().width()
|
||||
if pixel_widths[-1] > delta:
|
||||
pixel_widths[-1] -= delta
|
||||
else:
|
||||
pixel_widths[-2] -= delta
|
||||
|
||||
for i, w in enumerate(pixel_widths):
|
||||
assert w >= 0, i
|
||||
self.setColumnWidth(i, w)
|
||||
@ -180,6 +180,7 @@ class CompletionView(QTreeView):
|
||||
return self.model().last_item()
|
||||
else:
|
||||
return self.model().first_item()
|
||||
|
||||
while True:
|
||||
idx = self.indexAbove(idx) if upwards else self.indexBelow(idx)
|
||||
# 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
|
||||
return idx
|
||||
|
||||
raise utils.Unreachable
|
||||
|
||||
def _next_category_idx(self, upwards):
|
||||
"""Get the index of the previous/next category.
|
||||
|
||||
@ -222,6 +225,8 @@ class CompletionView(QTreeView):
|
||||
self.scrollTo(idx)
|
||||
return idx.child(0, 0)
|
||||
|
||||
raise utils.Unreachable
|
||||
|
||||
@cmdutils.register(instance='completion',
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
@cmdutils.argument('which', choices=['next', 'prev', 'next-category',
|
||||
|
@ -60,7 +60,8 @@ def value(optname, *_values, info):
|
||||
|
||||
opt = info.config.get_opt(optname)
|
||||
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")])
|
||||
model.add_category(cur_cat)
|
||||
|
||||
|
@ -19,8 +19,6 @@
|
||||
|
||||
"""A completion category that queries the SQL History store."""
|
||||
|
||||
import re
|
||||
|
||||
from PyQt5.QtSql import QSqlQueryModel
|
||||
|
||||
from qutebrowser.misc import sql
|
||||
@ -36,21 +34,7 @@ class HistoryCategory(QSqlQueryModel):
|
||||
"""Create a new History completion category."""
|
||||
super().__init__(parent=parent)
|
||||
self.name = "History"
|
||||
|
||||
# replace ' in timestamp-format to avoid breaking the query
|
||||
timestamp_format = config.val.completion.timestamp_format
|
||||
timefmt = ("strftime('{}', last_atime, 'unixepoch', 'localtime')"
|
||||
.format(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)
|
||||
self._query = None
|
||||
|
||||
# advertise that this model filters by URL and title
|
||||
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
|
||||
pattern = pattern.replace('%', '\\%')
|
||||
pattern = pattern.replace('_', '\\_')
|
||||
# treat spaces as wildcards to match any of the typed words
|
||||
pattern = re.sub(r' +', '%', pattern)
|
||||
pattern = '%{}%'.format(pattern)
|
||||
words = ['%{}%'.format(w) for w in pattern.split(' ')]
|
||||
|
||||
# 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'):
|
||||
self._query.run(pat=pattern)
|
||||
self._query.run(**{
|
||||
str(i): w for i, w in enumerate(words)})
|
||||
self.setQuery(self._query)
|
||||
|
||||
def removeRows(self, row, _count, _parent=None):
|
||||
|
@ -94,10 +94,11 @@ def session(*, info=None): # pylint: disable=unused-argument
|
||||
return model
|
||||
|
||||
|
||||
def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
"""A model to complete on open tabs across all windows.
|
||||
def _buffer(skip_win_id=None):
|
||||
"""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):
|
||||
"""Close the selected tab."""
|
||||
@ -109,6 +110,8 @@ def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(6, 40, 54))
|
||||
|
||||
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',
|
||||
window=win_id)
|
||||
if tabbed_browser.shutting_down:
|
||||
@ -120,19 +123,37 @@ def buffer(*, info=None): # pylint: disable=unused-argument
|
||||
tab.url().toDisplayString(),
|
||||
tabbed_browser.page_title(idx)))
|
||||
cat = listcategory.ListCategory("{}".format(win_id), tabs,
|
||||
delete_func=delete_buffer)
|
||||
delete_func=delete_buffer)
|
||||
model.add_category(cat)
|
||||
|
||||
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."""
|
||||
model = completionmodel.CompletionModel(column_widths=(6, 30, 64))
|
||||
|
||||
windows = []
|
||||
|
||||
for win_id in objreg.window_registry:
|
||||
if win_id == info.win_id:
|
||||
continue
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=win_id)
|
||||
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:
|
||||
@functools.wraps(func)
|
||||
def wrapper(option=None):
|
||||
"""Call the underlying function."""
|
||||
if self._check_match(option):
|
||||
return func()
|
||||
return None
|
||||
else:
|
||||
@functools.wraps(func)
|
||||
def wrapper(wrapper_self, option=None):
|
||||
"""Call the underlying function."""
|
||||
if self._check_match(option):
|
||||
return func(wrapper_self)
|
||||
return None
|
||||
|
||||
return wrapper
|
||||
|
||||
@ -461,7 +465,8 @@ class ConfigContainer:
|
||||
def __setattr__(self, attr, value):
|
||||
"""Set the given option in the config."""
|
||||
if attr.startswith('_'):
|
||||
return super().__setattr__(attr, value)
|
||||
super().__setattr__(attr, value)
|
||||
return
|
||||
|
||||
name = self._join(attr)
|
||||
with self._handle_error('setting', name):
|
||||
|
@ -174,6 +174,7 @@ def _parse_yaml_backends(name, node):
|
||||
elif isinstance(node, dict):
|
||||
return _parse_yaml_backends_dict(name, node)
|
||||
_raise_invalid_node(name, 'backends', node)
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
def _read_yaml(yaml_data):
|
||||
|
@ -34,6 +34,9 @@ history_gap_interval:
|
||||
`:history`. Use -1 to disable separation.
|
||||
|
||||
ignore_case:
|
||||
renamed: search.ignore_case
|
||||
|
||||
search.ignore_case:
|
||||
type:
|
||||
name: String
|
||||
valid_values:
|
||||
@ -43,6 +46,11 @@ ignore_case:
|
||||
default: smart
|
||||
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:
|
||||
type:
|
||||
name: String
|
||||
@ -79,6 +87,9 @@ new_instance_open_target_window:
|
||||
When `new_instance_open_target` is not set to `window`, this is ignored.
|
||||
|
||||
session_default_name:
|
||||
renamed: session.default_name
|
||||
|
||||
session.default_name:
|
||||
type:
|
||||
name: SessionName
|
||||
none_ok: true
|
||||
@ -88,6 +99,11 @@ session_default_name:
|
||||
|
||||
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:
|
||||
type:
|
||||
name: String
|
||||
@ -1292,6 +1308,7 @@ tabs.title.format:
|
||||
- host
|
||||
- private
|
||||
- current_url
|
||||
- protocol
|
||||
none_ok: true
|
||||
desc: |
|
||||
Format to use for the tab title.
|
||||
@ -1306,8 +1323,9 @@ tabs.title.format:
|
||||
* `{scroll_pos}`: Page scroll position.
|
||||
* `{host}`: Host of the current web page.
|
||||
* `{backend}`: Either ''webkit'' or ''webengine''
|
||||
* `{private}` : Indicates when private mode is enabled.
|
||||
* `{current_url}` : URL of the current web page.
|
||||
* `{private}`: Indicates when private mode is enabled.
|
||||
* `{current_url}`: URL of the current web page.
|
||||
* `{protocol}`: Protocol (http/https/...) of the current web page.
|
||||
|
||||
tabs.title.format_pinned:
|
||||
default: '{index}'
|
||||
@ -1324,6 +1342,7 @@ tabs.title.format_pinned:
|
||||
- host
|
||||
- private
|
||||
- current_url
|
||||
- protocol
|
||||
none_ok: true
|
||||
desc: Format to use for the tab title for pinned tabs. The same placeholders
|
||||
like for `tabs.title.format` are defined.
|
||||
@ -1467,6 +1486,7 @@ window.title_format:
|
||||
- backend
|
||||
- private
|
||||
- current_url
|
||||
- protocol
|
||||
default: '{perc}{title}{title_sep}qutebrowser'
|
||||
desc: |
|
||||
Format to use for the window title. The same placeholders like for
|
||||
@ -1520,9 +1540,15 @@ zoom.text_only:
|
||||
## colors
|
||||
|
||||
colors.completion.fg:
|
||||
default: white
|
||||
type: QtColor
|
||||
desc: Text color of the completion widget.
|
||||
default: ["white", "white", "white"]
|
||||
type:
|
||||
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:
|
||||
default: '#444444'
|
||||
@ -2287,6 +2313,7 @@ bindings.default:
|
||||
<Ctrl-C>: completion-item-yank
|
||||
<Ctrl-Shift-C>: completion-item-yank --sel
|
||||
<Return>: command-accept
|
||||
<Ctrl-Return>: command-accept --rapid
|
||||
<Ctrl-B>: rl-backward-char
|
||||
<Ctrl-F>: rl-forward-char
|
||||
<Alt-B>: rl-backward-word
|
||||
|
@ -136,7 +136,7 @@ class YamlConfig(QObject):
|
||||
with open(self._filename, 'r', encoding='utf-8') as f:
|
||||
yaml_data = utils.yaml_load(f)
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
return
|
||||
except OSError as e:
|
||||
desc = configexc.ConfigErrorDesc("While reading", e)
|
||||
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
|
||||
|
@ -499,7 +499,7 @@ class ListOrValue(BaseType):
|
||||
|
||||
_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)
|
||||
assert not isinstance(valtype, (List, ListOrValue)), valtype
|
||||
self.listtype = List(valtype, none_ok=none_ok, *args, **kwargs)
|
||||
@ -963,7 +963,7 @@ class Font(BaseType):
|
||||
# Gets set when the config is initialized.
|
||||
monospace_fonts = None
|
||||
font_regex = re.compile(r"""
|
||||
^(
|
||||
(
|
||||
(
|
||||
# style
|
||||
(?P<style>normal|italic|oblique) |
|
||||
@ -976,14 +976,14 @@ class Font(BaseType):
|
||||
(?P<size>[0-9]+((\.[0-9]+)?[pP][tT]|[pP][xX]))
|
||||
)\ # size/weight/style are space-separated
|
||||
)* # 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):
|
||||
self._basic_py_validation(value, str)
|
||||
if not value:
|
||||
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
|
||||
# as family.
|
||||
raise configexc.ValidationError(value, "must be a valid font")
|
||||
@ -1002,7 +1002,7 @@ class FontFamily(Font):
|
||||
if not value:
|
||||
return None
|
||||
|
||||
match = self.font_regex.match(value)
|
||||
match = self.font_regex.fullmatch(value)
|
||||
if not match: # pragma: no cover
|
||||
# This should never happen, as the regex always matches everything
|
||||
# as family.
|
||||
@ -1039,7 +1039,7 @@ class QtFont(Font):
|
||||
font.setStyle(QFont.StyleNormal)
|
||||
font.setWeight(QFont.Normal)
|
||||
|
||||
match = self.font_regex.match(value)
|
||||
match = self.font_regex.fullmatch(value)
|
||||
if not match: # pragma: no cover
|
||||
# This should never happen, as the regex always matches everything
|
||||
# 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">
|
||||
{% block style %}
|
||||
body {
|
||||
background-color: #fff;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ body {
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid grey;
|
||||
border: 1px solid #222;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ pre {
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid grey;
|
||||
border: 1px solid #222;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
@ -1,2 +1,4 @@
|
||||
# Upstream Mozilla's code
|
||||
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:
|
||||
browser: true
|
||||
|
||||
@ -21,7 +33,7 @@ rules:
|
||||
no-extra-parens: off
|
||||
id-length: ["error", {"exceptions": ["i", "k", "x", "y"]}]
|
||||
object-shorthand: "off"
|
||||
max-statements: ["error", {"max": 30}]
|
||||
max-statements: ["error", {"max": 40}]
|
||||
quotes: ["error", "double", {"avoidEscape": true}]
|
||||
object-property-newline: ["error", {"allowMultiplePropertiesPerLine": true}]
|
||||
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 = {
|
||||
"id": id,
|
||||
"value": elem.value,
|
||||
"outer_xml": elem.outerHTML,
|
||||
"rects": [], // Gets filled up later
|
||||
"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/2877
|
||||
// https://stackoverflow.com/q/22942689/2085149
|
||||
if (typeof elem.tagName === "string") {
|
||||
out.tag_name = elem.tagName;
|
||||
} else if (typeof elem.nodeName === "string") {
|
||||
@ -120,6 +122,18 @@ window._qutebrowser.webelem = (function() {
|
||||
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") {
|
||||
out.text = elem.textContent;
|
||||
} else if (typeof elem.text === "string") {
|
||||
|
@ -150,7 +150,8 @@ class BaseKeyParser(QObject):
|
||||
A (count, command) tuple.
|
||||
"""
|
||||
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
|
||||
if count == 0 and not cmd_input:
|
||||
cmd_input = keystring
|
||||
@ -213,7 +214,7 @@ class BaseKeyParser(QObject):
|
||||
elif match == self.Match.other:
|
||||
pass
|
||||
else:
|
||||
raise AssertionError("Invalid match value {!r}".format(match))
|
||||
raise utils.Unreachable("Invalid match value {!r}".format(match))
|
||||
return match
|
||||
|
||||
def _match_key(self, cmd_input):
|
||||
|
@ -322,10 +322,13 @@ class ModeManager(QObject):
|
||||
if self.mode is None:
|
||||
# We got events before mode is set, so just pass them through.
|
||||
return False
|
||||
if event.type() == QEvent.KeyPress:
|
||||
return self._eventFilter_keypress(event)
|
||||
else:
|
||||
return self._eventFilter_keyrelease(event)
|
||||
|
||||
handlers = {
|
||||
QEvent.KeyPress: self._eventFilter_keypress,
|
||||
QEvent.KeyRelease: self._eventFilter_keyrelease,
|
||||
}
|
||||
handler = handlers[event.type()]
|
||||
return handler(event)
|
||||
|
||||
@cmdutils.register(instance='mode-manager', scope='window')
|
||||
def clear_keychain(self):
|
||||
|
@ -71,7 +71,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
||||
txt = e.text().strip()
|
||||
if self._inhibited:
|
||||
self._debug_log("Ignoring key '{}', because the normal mode is "
|
||||
"currently inhibited.".format(txt))
|
||||
"currently inhibited.".format(txt))
|
||||
return self.Match.none
|
||||
match = super()._handle_single_key(e)
|
||||
if match == self.Match.partial:
|
||||
@ -83,6 +83,7 @@ class NormalKeyParser(keyparser.CommandKeyParser):
|
||||
return match
|
||||
|
||||
def set_inhibited_timeout(self, timeout):
|
||||
"""Ignore keypresses for the given duration."""
|
||||
if timeout != 0:
|
||||
self._debug_log("Inhibiting the normal mode for {}ms.".format(
|
||||
timeout))
|
||||
@ -217,7 +218,7 @@ class HintKeyParser(keyparser.CommandKeyParser):
|
||||
self._last_press = LastPress.none
|
||||
return True
|
||||
elif match == self.Match.other:
|
||||
pass
|
||||
return None
|
||||
elif match == self.Match.none:
|
||||
# We couldn't find a keychain so we check if it's a special key.
|
||||
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.config import config, configfiles
|
||||
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
|
||||
jinja, debug)
|
||||
jinja)
|
||||
from qutebrowser.mainwindow import messageview, prompt
|
||||
from qutebrowser.completion import completionwidget, completer
|
||||
from qutebrowser.keyinput import modeman
|
||||
@ -94,16 +94,19 @@ def get_window(via_ipc, force_window=False, force_tab=False,
|
||||
return window.win_id
|
||||
|
||||
|
||||
def raise_window(window):
|
||||
def raise_window(window, alert=True):
|
||||
"""Raise the given MainWindow object."""
|
||||
window.setWindowState(window.windowState() & ~Qt.WindowMinimized)
|
||||
window.setWindowState(window.windowState() | Qt.WindowActive)
|
||||
window.raise_()
|
||||
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."""
|
||||
try:
|
||||
win_mode = config.val.new_instance_open_target_window
|
||||
@ -131,7 +134,6 @@ class MainWindow(QWidget):
|
||||
Attributes:
|
||||
status: The StatusBar widget.
|
||||
tabbed_browser: The TabbedBrowser widget.
|
||||
state_before_fullscreen: window state before activation of fullscreen.
|
||||
_downloadview: The DownloadView widget.
|
||||
_vbox: The main QVBoxLayout.
|
||||
_commandrunner: The main CommandRunner instance.
|
||||
@ -231,8 +233,6 @@ class MainWindow(QWidget):
|
||||
|
||||
objreg.get("app").new_window.emit(self)
|
||||
|
||||
self.state_before_fullscreen = self.windowState()
|
||||
|
||||
def _init_geometry(self, geometry):
|
||||
"""Initialize the window geometry or load it from disk."""
|
||||
if geometry is not None:
|
||||
@ -320,7 +320,8 @@ class MainWindow(QWidget):
|
||||
def _init_completion(self):
|
||||
self._completion = completionwidget.CompletionView(self.win_id, self)
|
||||
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(
|
||||
completer_obj.on_selection_changed)
|
||||
objreg.register('completion', self._completion, scope='window',
|
||||
@ -493,12 +494,9 @@ class MainWindow(QWidget):
|
||||
def _on_fullscreen_requested(self, on):
|
||||
if not config.val.content.windowed_fullscreen:
|
||||
if on:
|
||||
self.state_before_fullscreen = self.windowState()
|
||||
self.showFullScreen()
|
||||
self.setWindowState(self.windowState() | Qt.WindowFullScreen)
|
||||
elif self.isFullScreen():
|
||||
self.setWindowState(self.state_before_fullscreen)
|
||||
log.misc.debug('on: {}, state before fullscreen: {}'.format(
|
||||
on, debug.qflags_key(Qt, self.state_before_fullscreen)))
|
||||
self.setWindowState(self.windowState() & ~Qt.WindowFullScreen)
|
||||
|
||||
@cmdutils.register(instance='main-window', scope='window')
|
||||
@pyqtSlot()
|
||||
|
@ -171,7 +171,7 @@ class PromptQueue(QObject):
|
||||
# just queue it up for later.
|
||||
log.prompt.debug("Adding {} to queue.".format(question))
|
||||
self._queue.append(question)
|
||||
return
|
||||
return None
|
||||
|
||||
if blocking:
|
||||
# If we're blocking we save the old question on the stack, so we
|
||||
@ -207,6 +207,7 @@ class PromptQueue(QObject):
|
||||
return question.answer
|
||||
else:
|
||||
question.completed.connect(self._pop_later)
|
||||
return None
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_left(self, mode):
|
||||
|
@ -26,7 +26,8 @@ from qutebrowser.keyinput import modeman, modeparsers
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.misc import cmdhistory, editor
|
||||
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):
|
||||
@ -66,6 +67,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
self.cursorPositionChanged.connect(self.update_completion)
|
||||
self.textChanged.connect(self.update_completion)
|
||||
self.textChanged.connect(self.updateGeometry)
|
||||
self.textChanged.connect(self._incremental_search)
|
||||
|
||||
def prefix(self):
|
||||
"""Get the currently entered command prefix."""
|
||||
@ -154,8 +156,12 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
|
||||
@cmdutils.register(instance='status-command',
|
||||
modes=[usertypes.KeyMode.command], scope='window')
|
||||
def command_accept(self):
|
||||
"""Execute the command currently in the commandline."""
|
||||
def command_accept(self, rapid=False):
|
||||
"""Execute the command currently in the commandline.
|
||||
|
||||
Args:
|
||||
rapid: Run the command without closing or clearing the command bar.
|
||||
"""
|
||||
prefixes = {
|
||||
':': '',
|
||||
'/': 'search -- ',
|
||||
@ -163,7 +169,9 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
}
|
||||
text = self.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:])
|
||||
|
||||
@cmdutils.register(instance='status-command', scope='window')
|
||||
@ -213,8 +221,8 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
elif text[0] in modeparsers.STARTCHARS:
|
||||
super().set_prompt(text[0])
|
||||
else:
|
||||
raise AssertionError("setText got called with invalid text "
|
||||
"'{}'!".format(text))
|
||||
raise utils.Unreachable("setText got called with invalid text "
|
||||
"'{}'!".format(text))
|
||||
super().setText(text)
|
||||
|
||||
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
|
||||
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:
|
||||
e.ignore()
|
||||
return
|
||||
@ -238,3 +252,16 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
text = 'x'
|
||||
width = self.fontMetrics().width(text)
|
||||
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():
|
||||
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.
|
||||
|
||||
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:
|
||||
message.confirm_async(
|
||||
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])
|
||||
else:
|
||||
yes_action()
|
||||
@ -818,6 +820,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
point, url = self._global_marks[key]
|
||||
|
||||
def callback(ok):
|
||||
"""Scroll once loading finished."""
|
||||
if ok:
|
||||
self.cur_load_finished.disconnect(callback)
|
||||
tab.scroller.to_point(point)
|
||||
|
@ -172,14 +172,15 @@ class TabWidget(QTabWidget):
|
||||
fields['perc'] = ''
|
||||
|
||||
try:
|
||||
fields['host'] = self.tab_url(idx).host()
|
||||
url = self.tab_url(idx)
|
||||
except qtutils.QtValueError:
|
||||
fields['host'] = ''
|
||||
|
||||
try:
|
||||
fields['current_url'] = self.tab_url(idx).url()
|
||||
except qtutils.QtValueError:
|
||||
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]
|
||||
if y is None:
|
||||
|
@ -152,7 +152,7 @@ def _show_dialog(*args, **kwargs):
|
||||
elif status == _Result.restart:
|
||||
quitter.restart()
|
||||
else:
|
||||
assert False, status
|
||||
raise utils.Unreachable(status)
|
||||
|
||||
sys.exit(usertypes.Exit.err_init)
|
||||
|
||||
@ -199,8 +199,7 @@ def _handle_nouveau_graphics():
|
||||
buttons=[button],
|
||||
)
|
||||
|
||||
# Should never be reached
|
||||
assert False
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
def _handle_wayland():
|
||||
@ -239,8 +238,7 @@ def _handle_wayland():
|
||||
"(based on Chromium). "
|
||||
)
|
||||
|
||||
# Should never be reached
|
||||
assert False
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
@attr.s
|
||||
@ -359,11 +357,11 @@ def _check_backend_modules():
|
||||
html.escape(imports.webengine_error))
|
||||
)
|
||||
|
||||
# Should never be reached
|
||||
assert False
|
||||
raise utils.Unreachable
|
||||
|
||||
|
||||
def init():
|
||||
"""Check for various issues related to QtWebKit/QtWebEngine."""
|
||||
_check_backend_modules()
|
||||
if objects.backend == usertypes.Backend.QtWebEngine:
|
||||
_handle_ssl_support()
|
||||
|
@ -62,7 +62,7 @@ def parse_fatal_stacktrace(text):
|
||||
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
|
||||
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:
|
||||
# We got some invalid text.
|
||||
return ('', '')
|
||||
@ -348,7 +348,6 @@ class _CrashDialog(QDialog):
|
||||
"but you're currently running v{} - please "
|
||||
"update!".format(newest, qutebrowser.__version__))
|
||||
text = '<br/><br/>'.join(lines)
|
||||
self.finish()
|
||||
msgbox.information(self, "Report successfully sent!", text,
|
||||
on_finished=self.finish, plain_text=False)
|
||||
|
||||
@ -365,7 +364,6 @@ class _CrashDialog(QDialog):
|
||||
"<a href=https://www.qutebrowser.org/>qutebrowser.org</a> "
|
||||
"by yourself.".format(msg))
|
||||
text = '<br/><br/>'.join(lines)
|
||||
self.finish()
|
||||
msgbox.information(self, "Report successfully sent!", text,
|
||||
on_finished=self.finish, plain_text=False)
|
||||
|
||||
|
@ -20,13 +20,13 @@
|
||||
"""Handlers for crashes and OS signals."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
import bdb
|
||||
import pdb # noqa: T002
|
||||
import signal
|
||||
import functools
|
||||
import faulthandler
|
||||
import os.path
|
||||
try:
|
||||
# WORKAROUND for segfaults when using pdb in pytest for some reason...
|
||||
import readline # pylint: disable=unused-import
|
||||
|
@ -202,14 +202,14 @@ def _check_modules(modules):
|
||||
messages = ['invalid escape sequence',
|
||||
'Flags not at the start of the expression']
|
||||
with log.ignore_py_warnings(
|
||||
category=DeprecationWarning,
|
||||
message=r'({})'.format('|'.join(messages))
|
||||
category=DeprecationWarning,
|
||||
message=r'({})'.format('|'.join(messages))
|
||||
), log.ignore_py_warnings(
|
||||
category=PendingDeprecationWarning,
|
||||
module='imp'
|
||||
category=PendingDeprecationWarning,
|
||||
module='imp'
|
||||
), log.ignore_py_warnings(
|
||||
category=ImportWarning,
|
||||
message=r'Not importing directory .*: missing __init__'
|
||||
category=ImportWarning,
|
||||
message=r'Not importing directory .*: missing __init__'
|
||||
):
|
||||
importlib.import_module(name)
|
||||
except ImportError as e:
|
||||
@ -261,6 +261,7 @@ def init_log(args):
|
||||
|
||||
|
||||
def check_optimize_flag():
|
||||
"""Check whether qutebrowser is running with -OO."""
|
||||
from qutebrowser.utils import log
|
||||
if sys.flags.optimize >= 2:
|
||||
log.init.warning("Running on optimize level higher than 1, "
|
||||
|
@ -111,9 +111,11 @@ class ExternalEditor(QObject):
|
||||
# the file from the external editor, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/1767
|
||||
with tempfile.NamedTemporaryFile(
|
||||
# pylint: disable=bad-continuation
|
||||
mode='w', prefix='qutebrowser-editor-',
|
||||
encoding=config.val.editor.encoding,
|
||||
delete=False) as fobj:
|
||||
# pylint: enable=bad-continuation
|
||||
if text:
|
||||
fobj.write(text)
|
||||
self._filename = fobj.name
|
||||
|
@ -19,12 +19,14 @@
|
||||
|
||||
"""A QProcess which shows notifications in the GUI."""
|
||||
|
||||
import locale
|
||||
import shlex
|
||||
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess,
|
||||
QProcessEnvironment)
|
||||
|
||||
from qutebrowser.utils import message, log
|
||||
from qutebrowser.browser import qutescheme
|
||||
|
||||
# A mapping of QProcess::ErrorCode's to human-readable strings.
|
||||
|
||||
@ -96,6 +98,16 @@ class GUIProcess(QObject):
|
||||
self._started = False
|
||||
log.procs.debug("Process finished with code {}, status {}.".format(
|
||||
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:
|
||||
message.error("{} crashed!".format(self._what.capitalize()))
|
||||
elif status == QProcess.NormalExit and code == 0:
|
||||
@ -109,13 +121,22 @@ class GUIProcess(QObject):
|
||||
message.error("{} exited with status {}, see :messages for "
|
||||
"details.".format(self._what.capitalize(), code))
|
||||
|
||||
stderr = bytes(self._proc.readAllStandardError()).decode('utf-8')
|
||||
stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8')
|
||||
if stdout:
|
||||
log.procs.error("Process stdout:\n" + stdout.strip())
|
||||
if stderr:
|
||||
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()
|
||||
def on_started(self):
|
||||
"""Called when the process started successfully."""
|
||||
|
@ -87,7 +87,7 @@ class KeyHintView(QLabel):
|
||||
Args:
|
||||
prefix: The current partial keystring.
|
||||
"""
|
||||
countstr, prefix = re.match(r'^(\d*)(.*)', prefix).groups()
|
||||
countstr, prefix = re.fullmatch(r'(\d*)(.*)', prefix).groups()
|
||||
if not prefix:
|
||||
self._show_timer.stop()
|
||||
self.hide()
|
||||
|
@ -241,7 +241,7 @@ class WrapperLayout(QLayout):
|
||||
self._widget = None
|
||||
|
||||
def addItem(self, _widget):
|
||||
raise AssertionError("Should never be called!")
|
||||
raise utils.Unreachable
|
||||
|
||||
def sizeHint(self):
|
||||
return self._widget.sizeHint()
|
||||
@ -250,7 +250,7 @@ class WrapperLayout(QLayout):
|
||||
return None
|
||||
|
||||
def takeAt(self, _index):
|
||||
raise AssertionError("Should never be called!")
|
||||
raise utils.Unreachable
|
||||
|
||||
def setGeometry(self, rect):
|
||||
self._widget.setGeometry(rect)
|
||||
|
@ -21,6 +21,8 @@
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import itertools
|
||||
import urllib
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
|
||||
@ -205,7 +207,13 @@ class SessionManager(QObject):
|
||||
for idx, item in enumerate(tab.history):
|
||||
qtutils.ensure_valid(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
|
||||
|
||||
def _save_all(self, *, only_window=None, with_private=False):
|
||||
@ -251,7 +259,7 @@ class SessionManager(QObject):
|
||||
object.
|
||||
"""
|
||||
if name is default:
|
||||
name = config.val.session_default_name
|
||||
name = config.val.session.default_name
|
||||
if name is None:
|
||||
if self._current is not None:
|
||||
name = self._current
|
||||
@ -283,7 +291,7 @@ class SessionManager(QObject):
|
||||
data = self._last_window_session
|
||||
if data is None:
|
||||
log.sessions.error("last_window_session is None while saving!")
|
||||
return
|
||||
return None
|
||||
else:
|
||||
data = self._save_all(only_window=only_window,
|
||||
with_private=with_private)
|
||||
@ -323,7 +331,18 @@ class SessionManager(QObject):
|
||||
def _load_tab(self, new_tab, data):
|
||||
"""Load yaml data into a newly opened tab."""
|
||||
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 = {}
|
||||
|
||||
if 'zoom' in data:
|
||||
@ -347,6 +366,20 @@ class SessionManager(QObject):
|
||||
if 'pinned' in histentry:
|
||||
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)
|
||||
url = QUrl.fromEncoded(histentry['url'].encode('ascii'))
|
||||
if 'original-url' in histentry:
|
||||
@ -360,6 +393,7 @@ class SessionManager(QObject):
|
||||
entries.append(entry)
|
||||
if active:
|
||||
new_tab.title_changed.emit(histentry['title'])
|
||||
|
||||
try:
|
||||
new_tab.history.load_items(entries)
|
||||
except ValueError as e:
|
||||
@ -388,7 +422,7 @@ class SessionManager(QObject):
|
||||
window=window.win_id)
|
||||
tab_to_focus = None
|
||||
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)
|
||||
if tab.get('active', False):
|
||||
tab_to_focus = i
|
||||
@ -460,7 +494,7 @@ class SessionManager(QObject):
|
||||
|
||||
Args:
|
||||
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.
|
||||
quiet: Don't show confirmation message.
|
||||
force: Force saving internal sessions (starting with an underline).
|
||||
@ -485,7 +519,9 @@ class SessionManager(QObject):
|
||||
raise cmdexc.CommandError("Error while saving session: {}"
|
||||
.format(e))
|
||||
else:
|
||||
if not quiet:
|
||||
if quiet:
|
||||
log.sessions.debug("Saved session {}.".format(name))
|
||||
else:
|
||||
message.info("Saved session {}.".format(name))
|
||||
|
||||
@cmdutils.register(instance='session-manager')
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
import re
|
||||
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.utils import log, utils
|
||||
|
||||
|
||||
class ShellLexer:
|
||||
@ -117,7 +117,8 @@ class ShellLexer:
|
||||
else:
|
||||
self.token += nextchar
|
||||
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:
|
||||
self.token += self.state
|
||||
if self.token or self.quoted:
|
||||
|
@ -74,7 +74,8 @@ def get_argparser():
|
||||
"session even if one would be restored.",
|
||||
action='store_true')
|
||||
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 "
|
||||
"qutebrowser instance running.")
|
||||
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