Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9d5b2df0c | ||
|
|
d6fd5a817e | ||
|
|
301186d407 | ||
|
|
ab121a98da | ||
|
|
a463038834 | ||
|
|
22761b4373 | ||
|
|
78f6f3a0e1 | ||
|
|
6166ea51e2 | ||
|
|
4d4065dfac | ||
|
|
81f350ee99 | ||
|
|
4b98e6e9ce | ||
|
|
11f8ab1f85 | ||
|
|
0449da048f | ||
|
|
c78e938dea | ||
|
|
99fb8a5d87 | ||
|
|
b1e0b8f119 | ||
|
|
8d49e001e9 | ||
|
|
965c176acf | ||
|
|
896da1c27e | ||
|
|
8f33fcfc52 | ||
|
|
91b0a33ab0 | ||
|
|
b059f4058f | ||
|
|
b63ce438b4 | ||
|
|
f96cf6fe27 | ||
|
|
bb1a1b80aa | ||
|
|
b703028411 | ||
|
|
6089d4a636 | ||
|
|
d2e550e922 | ||
|
|
d1d6fb3dce | ||
|
|
ea5ee0e7c8 | ||
|
|
f1435ce51f | ||
|
|
2a4e884e1b | ||
|
|
aef693805a | ||
|
|
553a2fbcbd | ||
|
|
ed253f23c6 | ||
|
|
60cc70151c | ||
|
|
3426d843bd | ||
|
|
13fe444c79 | ||
|
|
767f043dfa | ||
|
|
53c037f2fa | ||
|
|
73d08cb60c | ||
|
|
177707687c | ||
|
|
89c7f3ecfe | ||
|
|
03ac8874ff | ||
|
|
be2c67aa19 | ||
|
|
c57f0063bc | ||
|
|
6c6ae4e465 | ||
|
|
f0779f8cc0 | ||
|
|
f0d0d92124 | ||
|
|
fd5901070d | ||
|
|
b1501a691d | ||
|
|
d1e0de236d | ||
|
|
d029044787 | ||
|
|
769bc65343 | ||
|
|
bf4d6a5707 | ||
|
|
dec6842370 | ||
|
|
4ab5d2df28 | ||
|
|
7c86693dd4 | ||
|
|
0c6af7a5f3 | ||
|
|
4a632f85e0 | ||
|
|
0c5aed284b | ||
|
|
5d0dd5b11b | ||
|
|
324035240c | ||
|
|
d13296fe7d | ||
|
|
535db9d2a2 | ||
|
|
5562cc25e0 | ||
|
|
df3d41eb66 | ||
|
|
847d44cb31 | ||
|
|
9f9f1a6d52 | ||
|
|
294d8dd672 | ||
|
|
6d11fc72c6 | ||
|
|
fbca2be1c9 | ||
|
|
bbff9cb4f0 | ||
|
|
b0c22b9867 | ||
|
|
b02c406cb9 | ||
|
|
9816f754a1 | ||
|
|
fe845b7af8 | ||
|
|
bececc69c3 | ||
|
|
017f143a5f | ||
|
|
4363db90c0 | ||
|
|
b01041e455 | ||
|
|
f50a19a488 | ||
|
|
3752733f15 | ||
|
|
89e051ff51 | ||
|
|
57c8dff396 | ||
|
|
c48727d19a | ||
|
|
58c991145c | ||
|
|
791ff36c69 | ||
|
|
2d1c12f69b | ||
|
|
28dfd73c60 | ||
|
|
877d814815 | ||
|
|
42890b8a7f | ||
|
|
c295486333 | ||
|
|
23ee01a747 | ||
|
|
51415896bc | ||
|
|
b8752c02b4 | ||
|
|
ef9ddb2d5f | ||
|
|
05e835684d | ||
|
|
07957b105d | ||
|
|
5c15f56213 | ||
|
|
29ce0a5157 | ||
|
|
f84299e6c6 | ||
|
|
410d78cfa2 | ||
|
|
3cc1134e82 | ||
|
|
a714f0b70c | ||
|
|
512d7c4448 | ||
|
|
30b5319068 | ||
|
|
655ab31d48 | ||
|
|
7ccc69c0bc | ||
|
|
5e4f3ed7c5 | ||
|
|
40781b163e | ||
|
|
cafb487ac9 | ||
|
|
676313e7ae | ||
|
|
05e6515aad | ||
|
|
965a1256a3 | ||
|
|
ed013ac3cf | ||
|
|
e0271eff34 | ||
|
|
888a17b7c3 | ||
|
|
b899d8b44d | ||
|
|
77579e7ebd | ||
|
|
b06d8c55c5 | ||
|
|
fdb66d2e2b | ||
|
|
efd632ea73 | ||
|
|
512e51eeb4 | ||
|
|
33120bb780 | ||
|
|
86d91b9c3d | ||
|
|
d9a38fea1a | ||
|
|
b1f5c2a23c | ||
|
|
0d9c7049b6 | ||
|
|
3c68506665 | ||
|
|
1d2016d3a5 | ||
|
|
d84c4fab84 | ||
|
|
637d31c780 | ||
|
|
fff7c500ad | ||
|
|
d6735c69dd | ||
|
|
3af6279dca | ||
|
|
5baae727b0 | ||
|
|
58e140d3cb | ||
|
|
e7634dfe04 |
@@ -8,11 +8,13 @@ targets=qutebrowser,scripts
|
||||
# D209: Blank line before closing """ (removed from PEP257)
|
||||
# D402: First line should not be function's signature (false-positives)
|
||||
disable=D102,D209,D402
|
||||
exclude=test_.*,ez_setup
|
||||
exclude=test_.*
|
||||
|
||||
[pylint]
|
||||
args=--output-format=colorized,--reports=no,--rcfile=.pylintrc
|
||||
plugins=config,crlf,modeline,settrace,openencoding
|
||||
exclude=resources.py
|
||||
|
||||
[flake8]
|
||||
args=--config=.flake8
|
||||
exclude=resources.py
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
recursive-include qutebrowser/html *.html
|
||||
recursive-include qutebrowser/test *.py
|
||||
recursive-include icons *
|
||||
include qutebrowser/test/testfile
|
||||
include qutebrowser/git-commit-id
|
||||
include COPYING doc/*
|
||||
include COPYING doc/* README.asciidoc
|
||||
include qutebrowser.desktop
|
||||
|
||||
exclude scripts/run_checks.py
|
||||
exclude scripts/cleanup.py
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
qutebrowser
|
||||
===========
|
||||
|
||||
_A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit._
|
||||
image:icons/qutebrowser-64x64.png[] _A keyboard-driven, vim-like browser based
|
||||
on PyQt5 and QtWebKit._
|
||||
|
||||
qutebrowser is a keyboard-focused browser with with a minimal GUI. It's based
|
||||
on Python, PyQt5 and QtWebKit and free software, licensed under the GPL.
|
||||
@@ -21,6 +22,16 @@ image:doc/img/downloads.png[width=300,link="doc/img/downloads.png"]
|
||||
image:doc/img/completion.png[width=300,link="doc/img/completion.png"]
|
||||
image:doc/img/hints.png[width=300,link="doc/img/hints.png"]
|
||||
|
||||
Downloads
|
||||
---------
|
||||
|
||||
See the https://github.com/The-Compiler/qutebrowser/releases[github releases
|
||||
page] for available downloads (currently a source archive, and standalone
|
||||
packages as well as MSI installers for Windows).
|
||||
|
||||
See link:doc/INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
|
||||
qutebrowser running for various platforms.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
@@ -115,12 +126,19 @@ Contributors, sorted by the number of commits in descending order:
|
||||
// QUTE_AUTHORS_START
|
||||
* Florian Bruhin
|
||||
* Claude
|
||||
* John ShaggyTwoDope Jenkins
|
||||
* rikn00
|
||||
* Brian Jackson
|
||||
* Mathias Fussenegger
|
||||
* Johannes Altmanninger
|
||||
* Peter Vilim
|
||||
* Martin Zimmermann
|
||||
* Error 800
|
||||
* Mathias Fussenegger
|
||||
* Larry Hynes
|
||||
* Johannes Altmanninger
|
||||
* Joel Torstensson
|
||||
* Regina Hug
|
||||
* Peter Vilim
|
||||
* Matthias Lisin
|
||||
* Helen Sherwood-Taylor
|
||||
// QUTE_AUTHORS_END
|
||||
|
||||
The following people have contributed graphics:
|
||||
@@ -138,7 +156,7 @@ http://www.reddit.com/r/linux/comments/2huqbc/dwb_abandoned/[unmaintained] -
|
||||
main inspiration for qutebrowser)
|
||||
* https://github.com/fanglingsu/vimb[vimb] (C, GTK+ with WebKit1, active)
|
||||
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||
WebKit1, active)
|
||||
WebKit1, dead)
|
||||
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1, active)
|
||||
* https://mason-larobina.github.io/luakit/[luakit] (C/Lua, GTK+ with
|
||||
WebKit1, not very active)
|
||||
@@ -152,7 +170,10 @@ WebKit, active)
|
||||
* http://www.vimperator.org/[Vimperator] (Firefox addon)
|
||||
* http://5digits.org/pentadactyl/[Pentadactyl] (Firefox addon)
|
||||
* https://github.com/akhodakivskiy/VimFx[VimFx] (Firefox addon)
|
||||
* https://github.com/1995eaton/chromium-vim[cVim] (Chrome/Chromium addon)
|
||||
* http://vimium.github.io/[vimium] (Chrome/Chromium addon)
|
||||
* https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome] (Chrome/Chromium addon)
|
||||
* https://github.com/jinzhu/vrome[Vrome] (Chrome/Chromium addon)
|
||||
|
||||
Most of them were inspirations for qutebrowser in some way, thanks for that!
|
||||
|
||||
|
||||
@@ -75,5 +75,43 @@ Is there an adblocker?::
|
||||
usage], so implementing it properly might take some time and won't be done
|
||||
for v0.1 if at all.
|
||||
|
||||
// We link to github rather than to the file here so it also works with the
|
||||
// qutebrowser :help because that doesn't render HACKING.
|
||||
== Troubleshooting
|
||||
|
||||
Configuration not saved after modifying config.::
|
||||
When editing your config file manually, qutebrowser must be exited completely.
|
||||
This can be done by issuing the command `:quit` or by pressing `Ctrl+q`.
|
||||
|
||||
Unable to view flash content.::
|
||||
If you have flash installed for on your system, it's necessary to enable plugins
|
||||
to use the flash plugin. Using the command `:set content allow-plugins true`
|
||||
in qutebrowser will enable plugins. Packages for flash should
|
||||
be provided for your platform or it can be obtained from
|
||||
http://get.adobe.com/flashplayer/[Adobe].
|
||||
|
||||
Experiencing freezing on sites like duckduckgo and youtube.::
|
||||
This issue could be caused by stale plugin files installed by `mozplugger`
|
||||
if mozplugger was subsequently removed.
|
||||
Try exiting qutebroser and removing `~/.mozilla/plugins/mozplugger*.so`.
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/357[Issue #357]
|
||||
for more details.
|
||||
|
||||
Experiencing segfaults (crashes) on Debian systems.::
|
||||
For Debian it's highly recommended to install the `gstreamer0.10-plugins-base` package.
|
||||
This is a workaround for a bug in Qt, it has been fixed upstream in Qt 5.4
|
||||
More details can be found
|
||||
https://bugs.webkit.org/show_bug.cgi?id=119951[here].
|
||||
|
||||
Segfaults on Facebook, Medium, Amazon, ...::
|
||||
If you are on a Debian or Ubuntu based system, you might experience some crashes
|
||||
visting these sites. This is caused by a known bug in Qt which has been
|
||||
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
|
||||
some packages. There is currently no easy way to manually upgrade to Qt
|
||||
5.4 on those systems.
|
||||
|
||||
My issue is not listed.::
|
||||
If you experience any segfaults or crashes, you can report the issue in
|
||||
https://github.com/The-Compiler/qutebrowser/issues[the issue tracker] or
|
||||
using the `:report` command.
|
||||
If you are reporting a segfault, make sure you read the
|
||||
https://github.com/The-Compiler/qutebrowser/blob/master/doc/stacktrace.asciidoc[guide]
|
||||
on how to report them with all needed information.
|
||||
|
||||
@@ -65,25 +65,6 @@ handy. Of course, if using git is the issue which prevents you from
|
||||
contributing, feel free to send normal patches instead, e.g. generated via
|
||||
`diff -Nur`.
|
||||
|
||||
Finding the correct branch
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
qutebrowser is developed in the `master` branch. Feature branches are used by
|
||||
me occasionally and pushed as a backup, but frequently force-pushed. Do *not*
|
||||
base your work on any of the feature branches, use `master` instead.
|
||||
|
||||
For every release, a `vX.Y-stable` branch will be created. Base new features on
|
||||
the `master` branch, and bugfixes for existing stuff on the `...-stable`
|
||||
branch.
|
||||
|
||||
You can checkout the correct branch via:
|
||||
|
||||
----
|
||||
git checkout branch <1>
|
||||
----
|
||||
<1> Of course replace `branch` by `master` or `vX.Y-stable`.
|
||||
|
||||
|
||||
Getting patches
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ qutebrowser should run on these systems:
|
||||
Install the dependencies via apt-get:
|
||||
|
||||
----
|
||||
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-virtualenv
|
||||
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python-virtualenv
|
||||
----
|
||||
|
||||
To generate the documentation for the `:help` command, when using the git
|
||||
@@ -84,15 +84,18 @@ in your `PYTHON_TARGETS` (`/etc/portage/make.conf`) and rebuild your system
|
||||
On Windows
|
||||
----------
|
||||
|
||||
You can either use one of the prebuilt standalone packages or MSI installers,
|
||||
or install manually:
|
||||
You can either use one of the
|
||||
https://github.com/The-Compiler/qutebrowser/releases[prebuilt standalone
|
||||
packages or MSI installers], or install manually:
|
||||
|
||||
* Use the installer from http://www.python.org/downloads[python.org] to get
|
||||
Python 3 (be sure to install pip).
|
||||
* Use the installer from
|
||||
http://www.riverbankcomputing.com/software/pyqt/download5[Riverbank computing]
|
||||
to get Qt and PyQt5.
|
||||
* Run `pip install virtualenv` to install virtualenv.
|
||||
* Run `pip install virtualenv` or
|
||||
http://www.lfd.uci.edu/~gohlke/pythonlibs/#virtualenv[the installer from here]
|
||||
to install virtualenv.
|
||||
|
||||
Then run the supplied script to run qutebrowser inside a
|
||||
https://virtualenv.pypa.io/en/latest/virtualenv.html[virtualenv]:
|
||||
@@ -107,37 +110,26 @@ system-wide Qt5/PyQt5 installations are used in the virtualenv.
|
||||
On OS X
|
||||
-------
|
||||
|
||||
Running qutebrowser on OS X requires compiling PyQt5 by hand. These steps have
|
||||
been tested on OS X Mavericks:
|
||||
To install qutebrowser on OS X, you'll want a package manager, e.g.
|
||||
http://brew.sh/[Homebrew] or https://www.macports.org/[MacPorts]. Also make
|
||||
sure, you have https://itunes.apple.com/en/app/xcode/id497799835[XCode]
|
||||
installed to compile PyQt5 in a later step.
|
||||
|
||||
* Install XCode from the Appstore
|
||||
* Open a Terminal
|
||||
* Run `xcode-select --install`
|
||||
* Install the XCode commandline tools
|
||||
* Run `sudo /usr/bin/xcodebuild` and accept the license.
|
||||
* http://www.qt.io/download-open-source/[Download] and run the Qt5 installer.
|
||||
If you want, you can deselect Android/iOS when selecting the components to be
|
||||
installed.
|
||||
* http://www.python.org/downloads/[Download] and run the Python 3
|
||||
installer.
|
||||
* Download http://www.riverbankcomputing.com/software/sip/download[SIP] and
|
||||
http://www.riverbankcomputing.com/software/pyqt/download5[PyQt5] from Riverbank Coputing
|
||||
* Open a Terminal and use `cd ~/Downloads` to get to the download directory.
|
||||
* Use `tar xzvf sip-*.tar` to extract SIP and `cd sip-*` to change into the
|
||||
SIP directory
|
||||
* Run `python3 configure.py`, `make` and `sudo make install`.
|
||||
* Use `cd ~/Downloads` to get back to the download directory.
|
||||
* Use `tar xvf PyQt-*.tar` to extract PyQt and `cd PyQt-*` to change into the
|
||||
PyQt directory.
|
||||
* Run `sed -i -e "s/qmake_QT=\['webkit', 'network'\]/qmake_QT=['webkit',
|
||||
'network', 'printsupport']/" configure.py`
|
||||
* Run `sed -i -e "s/qmake_QT=\['webkitwidgets'\]/qmake_QT=['webkitwidgets',
|
||||
'printsupport']/" configure.py`
|
||||
* Run `python3 configure.py --qmake ~/Qt/5.4/clang_64/bin/qmake --sip
|
||||
/Library/Frameworks/Python.framework/Versions/3.4/bin/sip` and accept
|
||||
the license.
|
||||
* Run `make` and `sudo make install`.
|
||||
* Run `python3 setup.py install` to install all other dependencies
|
||||
----
|
||||
$ brew install python3 pyqt5
|
||||
$ pip3.4 install qutebrowser
|
||||
----
|
||||
|
||||
if you are using Homebrew. For MacPorts, run:
|
||||
|
||||
----
|
||||
$ sudo port install python34 py34-jinja2 asciidoc py34-pygments py34-pyqt5
|
||||
$ sudo pip3.4 install qutebrowser
|
||||
----
|
||||
|
||||
The preferences for qutebrowser are stored in
|
||||
`~/Library/Preferences/qutebrowser`, the application data is stored in
|
||||
`~/Library/Application Support/qutebrowser`.
|
||||
|
||||
Packagers
|
||||
---------
|
||||
|
||||
@@ -508,7 +508,7 @@ Syntax: +:zoom ['zoom']+
|
||||
|
||||
Set the zoom level for the current tab.
|
||||
|
||||
The zoom can be given as argument or as [count]. If neither of both is given, the zoom is set to 100%.
|
||||
The zoom can be given as argument or as [count]. If neither of both is given, the zoom is set to the default zoom.
|
||||
|
||||
==== positional arguments
|
||||
* +'zoom'+: The zoom percentage to set.
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|<<network-accept-language,accept-language>>|Value to send in the `accept-language` header.
|
||||
|<<network-user-agent,user-agent>>|User agent to send. Empty to send the default.
|
||||
|<<network-proxy,proxy>>|The proxy to use.
|
||||
|<<network-proxy-dns-requests,proxy-dns-requests>>|Whether to send DNS requests over the configured proxy.
|
||||
|<<network-ssl-strict,ssl-strict>>|Whether to validate SSL handshakes.
|
||||
|<<network-dns-prefetch,dns-prefetch>>|Whether to try to pre-fetch DNS entries to speed up browsing.
|
||||
|==============
|
||||
@@ -490,6 +491,17 @@ Valid values:
|
||||
|
||||
Default: +pass:[system]+
|
||||
|
||||
[[network-proxy-dns-requests]]
|
||||
=== proxy-dns-requests
|
||||
Whether to send DNS requests over the configured proxy.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[network-ssl-strict]]
|
||||
=== ssl-strict
|
||||
Whether to validate SSL handshakes.
|
||||
@@ -1014,7 +1026,7 @@ The file can be in one of the following formats:
|
||||
- One host per line
|
||||
- A zip-file of any of the above, with either only one file, or a file named 'hosts' (with any extension).
|
||||
|
||||
Default: +pass:[http://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext,http://hosts-file.net/ad_servers.asp]+
|
||||
Default: +pass:[http://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext]+
|
||||
|
||||
== hints
|
||||
Hinting settings.
|
||||
|
||||
40
doc/notes
40
doc/notes
@@ -40,3 +40,43 @@ Upstream Bugs
|
||||
TODO: Report to PyQt/Qt
|
||||
|
||||
- Report some other crashes
|
||||
|
||||
|
||||
/u/angelic_sedition's thoughts
|
||||
==============================
|
||||
|
||||
Well support for greasemonkey scripts and bookmarklets/js (which was mentioned
|
||||
in the arch forum post) would be a big addition. What I've usually missed when
|
||||
using other vim-like browsers is things that allow for different settings and
|
||||
key bindings for different contexts. With that implemented I think I could
|
||||
switch to a lightweight browser (and believe me, I'd like to) for the most part
|
||||
and only use firefox when I needed downthemall or something.
|
||||
|
||||
For example, I have different bindings based on tab position that are reloaded
|
||||
with a pentadactyl autocmd so that <space><homerow keys> will take me to tab
|
||||
1-10 if I'm in that range or 2-20 if I'm in that range. I have an autocmd that
|
||||
will run on completed downloads that passes the file path to a script that will
|
||||
open ranger in a floating window with that file cut (this is basically like
|
||||
using ranger to save files instead of the crappy gui popup).
|
||||
|
||||
I also have a few bindings based on tabgroups. Tabgroups are a firefox feature,
|
||||
but I find them very useful for sorting things by topic so that only the tabs
|
||||
I'm interested at the moment are visible.
|
||||
|
||||
Pentadactyl has a feature it calls groups. You can create a group that will
|
||||
activate for sites/urls that match a pattern with some regex support. This
|
||||
allows me, for example, to set up different (more convenient) bindings for
|
||||
zooming only on images. I'll never need use the equivalent of vim n (next text
|
||||
search match), so I can bind that to zoom. This allows setting up custom
|
||||
quickmarks/gotos using the same keys for different websites. For example, on
|
||||
reddit I have different g(some key) bindings to go to different subreddits.
|
||||
This can also be used to pass certain keys directly to the site (e.g. for use
|
||||
with RES). For sites that don't have modifiable bindings, I can use this with
|
||||
pentadactyl's feedkeys or xdotool to create my own custom bindings. I even have
|
||||
a binding that will call out to bash script with different arguments depending
|
||||
on the site to download an image or an image gallery depending on the site (in
|
||||
some cases passing the url to some cli program).
|
||||
|
||||
I've also noticed the lack of completion. For example, on "o" pentadactyl will
|
||||
show sites (e.g. from history) that can be completed. I think I've been spoiled
|
||||
by pentadactyl having completion for just about everything.
|
||||
|
||||
@@ -13,6 +13,7 @@ to make yourself familiar with the keybindings: +
|
||||
image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser keybinding cheatsheet",link="http://qutebrowser.org/img/cheatsheet-big.png"]
|
||||
* If you just cloned the repository, you'll need to run
|
||||
`scripts/asciidoc2html.py` to generate the documentation.
|
||||
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it.
|
||||
* Subscribe to
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist]
|
||||
where there are weekly "what's new in qutebrowser" posts.
|
||||
|
||||
BIN
icons/qutebrowser.ico
Normal file
BIN
icons/qutebrowser.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -15,7 +15,7 @@
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.48.5 r10040"
|
||||
version="1.0"
|
||||
sodipodi:docname="qutebrowser_bindings.svg"
|
||||
sodipodi:docname="cheatsheet.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
inkscape:export-filename="/home/vav/images/xmonad/xmbindings_lg.png"
|
||||
inkscape:export-xdpi="112.5"
|
||||
@@ -32,9 +32,9 @@
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.76095916"
|
||||
inkscape:zoom="1.2432572"
|
||||
inkscape:cx="510.06077"
|
||||
inkscape:cy="311.39152"
|
||||
inkscape:cy="315.85317"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="1024px"
|
||||
@@ -2064,28 +2064,39 @@
|
||||
x="670.88574"
|
||||
sodipodi:role="line"
|
||||
id="tspan4977"
|
||||
style="font-size:8px">open</tspan></text>
|
||||
style="font-size:8px">open <tspan
|
||||
style="fill:#ff0000"
|
||||
id="tspan3697">(6)</tspan></tspan></text>
|
||||
<text
|
||||
sodipodi:linespacing="89.999998%"
|
||||
id="text10564-3"
|
||||
y="160.04776"
|
||||
y="156.04776"
|
||||
x="670.26074"
|
||||
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
xml:space="preserve"><tspan
|
||||
id="tspan10570-6"
|
||||
y="160.04776"
|
||||
y="156.04776"
|
||||
x="670.26074"
|
||||
sodipodi:role="line" /><tspan
|
||||
y="167.4301"
|
||||
y="163.4301"
|
||||
x="670.26074"
|
||||
sodipodi:role="line"
|
||||
id="tspan4996"
|
||||
style="font-size:8px">open in</tspan><tspan
|
||||
y="174.6301"
|
||||
y="170.6301"
|
||||
x="670.26074"
|
||||
sodipodi:role="line"
|
||||
id="tspan4998"
|
||||
style="font-size:8px">new tab</tspan></text>
|
||||
style="font-size:8px">new tab<tspan
|
||||
style="fill:#ff0000"
|
||||
id="tspan3699"></tspan></tspan><tspan
|
||||
y="177.83009"
|
||||
x="670.26074"
|
||||
sodipodi:role="line"
|
||||
style="font-size:8px"
|
||||
id="tspan3701"><tspan
|
||||
style="fill:#ff0000"
|
||||
id="tspan3703">(6)</tspan></tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-size:9px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:89.99999762%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:TlwgTypewriter"
|
||||
|
||||
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
13
qutebrowser.rcc
Normal file
13
qutebrowser.rcc
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>icons/qutebrowser-16x16.png</file>
|
||||
<file>icons/qutebrowser-24x24.png</file>
|
||||
<file>icons/qutebrowser-32x32.png</file>
|
||||
<file>icons/qutebrowser-48x48.png</file>
|
||||
<file>icons/qutebrowser-64x64.png</file>
|
||||
<file>icons/qutebrowser-96x96.png</file>
|
||||
<file>icons/qutebrowser-128x128.png</file>
|
||||
<file>icons/qutebrowser-256x256.png</file>
|
||||
<file>icons/qutebrowser-512x512.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,11 +24,11 @@
|
||||
import os.path
|
||||
|
||||
__author__ = "Florian Bruhin"
|
||||
__copyright__ = "Copyright 2014 Florian Bruhin (The Compiler)"
|
||||
__copyright__ = "Copyright 2014-2015 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (0, 1, 0)
|
||||
__version_info__ = (0, 1, 2)
|
||||
__version__ = '.'.join(map(str, __version_info__))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -32,11 +32,12 @@ import traceback
|
||||
import faulthandler
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
QStandardPaths, QObject, Qt)
|
||||
|
||||
import qutebrowser
|
||||
import qutebrowser.resources # pylint: disable=unused-import
|
||||
from qutebrowser.commands import cmdutils, runners
|
||||
from qutebrowser.config import style, config, websettings
|
||||
from qutebrowser.browser import quickmarks, cookies, cache, adblock
|
||||
@@ -109,11 +110,24 @@ class Application(QApplication):
|
||||
if sent:
|
||||
sys.exit(0)
|
||||
|
||||
log.init.debug("Starting IPC server...")
|
||||
try:
|
||||
ipc.init()
|
||||
except ipc.IPCError as e:
|
||||
text = ('{}\n\nMaybe another instance is running but '
|
||||
'frozen?'.format(e))
|
||||
msgbox = QMessageBox(QMessageBox.Critical, "Error while "
|
||||
"connecting to running instance!", text)
|
||||
msgbox.exec_()
|
||||
# We didn't really initialize much so far, so we just quit hard.
|
||||
sys.exit(1)
|
||||
|
||||
log.init.debug("Starting init...")
|
||||
self.setQuitOnLastWindowClosed(False)
|
||||
self.setOrganizationName("qutebrowser")
|
||||
self.setApplicationName("qutebrowser")
|
||||
self.setApplicationVersion(qutebrowser.__version__)
|
||||
self._init_icon()
|
||||
utils.actute_warning()
|
||||
try:
|
||||
self._init_modules()
|
||||
@@ -135,9 +149,6 @@ class Application(QApplication):
|
||||
log.init.debug("Applying python hacks...")
|
||||
self._python_hacks()
|
||||
|
||||
log.init.debug("Starting IPC server...")
|
||||
ipc.init()
|
||||
|
||||
QDesktopServices.setUrlHandler('http', self.open_desktopservices_url)
|
||||
QDesktopServices.setUrlHandler('https', self.open_desktopservices_url)
|
||||
QDesktopServices.setUrlHandler('qute', self.open_desktopservices_url)
|
||||
@@ -188,6 +199,17 @@ class Application(QApplication):
|
||||
main_window = objreg.get('main-window', scope='window', window=win_id)
|
||||
self.setActiveWindow(main_window)
|
||||
|
||||
def _init_icon(self):
|
||||
"""Initialize the icon of qutebrowser."""
|
||||
icon = QIcon()
|
||||
for size in (16, 24, 32, 48, 64, 96, 128, 256, 512):
|
||||
filename = ':/icons/qutebrowser-{}x{}.png'.format(size, size)
|
||||
pixmap = QPixmap(filename)
|
||||
qtutils.ensure_not_null(pixmap)
|
||||
icon.addPixmap(pixmap)
|
||||
qtutils.ensure_not_null(icon)
|
||||
self.setWindowIcon(icon)
|
||||
|
||||
def _handle_segfault(self):
|
||||
"""Handle a segfault from a previous run."""
|
||||
path = standarddir.get(QStandardPaths.DataLocation)
|
||||
@@ -448,6 +470,15 @@ class Application(QApplication):
|
||||
pass
|
||||
state_config['geometry']['mainwindow'] = geom
|
||||
|
||||
def _save_version(self):
|
||||
"""Save the current version to the state config."""
|
||||
state_config = objreg.get('state-config')
|
||||
try:
|
||||
state_config.add_section('general')
|
||||
except configparser.DuplicateSectionError:
|
||||
pass
|
||||
state_config['general']['version'] = qutebrowser.__version__
|
||||
|
||||
def _destroy_crashlogfile(self):
|
||||
"""Clean up the crash log file and delete it."""
|
||||
if self._crashlogfile is None:
|
||||
@@ -465,7 +496,7 @@ class Application(QApplication):
|
||||
except OSError:
|
||||
log.destroy.exception("Could not remove crash log!")
|
||||
|
||||
def _exception_hook(self, exctype, excvalue, tb):
|
||||
def _exception_hook(self, exctype, excvalue, tb): # noqa
|
||||
"""Handle uncaught python exceptions.
|
||||
|
||||
It'll try very hard to write all open tabs to a file, and then exit
|
||||
@@ -565,6 +596,11 @@ class Application(QApplication):
|
||||
args = [sys.executable, '-m', 'qutebrowser']
|
||||
cwd = os.path.join(os.path.abspath(os.path.dirname(
|
||||
qutebrowser.__file__)), '..')
|
||||
if not os.path.isdir(cwd):
|
||||
# Probably running from an python egg. Let's fallback to
|
||||
# cwd=None and see if that works out.
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/323
|
||||
cwd = None
|
||||
for arg in sys.argv[1:]:
|
||||
if arg.startswith('-'):
|
||||
# We only want to preserve options on a restart.
|
||||
@@ -589,15 +625,15 @@ class Application(QApplication):
|
||||
log.destroy.debug("sys.path: {}".format(sys.path))
|
||||
log.destroy.debug("sys.argv: {}".format(sys.argv))
|
||||
log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen')))
|
||||
args, cwd = self._get_restart_args(pages)
|
||||
# Open a new process and immediately shutdown the existing one
|
||||
try:
|
||||
args, cwd = self._get_restart_args(pages)
|
||||
if cwd is None:
|
||||
subprocess.Popen(args)
|
||||
else:
|
||||
subprocess.Popen(args, cwd=cwd)
|
||||
except OSError as e:
|
||||
log.destroy.error("Failed to restart: {}".format(e))
|
||||
except OSError:
|
||||
log.destroy.exception("Failed to restart")
|
||||
else:
|
||||
if shutdown:
|
||||
self.shutdown()
|
||||
@@ -740,6 +776,7 @@ class Application(QApplication):
|
||||
else:
|
||||
to_save.append(("keyconfig", key_config.save))
|
||||
to_save += [("window geometry", self._save_geometry)]
|
||||
to_save += [("version", self._save_version)]
|
||||
try:
|
||||
command_history = objreg.get('command-history')
|
||||
except KeyError:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -112,7 +112,7 @@ class HostBlocker:
|
||||
"Run :adblock-update to get adblock lists.")
|
||||
|
||||
@cmdutils.register(instance='host-blocker')
|
||||
def adblock_update(self):
|
||||
def adblock_update(self, win_id: {'special': 'win_id'}):
|
||||
"""Update the adblock block lists."""
|
||||
self.blocked_hosts = set()
|
||||
self._done_count = 0
|
||||
@@ -125,15 +125,18 @@ class HostBlocker:
|
||||
if url.scheme() == 'file':
|
||||
try:
|
||||
fileobj = open(url.path(), 'rb')
|
||||
except OSError:
|
||||
log.misc.exception("Failed to open block list!")
|
||||
except OSError as e:
|
||||
message.error(win_id, "adblock: Error while reading {}: "
|
||||
"{}".format(url.path(), e.strerror))
|
||||
continue
|
||||
download = FakeDownload(fileobj)
|
||||
self._in_progress.append(download)
|
||||
self.on_download_finished(download)
|
||||
else:
|
||||
fobj = io.BytesIO()
|
||||
fobj.name = 'adblock: ' + url.host()
|
||||
download = download_manager.get(url, fileobj=fobj)
|
||||
download = download_manager.get(url, fileobj=fobj,
|
||||
auto_remove=True)
|
||||
self._in_progress.append(download)
|
||||
download.finished.connect(
|
||||
functools.partial(self.on_download_finished, download))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -35,7 +35,7 @@ import pygments.lexers
|
||||
import pygments.formatters
|
||||
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.browser import webelem
|
||||
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
|
||||
objreg, utils)
|
||||
@@ -344,6 +344,9 @@ class CommandDispatcher:
|
||||
if preview:
|
||||
diag = QPrintPreviewDialog()
|
||||
diag.setAttribute(Qt.WA_DeleteOnClose)
|
||||
diag.setWindowFlags(diag.windowFlags() |
|
||||
Qt.WindowMaximizeButtonHint |
|
||||
Qt.WindowMinimizeButtonHint)
|
||||
diag.paintRequested.connect(tab.print)
|
||||
diag.exec_()
|
||||
else:
|
||||
@@ -611,18 +614,20 @@ class CommandDispatcher:
|
||||
tab.zoom(-count)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def zoom(self, zoom=None, count: {'special': 'count'}=None):
|
||||
def zoom(self, zoom: {'type': int}=None,
|
||||
count: {'special': 'count'}=None):
|
||||
"""Set the zoom level for the current tab.
|
||||
|
||||
The zoom can be given as argument or as [count]. If neither of both is
|
||||
given, the zoom is set to 100%.
|
||||
given, the zoom is set to the default zoom.
|
||||
|
||||
Args:
|
||||
zoom: The zoom percentage to set.
|
||||
count: The zoom percentage to set.
|
||||
"""
|
||||
try:
|
||||
level = cmdutils.arg_or_count(zoom, count, default=100)
|
||||
default = config.get('ui', 'default-zoom')
|
||||
level = cmdutils.arg_or_count(zoom, count, default=default)
|
||||
except ValueError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
tab = self._current_widget()
|
||||
@@ -917,10 +922,10 @@ class CommandDispatcher:
|
||||
topic))
|
||||
try:
|
||||
config.get(*parts)
|
||||
except config.NoSectionError:
|
||||
except configexc.NoSectionError:
|
||||
raise cmdexc.CommandError("Invalid section {}!".format(
|
||||
parts[0]))
|
||||
except config.NoOptionError:
|
||||
except configexc.NoOptionError:
|
||||
raise cmdexc.CommandError("Invalid option {}!".format(
|
||||
parts[1]))
|
||||
path = 'settings.html#{}'.format(topic.replace('->', '-'))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import os.path
|
||||
import shutil
|
||||
import functools
|
||||
@@ -171,6 +172,7 @@ class DownloadItem(QObject):
|
||||
_read_timer: A QTimer which reads the QNetworkReply into self._buffer
|
||||
periodically.
|
||||
_retry_info: A RetryInfo instance.
|
||||
_win_id: The window ID the DownloadItem runs in.
|
||||
|
||||
Signals:
|
||||
data_changed: The downloads metadata changed.
|
||||
@@ -193,7 +195,7 @@ class DownloadItem(QObject):
|
||||
redirected = pyqtSignal(QNetworkRequest, QNetworkReply)
|
||||
do_retry = pyqtSignal('QNetworkReply')
|
||||
|
||||
def __init__(self, reply, parent=None):
|
||||
def __init__(self, reply, win_id, parent=None):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
@@ -217,6 +219,7 @@ class DownloadItem(QObject):
|
||||
self.fileobj = None
|
||||
self._filename = None
|
||||
self.init_reply(reply)
|
||||
self._win_id = win_id
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, basename=self.basename)
|
||||
@@ -256,6 +259,27 @@ class DownloadItem(QObject):
|
||||
name=self.basename, speed=speed, remaining=remaining,
|
||||
perc=perc, down=down, total=total, errmsg=errmsg))
|
||||
|
||||
def _create_fileobj(self):
|
||||
"""Creates a file object using the internal filename."""
|
||||
try:
|
||||
fileobj = open(self._filename, 'wb')
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
else:
|
||||
self.set_fileobj(fileobj)
|
||||
|
||||
def _ask_overwrite_question(self):
|
||||
"""Create a Question object to be asked."""
|
||||
q = usertypes.Question(self)
|
||||
q.text = self._filename + " already exists. Overwrite? (y/n)"
|
||||
q.mode = usertypes.PromptMode.yesno
|
||||
q.answered_yes.connect(self._create_fileobj)
|
||||
q.answered_no.connect(functools.partial(self.cancel, False))
|
||||
q.cancelled.connect(functools.partial(self.cancel, False))
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.ask(q, blocking=False)
|
||||
|
||||
def _die(self, msg):
|
||||
"""Abort the download and emit an error."""
|
||||
assert not self.successful
|
||||
@@ -312,8 +336,12 @@ class DownloadItem(QObject):
|
||||
return utils.interpolate_color(
|
||||
start, stop, self.stats.percentage(), system)
|
||||
|
||||
def cancel(self):
|
||||
"""Cancel the download."""
|
||||
def cancel(self, remove_data=True):
|
||||
"""Cancel the download.
|
||||
|
||||
Args:
|
||||
remove_data: Whether to remove the downloaded data.
|
||||
"""
|
||||
log.downloads.debug("cancelled")
|
||||
self._read_timer.stop()
|
||||
self.cancelled.emit()
|
||||
@@ -325,7 +353,8 @@ class DownloadItem(QObject):
|
||||
if self.fileobj is not None:
|
||||
self.fileobj.close()
|
||||
try:
|
||||
if self._filename is not None and os.path.exists(self._filename):
|
||||
if (self._filename is not None and os.path.exists(self._filename)
|
||||
and remove_data):
|
||||
os.remove(self._filename)
|
||||
except OSError:
|
||||
log.downloads.exception("Failed to remove partial file")
|
||||
@@ -357,6 +386,10 @@ class DownloadItem(QObject):
|
||||
"existing: {}, fileobj {}".format(
|
||||
filename, self._filename, self.fileobj))
|
||||
filename = os.path.expanduser(filename)
|
||||
# Remove chars which can't be encoded in the filename encoding.
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/427
|
||||
encoding = sys.getfilesystemencoding()
|
||||
filename = utils.force_encoding(filename, encoding)
|
||||
if os.path.isabs(filename) and os.path.isdir(filename):
|
||||
# We got an absolute directory from the user, so we save it under
|
||||
# the default filename in that directory.
|
||||
@@ -376,12 +409,12 @@ class DownloadItem(QObject):
|
||||
self._filename = os.path.join(download_dir, filename)
|
||||
self.basename = filename
|
||||
log.downloads.debug("Setting filename to {}".format(filename))
|
||||
try:
|
||||
fileobj = open(self._filename, 'wb')
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
if os.path.isfile(self._filename):
|
||||
# The file already exists, so ask the user if it should be
|
||||
# overwritten.
|
||||
self._ask_overwrite_question()
|
||||
else:
|
||||
self.set_fileobj(fileobj)
|
||||
self._create_fileobj()
|
||||
|
||||
def set_fileobj(self, fileobj):
|
||||
""""Set the file object to write the download to.
|
||||
@@ -414,7 +447,6 @@ class DownloadItem(QObject):
|
||||
def finish_download(self):
|
||||
"""Write buffered data to disk and finish the QNetworkReply."""
|
||||
log.downloads.debug("Finishing download...")
|
||||
self._read_timer.stop()
|
||||
if self.reply.isOpen():
|
||||
self.fileobj.write(self.reply.readAll())
|
||||
if self.autoclose:
|
||||
@@ -438,6 +470,7 @@ class DownloadItem(QObject):
|
||||
"""
|
||||
if self.reply is None:
|
||||
return
|
||||
self._read_timer.stop()
|
||||
self.stats.finish()
|
||||
is_redirected = self._handle_redirect()
|
||||
if is_redirected:
|
||||
@@ -477,7 +510,8 @@ class DownloadItem(QObject):
|
||||
if not self.reply.isOpen():
|
||||
raise OSError("Reply is closed!")
|
||||
data = self.reply.read(1024)
|
||||
self._buffer.write(data)
|
||||
if data is not None:
|
||||
self._buffer.write(data)
|
||||
|
||||
def _handle_redirect(self):
|
||||
"""Handle a HTTP redirect.
|
||||
@@ -528,7 +562,8 @@ class DownloadManager(QAbstractListModel):
|
||||
self._win_id = win_id
|
||||
self.downloads = []
|
||||
self.questions = []
|
||||
self._networkmanager = networkmanager.NetworkManager(win_id, self)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id, None, self)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, downloads=len(self.downloads))
|
||||
@@ -556,7 +591,8 @@ class DownloadManager(QAbstractListModel):
|
||||
self.get(url, filename=dest)
|
||||
|
||||
@pyqtSlot('QUrl', 'QWebPage')
|
||||
def get(self, url, page=None, fileobj=None, filename=None):
|
||||
def get(self, url, page=None, fileobj=None, filename=None,
|
||||
auto_remove=False):
|
||||
"""Start a download with a link URL.
|
||||
|
||||
Args:
|
||||
@@ -564,6 +600,8 @@ class DownloadManager(QAbstractListModel):
|
||||
page: The QWebPage to get the download from.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
If the download could start immediately, (fileobj/filename given),
|
||||
@@ -577,9 +615,10 @@ class DownloadManager(QAbstractListModel):
|
||||
urlutils.invalid_url_error(self._win_id, url, "start download")
|
||||
return
|
||||
req = QNetworkRequest(url)
|
||||
return self.get_request(req, page, fileobj, filename)
|
||||
return self.get_request(req, page, fileobj, filename, auto_remove)
|
||||
|
||||
def get_request(self, request, page=None, fileobj=None, filename=None):
|
||||
def get_request(self, request, page=None, fileobj=None, filename=None,
|
||||
auto_remove=False):
|
||||
"""Start a download with a QNetworkRequest.
|
||||
|
||||
Args:
|
||||
@@ -587,6 +626,8 @@ class DownloadManager(QAbstractListModel):
|
||||
page: The QWebPage to use.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
If the download could start immediately, (fileobj/filename given),
|
||||
@@ -601,17 +642,23 @@ class DownloadManager(QAbstractListModel):
|
||||
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
|
||||
QNetworkRequest.AlwaysNetwork)
|
||||
if fileobj is not None or filename is not None:
|
||||
return self.fetch_request(request, filename, fileobj, page)
|
||||
return self.fetch_request(request, filename, fileobj, page,
|
||||
auto_remove)
|
||||
q = self._prepare_question()
|
||||
q.default = urlutils.filename_from_url(request.url())
|
||||
filename = urlutils.filename_from_url(request.url())
|
||||
encoding = sys.getfilesystemencoding()
|
||||
filename = utils.force_encoding(filename, encoding)
|
||||
q.default = filename
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
q.answered.connect(
|
||||
lambda fn: self.fetch_request(request, filename=fn, page=page))
|
||||
lambda fn: self.fetch_request(request, filename=fn, page=page,
|
||||
auto_remove=auto_remove))
|
||||
message_bridge.ask(q, blocking=False)
|
||||
return None
|
||||
|
||||
def fetch_request(self, request, page=None, fileobj=None, filename=None):
|
||||
def fetch_request(self, request, page=None, fileobj=None, filename=None,
|
||||
auto_remove=False):
|
||||
"""Download a QNetworkRequest to disk.
|
||||
|
||||
Args:
|
||||
@@ -619,6 +666,8 @@ class DownloadManager(QAbstractListModel):
|
||||
page: The QWebPage to use.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
@@ -628,16 +677,18 @@ class DownloadManager(QAbstractListModel):
|
||||
else:
|
||||
nam = page.networkAccessManager()
|
||||
reply = nam.get(request)
|
||||
return self.fetch(reply, fileobj, filename)
|
||||
return self.fetch(reply, fileobj, filename, auto_remove)
|
||||
|
||||
@pyqtSlot('QNetworkReply')
|
||||
def fetch(self, reply, fileobj=None, filename=None):
|
||||
def fetch(self, reply, fileobj=None, filename=None, auto_remove=False):
|
||||
"""Download a QNetworkReply to disk.
|
||||
|
||||
Args:
|
||||
reply: The QNetworkReply to download.
|
||||
fileobj: The file object to write the answer to.
|
||||
filename: A path to write the data to.
|
||||
auto_remove: Whether to remove the download even if
|
||||
ui -> remove-finished-downloads is set to false.
|
||||
|
||||
Return:
|
||||
The created DownloadItem.
|
||||
@@ -652,10 +703,10 @@ class DownloadManager(QAbstractListModel):
|
||||
_inline, suggested_filename = http.parse_content_disposition(reply)
|
||||
log.downloads.debug("fetch: {} -> {}".format(reply.url(),
|
||||
suggested_filename))
|
||||
download = DownloadItem(reply, self)
|
||||
download = DownloadItem(reply, self._win_id, self)
|
||||
download.cancelled.connect(
|
||||
functools.partial(self.remove_item, download))
|
||||
if config.get('ui', 'remove-finished-downloads'):
|
||||
if config.get('ui', 'remove-finished-downloads') or auto_remove:
|
||||
download.finished.connect(
|
||||
functools.partial(self.remove_item, download))
|
||||
download.data_changed.connect(
|
||||
@@ -677,6 +728,9 @@ class DownloadManager(QAbstractListModel):
|
||||
download.autoclose = False
|
||||
else:
|
||||
q = self._prepare_question()
|
||||
encoding = sys.getfilesystemencoding()
|
||||
suggested_filename = utils.force_encoding(suggested_filename,
|
||||
encoding)
|
||||
q.default = suggested_filename
|
||||
q.answered.connect(download.set_filename)
|
||||
q.cancelled.connect(download.cancel)
|
||||
@@ -748,6 +802,17 @@ class DownloadManager(QAbstractListModel):
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_clear(self):
|
||||
"""Check if there are finished downloads to clear."""
|
||||
if self.downloads:
|
||||
return any(download.done for download in self.downloads)
|
||||
else:
|
||||
return False
|
||||
|
||||
def clear(self):
|
||||
"""Remove all finished downloads."""
|
||||
self.remove_items(d for d in self.downloads if d.done)
|
||||
|
||||
def last_index(self):
|
||||
"""Get the last index in the model.
|
||||
|
||||
@@ -769,6 +834,34 @@ class DownloadManager(QAbstractListModel):
|
||||
self.endRemoveRows()
|
||||
download.deleteLater()
|
||||
|
||||
def remove_items(self, downloads):
|
||||
"""Remove an iterable of downloads."""
|
||||
# On the first pass, we only generate the indices so we get the
|
||||
# first/last one for beginRemoveRows.
|
||||
indices = []
|
||||
# We need to iterate over downloads twice, which won't work if it's a
|
||||
# generator.
|
||||
downloads = list(downloads)
|
||||
for download in downloads:
|
||||
try:
|
||||
indices.append(self.downloads.index(download))
|
||||
except ValueError:
|
||||
# already removed
|
||||
pass
|
||||
if not indices:
|
||||
return
|
||||
indices.sort()
|
||||
self.beginRemoveRows(QModelIndex(), indices[0], indices[-1])
|
||||
for download in downloads:
|
||||
try:
|
||||
self.downloads.remove(download)
|
||||
except ValueError:
|
||||
# already removed
|
||||
pass
|
||||
else:
|
||||
download.deleteLater()
|
||||
self.endRemoveRows()
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
"""Simple constant header."""
|
||||
if (section == 0 and orientation == Qt.Horizontal and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -93,6 +93,7 @@ class DownloadView(QListView):
|
||||
self.setWrapping(True)
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.show_context_menu)
|
||||
self.clicked.connect(self.on_clicked)
|
||||
|
||||
def __repr__(self):
|
||||
model = self.model()
|
||||
@@ -102,28 +103,66 @@ class DownloadView(QListView):
|
||||
count = model.rowCount()
|
||||
return utils.get_repr(self, count=count)
|
||||
|
||||
@pyqtSlot('QModelIndex')
|
||||
def on_clicked(self, index):
|
||||
"""Handle clicking of an item.
|
||||
|
||||
Args:
|
||||
index: The QModelIndex of the clicked item.
|
||||
"""
|
||||
if not index.isValid():
|
||||
return
|
||||
item = self.model().data(index, downloads.ModelRole.item)
|
||||
if item.done and item.successful:
|
||||
item.open_file()
|
||||
self.model().remove_item(item)
|
||||
|
||||
def _get_menu_actions(self, item):
|
||||
"""Get the available context menu actions for a given DownloadItem.
|
||||
|
||||
Args:
|
||||
item: The DownloadItem to get the actions for, or None.
|
||||
|
||||
Return:
|
||||
A list of either:
|
||||
- (QAction, callable) tuples.
|
||||
- (None, None) for a seperator
|
||||
"""
|
||||
actions = []
|
||||
if item is None:
|
||||
pass
|
||||
elif item.done:
|
||||
if item.successful:
|
||||
actions.append(("Open", item.open_file))
|
||||
else:
|
||||
actions.append(("Retry", item.retry))
|
||||
actions.append(("Remove",
|
||||
functools.partial(self.model().remove_item, item)))
|
||||
else:
|
||||
actions.append(("Cancel", item.cancel))
|
||||
if self.model().can_clear():
|
||||
actions.append((None, None))
|
||||
actions.append(("Remove all finished", self.model().clear))
|
||||
return actions
|
||||
|
||||
@pyqtSlot('QPoint')
|
||||
def show_context_menu(self, point):
|
||||
"""Show the context menu."""
|
||||
index = self.indexAt(point)
|
||||
if not index.isValid():
|
||||
return
|
||||
item = self.model().data(index, downloads.ModelRole.item)
|
||||
self._menu = QMenu(self)
|
||||
if item.done:
|
||||
if item.successful:
|
||||
open_action = self._menu.addAction("Open")
|
||||
open_action.triggered.connect(item.open_file)
|
||||
else:
|
||||
retry_action = self._menu.addAction("Retry")
|
||||
retry_action.triggered.connect(item.retry)
|
||||
remove = self._menu.addAction("Remove")
|
||||
remove.triggered.connect(functools.partial(
|
||||
self.model().remove_item, item))
|
||||
if index.isValid():
|
||||
item = self.model().data(index, downloads.ModelRole.item)
|
||||
else:
|
||||
cancel = self._menu.addAction("Cancel")
|
||||
cancel.triggered.connect(item.cancel)
|
||||
self._menu.popup(self.viewport().mapToGlobal(point))
|
||||
item = None
|
||||
self._menu = QMenu(self)
|
||||
actions = self._get_menu_actions(item)
|
||||
for (name, handler) in actions:
|
||||
if name is None and handler is None:
|
||||
self._menu.addSeparator()
|
||||
else:
|
||||
action = self._menu.addAction(name)
|
||||
action.triggered.connect(handler)
|
||||
if actions:
|
||||
self._menu.popup(self.viewport().mapToGlobal(point))
|
||||
|
||||
def minimumSizeHint(self):
|
||||
"""Override minimumSizeHint so the size is correct in a layout."""
|
||||
@@ -132,9 +171,12 @@ class DownloadView(QListView):
|
||||
def sizeHint(self):
|
||||
"""Return sizeHint based on the view contents."""
|
||||
idx = self.model().last_index()
|
||||
height = self.visualRect(idx).bottom()
|
||||
if height != -1:
|
||||
size = QSize(0, height + 2)
|
||||
bottom = self.visualRect(idx).bottom()
|
||||
if bottom != -1:
|
||||
margins = self.contentsMargins()
|
||||
height = (bottom + margins.top() + margins.bottom() +
|
||||
2 * self.spacing())
|
||||
size = QSize(0, height)
|
||||
else:
|
||||
size = QSize(0, 0)
|
||||
qtutils.ensure_valid(size)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -267,6 +267,14 @@ class HintManager(QObject):
|
||||
display = elem.styleProperty('display', QWebElement.InlineStyle)
|
||||
return display == 'none'
|
||||
|
||||
def _show_elem(self, elem):
|
||||
"""Show a given element."""
|
||||
elem.setStyleProperty('display', 'inline !important')
|
||||
|
||||
def _hide_elem(self, elem):
|
||||
"""Hide a given element."""
|
||||
elem.setStyleProperty('display', 'none !important')
|
||||
|
||||
def _set_style_properties(self, elem, label):
|
||||
"""Set the hint CSS on the element given.
|
||||
|
||||
@@ -275,23 +283,23 @@ class HintManager(QObject):
|
||||
label: The label QWebElement.
|
||||
"""
|
||||
attrs = [
|
||||
('display', 'inline'),
|
||||
('z-index', '100000'),
|
||||
('pointer-events', 'none'),
|
||||
('position', 'absolute'),
|
||||
('color', config.get('colors', 'hints.fg')),
|
||||
('background', config.get('colors', 'hints.bg')),
|
||||
('font', config.get('fonts', 'hints')),
|
||||
('border', config.get('hints', 'border')),
|
||||
('opacity', str(config.get('hints', 'opacity'))),
|
||||
('display', 'inline !important'),
|
||||
('z-index', '100000 !important'),
|
||||
('pointer-events', 'none !important'),
|
||||
('position', 'absolute !important'),
|
||||
('color', config.get('colors', 'hints.fg') + ' !important'),
|
||||
('background', config.get('colors', 'hints.bg') + ' !important'),
|
||||
('font', config.get('fonts', 'hints') + ' !important'),
|
||||
('border', config.get('hints', 'border') + ' !important'),
|
||||
('opacity', str(config.get('hints', 'opacity')) + ' !important'),
|
||||
]
|
||||
|
||||
# Make text uppercase if set in config
|
||||
if (config.get('hints', 'uppercase') and
|
||||
config.get('hints', 'mode') == 'letter'):
|
||||
attrs.append(('texttransform', 'uppercase'))
|
||||
attrs.append(('text-transform', 'uppercase !important'))
|
||||
else:
|
||||
attrs.append(('texttransform', 'none'))
|
||||
attrs.append(('text-transform', 'none !important'))
|
||||
|
||||
for k, v in attrs:
|
||||
label.setStyleProperty(k, v)
|
||||
@@ -313,8 +321,8 @@ class HintManager(QObject):
|
||||
top /= zoom
|
||||
log.hints.vdebug("Drawing label '{!r}' at {}/{} for element '{!r}', "
|
||||
"zoom level {}".format(label, left, top, elem, zoom))
|
||||
label.setStyleProperty('left', '{}px'.format(left))
|
||||
label.setStyleProperty('top', '{}px'.format(top))
|
||||
label.setStyleProperty('left', '{}px !important'.format(left))
|
||||
label.setStyleProperty('top', '{}px !important'.format(top))
|
||||
|
||||
def _draw_label(self, elem, string):
|
||||
"""Draw a hint label over an element.
|
||||
@@ -671,7 +679,7 @@ class HintManager(QObject):
|
||||
raise cmdexc.CommandError("No frame focused!")
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self._win_id)
|
||||
if usertypes.KeyMode.hint in mode_manager.mode_stack:
|
||||
if mode_manager.mode == usertypes.KeyMode.hint:
|
||||
raise cmdexc.CommandError("Already hinting!")
|
||||
self._check_args(target, *args)
|
||||
self._context = HintContext()
|
||||
@@ -689,11 +697,8 @@ class HintManager(QObject):
|
||||
window=self._win_id)
|
||||
message_bridge.set_text(self.HINT_TEXTS[target])
|
||||
self._connect_frame_signals()
|
||||
try:
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
except modeman.ModeLockedError:
|
||||
self._cleanup()
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.hint,
|
||||
'HintManager.start')
|
||||
|
||||
def handle_partial_key(self, keystr):
|
||||
"""Handle a new partial keypress."""
|
||||
@@ -709,10 +714,10 @@ class HintManager(QObject):
|
||||
match_color, matched, rest))
|
||||
if self._is_hidden(elems.label):
|
||||
# hidden element which matches again -> unhide it
|
||||
elems.label.setStyleProperty('display', 'inline')
|
||||
self._show_elem(elems.label)
|
||||
else:
|
||||
# element doesn't match anymore -> hide it
|
||||
elems.label.setStyleProperty('display', 'none')
|
||||
self._hide_elem(elems.label)
|
||||
except webelem.IsNullError:
|
||||
pass
|
||||
|
||||
@@ -728,10 +733,10 @@ class HintManager(QObject):
|
||||
str(elems.elem).lower().startswith(filterstr)):
|
||||
if self._is_hidden(elems.label):
|
||||
# hidden element which matches again -> unhide it
|
||||
elems.label.setStyleProperty('display', 'inline')
|
||||
self._show_elem(elems.label)
|
||||
else:
|
||||
# element doesn't match anymore -> hide it
|
||||
elems.label.setStyleProperty('display', 'none')
|
||||
self._hide_elem(elems.label)
|
||||
except webelem.IsNullError:
|
||||
pass
|
||||
visible = {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -39,14 +39,15 @@ def parse_content_disposition(reply):
|
||||
"""
|
||||
is_inline = True
|
||||
filename = None
|
||||
content_disposition_header = 'Content-Disposition'.encode('iso-8859-1')
|
||||
# First check if the Content-Disposition header has a filename
|
||||
# attribute.
|
||||
if reply.hasRawHeader('Content-Disposition'):
|
||||
if reply.hasRawHeader(content_disposition_header):
|
||||
# We use the unsafe variant of the filename as we sanitize it via
|
||||
# os.path.basename later.
|
||||
try:
|
||||
content_disposition = rfc6266.parse_headers(
|
||||
bytes(reply.rawHeader('Content-Disposition')))
|
||||
bytes(reply.rawHeader(content_disposition_header)))
|
||||
filename = content_disposition.filename()
|
||||
except UnicodeDecodeError:
|
||||
log.misc.exception("Error while decoding filename")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -47,6 +47,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
_scheme_handlers: A dictionary (scheme -> handler) of supported custom
|
||||
schemes.
|
||||
_win_id: The window ID this NetworkManager is associated with.
|
||||
_tab_id: The tab ID this NetworkManager is associated with.
|
||||
|
||||
Signals:
|
||||
shutting_down: Emitted when the QNAM is shutting down.
|
||||
@@ -54,7 +55,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
|
||||
shutting_down = pyqtSignal()
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
def __init__(self, win_id, tab_id, parent=None):
|
||||
log.init.debug("Initializing NetworkManager")
|
||||
with log.disable_qt_msghandler():
|
||||
# WORKAROUND for a hang when a message is printed - See:
|
||||
@@ -62,6 +63,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
super().__init__(parent)
|
||||
log.init.debug("NetworkManager init done")
|
||||
self._win_id = win_id
|
||||
self._tab_id = tab_id
|
||||
self._requests = []
|
||||
self._scheme_handlers = {
|
||||
'qute': qutescheme.QuteSchemeHandler(win_id),
|
||||
@@ -105,13 +107,15 @@ class NetworkManager(QNetworkAccessManager):
|
||||
self.setCache(cache)
|
||||
cache.setParent(app)
|
||||
|
||||
def _ask(self, win_id, text, mode):
|
||||
def _ask(self, win_id, text, mode, owner=None):
|
||||
"""Ask a blocking question in the statusbar.
|
||||
|
||||
Args:
|
||||
win_id: The ID of the window which is calling this function.
|
||||
text: The text to display to the user.
|
||||
mode: A PromptMode.
|
||||
owner: An object which will abort the question if destroyed, or
|
||||
None.
|
||||
|
||||
Return:
|
||||
The answer the user gave or None if the prompt was cancelled.
|
||||
@@ -120,6 +124,11 @@ class NetworkManager(QNetworkAccessManager):
|
||||
q.text = text
|
||||
q.mode = mode
|
||||
self.shutting_down.connect(q.abort)
|
||||
if owner is not None:
|
||||
owner.destroyed.connect(q.abort)
|
||||
webview = objreg.get('webview', scope='tab', window=self._win_id,
|
||||
tab=self._tab_id)
|
||||
webview.loadStarted.connect(q.abort)
|
||||
bridge = objreg.get('message-bridge', scope='window', window=win_id)
|
||||
bridge.ask(q, blocking=True)
|
||||
q.deleteLater()
|
||||
@@ -158,7 +167,8 @@ class NetworkManager(QNetworkAccessManager):
|
||||
err_string = '\n'.join('- ' + err.errorString() for err in errors)
|
||||
answer = self._ask(self._win_id,
|
||||
'SSL errors - continue?\n{}'.format(err_string),
|
||||
mode=usertypes.PromptMode.yesno)
|
||||
mode=usertypes.PromptMode.yesno,
|
||||
owner=reply)
|
||||
if answer:
|
||||
reply.ignoreSslErrors()
|
||||
elif ssl_strict:
|
||||
@@ -172,11 +182,12 @@ class NetworkManager(QNetworkAccessManager):
|
||||
reply.ignoreSslErrors()
|
||||
|
||||
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
||||
def on_authentication_required(self, _reply, authenticator):
|
||||
def on_authentication_required(self, reply, authenticator):
|
||||
"""Called when a website needs authentication."""
|
||||
answer = self._ask(self._win_id,
|
||||
"Username ({}):".format(authenticator.realm()),
|
||||
mode=usertypes.PromptMode.user_pwd)
|
||||
mode=usertypes.PromptMode.user_pwd,
|
||||
owner=reply)
|
||||
self._fill_authenticator(authenticator, answer)
|
||||
|
||||
@pyqtSlot('QNetworkProxy', 'QAuthenticator')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -20,7 +20,7 @@
|
||||
"""Handling of proxies."""
|
||||
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkProxyFactory
|
||||
from PyQt5.QtNetwork import QNetworkProxy, QNetworkProxyFactory
|
||||
|
||||
from qutebrowser.config import config, configtypes
|
||||
|
||||
@@ -45,6 +45,15 @@ class ProxyFactory(QNetworkProxyFactory):
|
||||
"""
|
||||
proxy = config.get('network', 'proxy')
|
||||
if proxy is configtypes.SYSTEM_PROXY:
|
||||
return QNetworkProxyFactory.systemProxyForQuery(query)
|
||||
proxies = QNetworkProxyFactory.systemProxyForQuery(query)
|
||||
else:
|
||||
return [proxy]
|
||||
proxies = [proxy]
|
||||
for p in proxies:
|
||||
if p.type() != QNetworkProxy.NoProxy:
|
||||
capabilities = p.capabilities()
|
||||
if config.get('network', 'proxy-dns-requests'):
|
||||
capabilities |= QNetworkProxy.HostNameLookupCapability
|
||||
else:
|
||||
capabilities &= ~QNetworkProxy.HostNameLookupCapability
|
||||
p.setCapabilities(capabilities)
|
||||
return proxies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -27,6 +27,8 @@ Module attributes:
|
||||
pyeval_output: The output of the last :pyeval command.
|
||||
"""
|
||||
|
||||
import configparser
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QObject
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
@@ -34,7 +36,7 @@ import qutebrowser
|
||||
from qutebrowser.browser.network import schemehandler, networkreply
|
||||
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
|
||||
objreg)
|
||||
from qutebrowser.config import configtypes, configdata
|
||||
from qutebrowser.config import configexc, configdata
|
||||
|
||||
|
||||
pyeval_output = ":pyeval was never called"
|
||||
@@ -93,7 +95,7 @@ class JSBridge(QObject):
|
||||
"""Slot to set a setting from qute:settings."""
|
||||
try:
|
||||
objreg.get('config').set('conf', sectname, optname, value)
|
||||
except configtypes.ValidationError as e:
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
message.error(win_id, e)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -47,7 +47,7 @@ class BrowserPage(QWebPage):
|
||||
_ignore_load_started: Whether to ignore the next loadStarted signal.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
def __init__(self, win_id, tab_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._extension_handlers = {
|
||||
@@ -56,7 +56,8 @@ class BrowserPage(QWebPage):
|
||||
}
|
||||
self._ignore_load_started = False
|
||||
self.error_occured = False
|
||||
self._networkmanager = networkmanager.NetworkManager(win_id, self)
|
||||
self._networkmanager = networkmanager.NetworkManager(
|
||||
win_id, tab_id, self)
|
||||
self.setNetworkAccessManager(self._networkmanager)
|
||||
self.setForwardUnsupportedContent(True)
|
||||
self.printRequested.connect(self.on_print_requested)
|
||||
@@ -72,8 +73,8 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def javaScriptPrompt(self, _frame, msg, default):
|
||||
"""Override javaScriptPrompt to use the statusbar."""
|
||||
answer = message.ask(self._win_id, "js: {}".format(msg),
|
||||
usertypes.PromptMode.text, default)
|
||||
answer = self._ask("js: {}".format(msg), usertypes.PromptMode.text,
|
||||
default)
|
||||
if answer is None:
|
||||
return (False, "")
|
||||
else:
|
||||
@@ -157,6 +158,28 @@ class BrowserPage(QWebPage):
|
||||
suggested_file)
|
||||
return True
|
||||
|
||||
def _ask(self, text, mode, default=None):
|
||||
"""Ask a blocking question in the statusbar.
|
||||
|
||||
Args:
|
||||
text: The text to display to the user.
|
||||
mode: A PromptMode.
|
||||
default: The default value to display.
|
||||
|
||||
Return:
|
||||
The answer the user gave or None if the prompt was cancelled.
|
||||
"""
|
||||
q = usertypes.Question()
|
||||
q.text = text
|
||||
q.mode = mode
|
||||
q.default = default
|
||||
self.loadStarted.connect(q.abort)
|
||||
bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
bridge.ask(q, blocking=True)
|
||||
q.deleteLater()
|
||||
return q.answer
|
||||
|
||||
def display_content(self, reply, mimetype):
|
||||
"""Display a QNetworkReply with an explicitely set mimetype."""
|
||||
self.mainFrame().setContent(reply.readAll(), mimetype, reply.url())
|
||||
@@ -268,13 +291,12 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def javaScriptAlert(self, _frame, msg):
|
||||
"""Override javaScriptAlert to use the statusbar."""
|
||||
message.ask(self._win_id, "[js alert] {}".format(msg),
|
||||
usertypes.PromptMode.alert)
|
||||
self._ask("[js alert] {}".format(msg), usertypes.PromptMode.alert)
|
||||
|
||||
def javaScriptConfirm(self, _frame, msg):
|
||||
"""Override javaScriptConfirm to use the statusbar."""
|
||||
ans = message.ask(self._win_id, "[js confirm] {}".format(msg),
|
||||
usertypes.PromptMode.yesno)
|
||||
ans = self._ask("[js confirm] {}".format(msg),
|
||||
usertypes.PromptMode.yesno)
|
||||
return bool(ans)
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, line, source):
|
||||
@@ -293,8 +315,8 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def shouldInterruptJavaScript(self):
|
||||
"""Override shouldInterruptJavaScript to use the statusbar."""
|
||||
answer = message.ask(self._win_id, "Interrupt long-running "
|
||||
"javascript?", usertypes.PromptMode.yesno)
|
||||
answer = self._ask("Interrupt long-running javascript?",
|
||||
usertypes.PromptMode.yesno)
|
||||
if answer is None:
|
||||
answer = True
|
||||
return answer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,10 +19,12 @@
|
||||
|
||||
"""The main browser widgets."""
|
||||
|
||||
import sys
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWidgets import QApplication, QStyleFactory
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
|
||||
@@ -65,6 +67,7 @@ class WebView(QWebView):
|
||||
_force_open_target: Override for open_target.
|
||||
_check_insertmode: If True, in mouseReleaseEvent we should check if we
|
||||
need to enter/leave insert mode.
|
||||
_default_zoom_changed: Whether the zoom was changed from the default.
|
||||
_win_id: The window ID of the view.
|
||||
|
||||
Signals:
|
||||
@@ -83,6 +86,10 @@ class WebView(QWebView):
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
if sys.platform == 'darwin' and qtutils.version_check('5.4'):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/462
|
||||
self.setStyle(QStyleFactory.create('Fusion'))
|
||||
self._win_id = win_id
|
||||
self.load_status = LoadStatus.none
|
||||
self._check_insertmode = False
|
||||
@@ -95,7 +102,13 @@ class WebView(QWebView):
|
||||
self._zoom = None
|
||||
self._has_ssl_errors = False
|
||||
self.init_neighborlist()
|
||||
objreg.get('config').changed.connect(self.init_neighborlist)
|
||||
cfg = objreg.get('config')
|
||||
cfg.changed.connect(self.init_neighborlist)
|
||||
# For some reason, this signal doesn't get disconnected automatically
|
||||
# when the WebView is destroyed on older PyQt versions.
|
||||
# See https://github.com/The-Compiler/qutebrowser/issues/390
|
||||
self.destroyed.connect(functools.partial(
|
||||
cfg.changed.disconnect, self.init_neighborlist))
|
||||
self._cur_url = None
|
||||
self.cur_url = QUrl()
|
||||
self.progress = 0
|
||||
@@ -105,19 +118,20 @@ class WebView(QWebView):
|
||||
window=win_id)
|
||||
tab_registry[self.tab_id] = self
|
||||
objreg.register('webview', self, registry=self.registry)
|
||||
page = webpage.BrowserPage(win_id, self)
|
||||
page = webpage.BrowserPage(win_id, self.tab_id, self)
|
||||
self.setPage(page)
|
||||
hintmanager = hints.HintManager(win_id, self.tab_id, self)
|
||||
hintmanager.mouse_event.connect(self.on_mouse_event)
|
||||
hintmanager.set_open_target.connect(self.set_force_open_target)
|
||||
objreg.register('hintmanager', hintmanager, registry=self.registry)
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=win_id)
|
||||
mode_manager.entered.connect(self.on_mode_entered)
|
||||
mode_manager.left.connect(self.on_mode_left)
|
||||
page.linkHovered.connect(self.linkHovered)
|
||||
page.mainFrame().loadStarted.connect(self.on_load_started)
|
||||
self.urlChanged.connect(self.on_url_changed)
|
||||
page.mainFrame().loadFinished.connect(self.on_load_finished)
|
||||
page.scrollRequested.connect(self.on_scroll_requested)
|
||||
# Calculate scroll position once
|
||||
self.on_scroll_requested()
|
||||
self.loadProgress.connect(lambda p: setattr(self, 'progress', p))
|
||||
self.page().statusBarMessage.connect(
|
||||
lambda msg: setattr(self, 'statusbar_message', msg))
|
||||
@@ -125,6 +139,8 @@ class WebView(QWebView):
|
||||
lambda *args: setattr(self, '_has_ssl_errors', True))
|
||||
self.viewing_source = False
|
||||
self.setZoomFactor(float(config.get('ui', 'default-zoom')) / 100)
|
||||
self._default_zoom_changed = False
|
||||
objreg.get('config').changed.connect(self.on_config_changed)
|
||||
|
||||
def __repr__(self):
|
||||
url = utils.elide(self.url().toDisplayString(), 50)
|
||||
@@ -155,6 +171,10 @@ class WebView(QWebView):
|
||||
def on_config_changed(self, section, option):
|
||||
"""Reinitialize the zoom neighborlist if related config changed."""
|
||||
if section == 'ui' and option in ('zoom-levels', 'default-zoom'):
|
||||
if not self._default_zoom_changed:
|
||||
self.setZoomFactor(float(config.get('ui', 'default-zoom')) /
|
||||
100)
|
||||
self._default_zoom_changed = False
|
||||
self.init_neighborlist()
|
||||
|
||||
def init_neighborlist(self):
|
||||
@@ -224,8 +244,8 @@ class WebView(QWebView):
|
||||
if ((hitresult.isContentEditable() and elem.is_writable()) or
|
||||
elem.is_editable()):
|
||||
log.mouse.debug("Clicked editable element!")
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'click')
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.insert, 'click',
|
||||
only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
@@ -244,8 +264,8 @@ class WebView(QWebView):
|
||||
return
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element (delayed)!")
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed')
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'click-delayed', only_if_normal=True)
|
||||
else:
|
||||
log.mouse.debug("Clicked non-editable element (delayed)!")
|
||||
if config.get('input', 'auto-leave-insert-mode'):
|
||||
@@ -331,6 +351,7 @@ class WebView(QWebView):
|
||||
raise cmdexc.CommandError("Can't zoom {}%!".format(perc))
|
||||
self.setZoomFactor(float(perc) / 100)
|
||||
message.info(self._win_id, "Zoom level: {}%".format(perc))
|
||||
self._default_zoom_changed = True
|
||||
|
||||
def zoom(self, offset):
|
||||
"""Increase/Decrease the zoom level.
|
||||
@@ -385,7 +406,7 @@ class WebView(QWebView):
|
||||
return
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self._win_id)
|
||||
cur_mode = mode_manager.mode()
|
||||
cur_mode = mode_manager.mode
|
||||
if cur_mode == usertypes.KeyMode.insert or not ok:
|
||||
return
|
||||
frame = self.page().currentFrame()
|
||||
@@ -396,32 +417,26 @@ class WebView(QWebView):
|
||||
return
|
||||
log.modes.debug("focus element: {}".format(repr(elem)))
|
||||
if elem.is_editable():
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'load finished')
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.insert,
|
||||
'load finished', only_if_normal=True)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_scroll_requested(self):
|
||||
"""Recalculate scroll percentage when the page got scrolled.
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_entered(self, mode):
|
||||
"""Ignore attempts to focus the widget if in any status-input mode."""
|
||||
if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno):
|
||||
log.webview.debug("Ignoring focus because mode {} was "
|
||||
"entered.".format(mode))
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
|
||||
If necessary, we emit scroll_pos_changed so the statusbar percentage
|
||||
updates.
|
||||
"""
|
||||
QTimer.singleShot(0, self.update_scroll_perc)
|
||||
|
||||
@pyqtSlot()
|
||||
def update_scroll_perc(self):
|
||||
"""Update the scroll position after on_scroll_requested."""
|
||||
frame = self.page().mainFrame()
|
||||
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
||||
frame.scrollBarValue(Qt.Vertical))
|
||||
if self._old_scroll_pos != new_pos:
|
||||
self._old_scroll_pos = new_pos
|
||||
m = (frame.scrollBarMaximum(Qt.Horizontal),
|
||||
frame.scrollBarMaximum(Qt.Vertical))
|
||||
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
||||
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
||||
self.scroll_pos = perc
|
||||
self.scroll_pos_changed.emit(*perc)
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
"""Restore focus policy if status-input modes were left."""
|
||||
if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno):
|
||||
log.webview.debug("Restoring focus policy because mode {} was "
|
||||
"left.".format(mode))
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_force_open_target(self, target):
|
||||
@@ -460,6 +475,33 @@ class WebView(QWebView):
|
||||
window=self._win_id)
|
||||
return tabbed_browser.tabopen(background=False)
|
||||
|
||||
def paintEvent(self, e):
|
||||
"""Extend paintEvent to emit a signal if the scroll position changed.
|
||||
|
||||
This is a bit of a hack: We listen to repaint requests here, in the
|
||||
hope a repaint will always be requested when scrolling, and if the
|
||||
scroll position actually changed, we emit a signal.
|
||||
|
||||
Args:
|
||||
e: The QPaintEvent.
|
||||
|
||||
Return:
|
||||
The superclass event return value.
|
||||
"""
|
||||
frame = self.page().mainFrame()
|
||||
new_pos = (frame.scrollBarValue(Qt.Horizontal),
|
||||
frame.scrollBarValue(Qt.Vertical))
|
||||
if self._old_scroll_pos != new_pos:
|
||||
self._old_scroll_pos = new_pos
|
||||
m = (frame.scrollBarMaximum(Qt.Horizontal),
|
||||
frame.scrollBarMaximum(Qt.Vertical))
|
||||
perc = (round(100 * new_pos[0] / m[0]) if m[0] != 0 else 0,
|
||||
round(100 * new_pos[1] / m[1]) if m[1] != 0 else 0)
|
||||
self.scroll_pos = perc
|
||||
self.scroll_pos_changed.emit(*perc)
|
||||
# Let superclass handle the event
|
||||
super().paintEvent(e)
|
||||
|
||||
def mousePressEvent(self, e):
|
||||
"""Extend QWidget::mousePressEvent().
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -106,7 +106,7 @@ class Command:
|
||||
"""
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=win_id)
|
||||
curmode = mode_manager.mode()
|
||||
curmode = mode_manager.mode
|
||||
if self._modes is not None and curmode not in self._modes:
|
||||
mode_names = '/'.join(mode.name for mode in self._modes)
|
||||
raise cmdexc.PrerequisitesError(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,7 +24,7 @@ import re
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl
|
||||
from PyQt5.QtWebKitWidgets import QWebPage
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.config import config, configexc
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import message, log, utils, objreg
|
||||
from qutebrowser.misc import split
|
||||
@@ -166,12 +166,11 @@ class CommandRunner(QObject):
|
||||
self._args = []
|
||||
self._win_id = win_id
|
||||
|
||||
def _get_alias(self, text, alias_no_args):
|
||||
def _get_alias(self, text):
|
||||
"""Get an alias from the config.
|
||||
|
||||
Args:
|
||||
text: The text to parse.
|
||||
alias_no_args: Whether to apply an alias if there are no arguments.
|
||||
|
||||
Return:
|
||||
None if no alias was found.
|
||||
@@ -180,21 +179,17 @@ class CommandRunner(QObject):
|
||||
parts = text.strip().split(maxsplit=1)
|
||||
try:
|
||||
alias = config.get('aliases', parts[0])
|
||||
except (config.NoOptionError, config.NoSectionError):
|
||||
except (configexc.NoOptionError, configexc.NoSectionError):
|
||||
return None
|
||||
try:
|
||||
new_cmd = '{} {}'.format(alias, parts[1])
|
||||
except IndexError:
|
||||
if alias_no_args:
|
||||
new_cmd = alias
|
||||
else:
|
||||
new_cmd = parts[0]
|
||||
new_cmd = alias
|
||||
if text.endswith(' '):
|
||||
new_cmd += ' '
|
||||
return new_cmd
|
||||
|
||||
def parse(self, text, aliases=True, fallback=False, alias_no_args=True,
|
||||
keep=False):
|
||||
def parse(self, text, aliases=True, fallback=False, keep=False):
|
||||
"""Split the commandline text into command and arguments.
|
||||
|
||||
Args:
|
||||
@@ -202,7 +197,6 @@ class CommandRunner(QObject):
|
||||
aliases: Whether to handle aliases.
|
||||
fallback: Whether to do a fallback splitting when the command was
|
||||
unknown.
|
||||
alias_no_args: Whether to apply an alias if there are no arguments.
|
||||
keep: Whether to keep special chars and whitespace
|
||||
|
||||
Return:
|
||||
@@ -212,10 +206,11 @@ class CommandRunner(QObject):
|
||||
if not cmdstr and not fallback:
|
||||
raise cmdexc.NoSuchCommandError("No command given")
|
||||
if aliases:
|
||||
new_cmd = self._get_alias(text, alias_no_args)
|
||||
new_cmd = self._get_alias(text)
|
||||
if new_cmd is not None:
|
||||
log.commands.debug("Re-parsing with '{}'.".format(new_cmd))
|
||||
return self.parse(new_cmd, aliases=False)
|
||||
return self.parse(new_cmd, aliases=False, fallback=fallback,
|
||||
keep=keep)
|
||||
try:
|
||||
self._cmd = cmdutils.cmd_dict[cmdstr]
|
||||
except KeyError:
|
||||
@@ -276,8 +271,11 @@ class CommandRunner(QObject):
|
||||
maxsplit=maxsplit)
|
||||
for s in args:
|
||||
# remove quotes and replace \" by "
|
||||
s = re.sub(r"""(^|[^\\])["']""", r'\1', s)
|
||||
s = re.sub(r"""\\(["'])""", r'\1', s)
|
||||
if s == '""' or s == "''":
|
||||
s = ''
|
||||
else:
|
||||
s = re.sub(r"""(^|[^\\])["']""", r'\1', s)
|
||||
s = re.sub(r"""\\(["'])""", r'\1', s)
|
||||
self._args.append(s)
|
||||
break
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -150,6 +150,9 @@ class Completer(QObject):
|
||||
Return:
|
||||
A (parts, cursor_part) tuple with the modified values.
|
||||
"""
|
||||
if parts == ['']:
|
||||
# Empty commandline, i.e. only :.
|
||||
return [''], 0
|
||||
filtered_parts = []
|
||||
for i, part in enumerate(parts):
|
||||
if part == '--':
|
||||
@@ -177,7 +180,11 @@ class Completer(QObject):
|
||||
return
|
||||
except IndexError:
|
||||
pass
|
||||
log.completion.debug("Before filtering flags: parts {}, cursor_part "
|
||||
"{}".format(parts, cursor_part))
|
||||
parts, cursor_part = self._filter_cmdline_parts(parts, cursor_part)
|
||||
log.completion.debug("After filtering flags: parts {}, cursor_part "
|
||||
"{}".format(parts, cursor_part))
|
||||
if cursor_part == 0:
|
||||
# '|' or 'set|'
|
||||
return self._models[usertypes.Completion.command]
|
||||
@@ -319,11 +326,12 @@ class Completer(QObject):
|
||||
if completion.enabled:
|
||||
completion.show()
|
||||
|
||||
def split(self, keep=False):
|
||||
def split(self, keep=False, aliases=False):
|
||||
"""Get the text split up in parts.
|
||||
|
||||
Args:
|
||||
keep: Whether to keep special chars and whitespace.
|
||||
aliases: Whether to resolve aliases.
|
||||
"""
|
||||
text = self._cmd.text()[len(self._cmd.prefix()):]
|
||||
if not text:
|
||||
@@ -335,8 +343,7 @@ class Completer(QObject):
|
||||
# the whitespace.
|
||||
return [text]
|
||||
runner = runners.CommandRunner(self._win_id)
|
||||
parts = runner.parse(text, fallback=True, alias_no_args=False,
|
||||
keep=keep)
|
||||
parts = runner.parse(text, fallback=True, aliases=aliases, keep=keep)
|
||||
if self._empty_item_idx is not None:
|
||||
log.completion.debug("Empty element queued at {}, "
|
||||
"inserting.".format(self._empty_item_idx))
|
||||
@@ -359,28 +366,33 @@ class Completer(QObject):
|
||||
"text: {}, parts: {}, cursor_pos after removing prefix '{}': "
|
||||
"{}".format(self._cmd.text(), parts, self._cmd.prefix(),
|
||||
cursor_pos))
|
||||
skip = 0
|
||||
for i, part in enumerate(parts):
|
||||
log.completion.vdebug("Checking part {}: {}".format(i, parts[i]))
|
||||
if not part:
|
||||
skip += 1
|
||||
continue
|
||||
if cursor_pos <= len(part):
|
||||
# foo| bar
|
||||
self._cursor_part = i
|
||||
self._cursor_part = i - skip
|
||||
if spaces:
|
||||
self._empty_item_idx = i
|
||||
self._empty_item_idx = i - skip
|
||||
else:
|
||||
self._empty_item_idx = None
|
||||
log.completion.vdebug("cursor_pos {} <= len(part) {}, "
|
||||
"setting cursor_part {}, empty_item_idx "
|
||||
"{}".format(cursor_pos, len(part), i,
|
||||
self._empty_item_idx))
|
||||
"setting cursor_part {} - {} (skip), "
|
||||
"empty_item_idx {}".format(
|
||||
cursor_pos, len(part), i, skip,
|
||||
self._empty_item_idx))
|
||||
break
|
||||
cursor_pos -= len(part)
|
||||
log.completion.vdebug(
|
||||
"Removing len({!r}) -> {} from cursor_pos -> {}".format(
|
||||
part, len(part), cursor_pos))
|
||||
else:
|
||||
self._cursor_part = i
|
||||
self._cursor_part = i - skip
|
||||
if spaces:
|
||||
self._empty_item_idx = i
|
||||
self._empty_item_idx = i - skip
|
||||
else:
|
||||
self._empty_item_idx = None
|
||||
log.completion.debug("cursor_part {}, spaces {}".format(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -28,7 +28,7 @@ from PyQt5.QtCore import QRectF, QSize, Qt
|
||||
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
|
||||
QAbstractTextDocumentLayout)
|
||||
|
||||
from qutebrowser.config import config, style
|
||||
from qutebrowser.config import config, configexc, style
|
||||
from qutebrowser.utils import qtutils
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
option = 'completion.fg'
|
||||
try:
|
||||
self._painter.setPen(config.get('colors', option))
|
||||
except config.NoOptionError:
|
||||
except configexc.NoOptionError:
|
||||
self._painter.setPen(config.get('colors', 'completion.fg'))
|
||||
ctx = QAbstractTextDocumentLayout.PaintContext()
|
||||
ctx.palette.setColor(QPalette.Text, self._painter.pen().color())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -35,7 +35,7 @@ import collections.abc
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QStandardPaths, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from qutebrowser.config import configdata, configtypes, textwrapper
|
||||
from qutebrowser.config import configdata, configexc, textwrapper
|
||||
from qutebrowser.config.parsers import ini, keyconf
|
||||
from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import message, objreg, utils, standarddir, log, qtutils
|
||||
@@ -63,11 +63,9 @@ class change_filter: # pylint: disable=invalid-name
|
||||
See class attributes.
|
||||
"""
|
||||
if sectname not in configdata.DATA:
|
||||
raise NoSectionError("Section '{}' does not exist!".format(
|
||||
sectname))
|
||||
raise configexc.NoSectionError(sectname)
|
||||
if optname is not None and optname not in configdata.DATA[sectname]:
|
||||
raise NoOptionError("Option '{}' does not exist in section "
|
||||
"'{}'!".format(optname, sectname))
|
||||
raise configexc.NoOptionError(optname, sectname)
|
||||
self._sectname = sectname
|
||||
self._optname = optname
|
||||
|
||||
@@ -125,16 +123,14 @@ def init(args):
|
||||
try:
|
||||
app = objreg.get('app')
|
||||
config_obj = ConfigManager(confdir, 'qutebrowser.conf', app)
|
||||
except (configtypes.ValidationError, NoOptionError, NoSectionError,
|
||||
UnknownSectionError, InterpolationSyntaxError,
|
||||
configparser.InterpolationError,
|
||||
configparser.DuplicateSectionError,
|
||||
configparser.DuplicateOptionError,
|
||||
configparser.ParsingError) as e:
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
log.init.exception(e)
|
||||
errstr = "Error while reading config:"
|
||||
if hasattr(e, 'section') and hasattr(e, 'option'):
|
||||
errstr += "\n\n{} -> {}:".format(e.section, e.option)
|
||||
try:
|
||||
errstr += "\n\n{} -> {}:".format(
|
||||
e.section, e.option) # pylint: disable=no-member
|
||||
except AttributeError:
|
||||
pass
|
||||
errstr += "\n{}".format(e)
|
||||
msgbox = QMessageBox(QMessageBox.Critical,
|
||||
"Error while reading config!", errstr)
|
||||
@@ -169,34 +165,6 @@ def init(args):
|
||||
objreg.register('command-history', command_history)
|
||||
|
||||
|
||||
class NoSectionError(configparser.NoSectionError):
|
||||
|
||||
"""Exception raised when a section was not found."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class NoOptionError(configparser.NoOptionError):
|
||||
|
||||
"""Exception raised when an option was not found."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class InterpolationSyntaxError(ValueError):
|
||||
|
||||
"""Exception raised when configparser interpolation raised an error."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UnknownSectionError(Exception):
|
||||
|
||||
"""Exception raised when there was an unknwon section in the config."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ConfigManager(QObject):
|
||||
|
||||
"""Configuration manager for qutebrowser.
|
||||
@@ -257,11 +225,13 @@ class ConfigManager(QObject):
|
||||
self._fname = fname
|
||||
if configdir is None:
|
||||
self._configdir = None
|
||||
self._initialized = True
|
||||
else:
|
||||
self._configdir = configdir
|
||||
parser = ini.ReadConfigParser(configdir, fname)
|
||||
self._from_cp(parser)
|
||||
self._initialized = True
|
||||
self._initialized = True
|
||||
self._validate_all()
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get a section from the config."""
|
||||
@@ -341,6 +311,33 @@ class ConfigManager(QObject):
|
||||
lines.append(keyval)
|
||||
return lines
|
||||
|
||||
def _get_real_sectname(self, cp, sectname):
|
||||
"""Get an old or new section name based on a configparser.
|
||||
|
||||
This checks if sectname is in cp, and if not, migrates it if needed and
|
||||
tries again.
|
||||
|
||||
Args:
|
||||
cp: The configparser to check.
|
||||
sectname: The new section name.
|
||||
|
||||
Returns:
|
||||
The section name in the configparser as a string, or None if the
|
||||
configparser doesn't contain the section.
|
||||
"""
|
||||
reverse_renamed_sections = {v: k for k, v in
|
||||
self.RENAMED_SECTIONS.items()}
|
||||
if sectname in reverse_renamed_sections:
|
||||
old_sectname = reverse_renamed_sections[sectname]
|
||||
else:
|
||||
old_sectname = sectname
|
||||
if old_sectname in cp:
|
||||
return old_sectname
|
||||
elif sectname in cp:
|
||||
return sectname
|
||||
else:
|
||||
return None
|
||||
|
||||
def _from_cp(self, cp):
|
||||
"""Read the config from a configparser instance.
|
||||
|
||||
@@ -351,34 +348,17 @@ class ConfigManager(QObject):
|
||||
if sectname in self.RENAMED_SECTIONS:
|
||||
sectname = self.RENAMED_SECTIONS[sectname]
|
||||
if sectname is not 'DEFAULT' and sectname not in self.sections:
|
||||
raise UnknownSectionError("Unknown section '{}'!".format(
|
||||
sectname))
|
||||
raise configexc.NoSectionError(sectname)
|
||||
for sectname in self.sections:
|
||||
reverse_renamed_sections = {v: k for k, v in
|
||||
self.RENAMED_SECTIONS.items()}
|
||||
if sectname in reverse_renamed_sections:
|
||||
old_sectname = reverse_renamed_sections[sectname]
|
||||
else:
|
||||
old_sectname = sectname
|
||||
if old_sectname in cp:
|
||||
real_sectname = old_sectname
|
||||
elif sectname in cp:
|
||||
real_sectname = sectname
|
||||
else:
|
||||
real_sectname = self._get_real_sectname(cp, sectname)
|
||||
if real_sectname is None:
|
||||
continue
|
||||
for k, v in cp[real_sectname].items():
|
||||
if k.startswith(self.ESCAPE_CHAR):
|
||||
k = k[1:]
|
||||
# configparser can't handle = in keys :(
|
||||
if (sectname, k) in self.RENAMED_OPTIONS:
|
||||
k = self.RENAMED_OPTIONS[sectname, k]
|
||||
try:
|
||||
self.set('conf', sectname, k, v, validate=False)
|
||||
except configtypes.ValidationError as e:
|
||||
e.section = sectname
|
||||
e.option = k
|
||||
raise
|
||||
self._validate_all()
|
||||
self.set('conf', sectname, k, v, validate=False)
|
||||
|
||||
def _validate_all(self):
|
||||
"""Validate all values set in self._from_cp."""
|
||||
@@ -387,7 +367,12 @@ class ConfigManager(QObject):
|
||||
for optname, opt in sect.items():
|
||||
interpolated = self._interpolation.before_get(
|
||||
self, sectname, optname, opt.value(), mapping)
|
||||
opt.typ.validate(interpolated)
|
||||
try:
|
||||
opt.typ.validate(interpolated)
|
||||
except configexc.ValidationError as e:
|
||||
e.section = sectname
|
||||
e.option = optname
|
||||
raise
|
||||
|
||||
def _changed(self, sectname, optname):
|
||||
"""Notify other objects the config has changed."""
|
||||
@@ -456,7 +441,7 @@ class ConfigManager(QObject):
|
||||
try:
|
||||
sectdict = self.sections[sectname]
|
||||
except KeyError:
|
||||
raise NoSectionError(sectname)
|
||||
raise configexc.NoSectionError(sectname)
|
||||
optname = self.optionxform(optname)
|
||||
existed = optname in sectdict
|
||||
if existed:
|
||||
@@ -483,11 +468,11 @@ class ConfigManager(QObject):
|
||||
try:
|
||||
sect = self.sections[sectname]
|
||||
except KeyError:
|
||||
raise NoSectionError(sectname)
|
||||
raise configexc.NoSectionError(sectname)
|
||||
try:
|
||||
val = sect[optname]
|
||||
except KeyError:
|
||||
raise NoOptionError(optname, sectname)
|
||||
raise configexc.NoOptionError(optname, sectname)
|
||||
if raw:
|
||||
return val.value()
|
||||
mapping = {key: val.value() for key, val in sect.values.items()}
|
||||
@@ -538,8 +523,7 @@ class ConfigManager(QObject):
|
||||
"are required: value")
|
||||
layer = 'temp' if temp else 'conf'
|
||||
self.set(layer, sectname, optname, value)
|
||||
except (NoOptionError, NoSectionError, configtypes.ValidationError,
|
||||
ValueError) as e:
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
raise cmdexc.CommandError("set: {} - {}".format(
|
||||
e.__class__.__name__, e))
|
||||
|
||||
@@ -557,11 +541,11 @@ class ConfigManager(QObject):
|
||||
value = self._interpolation.before_set(self, sectname, optname,
|
||||
value)
|
||||
except ValueError as e:
|
||||
raise InterpolationSyntaxError(e)
|
||||
raise configexc.InterpolationSyntaxError(optname, sectname, str(e))
|
||||
try:
|
||||
sect = self.sections[sectname]
|
||||
except KeyError:
|
||||
raise NoSectionError(sectname)
|
||||
raise configexc.NoSectionError(sectname)
|
||||
mapping = {key: val.value() for key, val in sect.values.items()}
|
||||
if validate:
|
||||
interpolated = self._interpolation.before_get(
|
||||
@@ -571,7 +555,7 @@ class ConfigManager(QObject):
|
||||
try:
|
||||
sect.setv(layer, optname, value, interpolated)
|
||||
except KeyError:
|
||||
raise NoOptionError(optname, sectname)
|
||||
raise configexc.NoOptionError(optname, sectname)
|
||||
else:
|
||||
if self._initialized:
|
||||
self._after_set(sectname, optname)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -41,6 +41,11 @@ FIRST_COMMENT = r"""
|
||||
|
||||
# Configfile for qutebrowser.
|
||||
#
|
||||
# WARNING:
|
||||
#
|
||||
# This config file will be OVERWRITTEN when closing qutebrowser.
|
||||
# Close qutebrowser before changing this file, or YOUR CHANGES WILL BE LOST.
|
||||
#
|
||||
# This configfile is parsed by python's configparser in extended
|
||||
# interpolation mode. The format is very INI-like, so there are
|
||||
# categories like [general] with "key = value"-pairs.
|
||||
@@ -253,6 +258,10 @@ DATA = collections.OrderedDict([
|
||||
"In addition to the listed values, you can use a `socks://...` or "
|
||||
"`http://...` URL."),
|
||||
|
||||
('proxy-dns-requests',
|
||||
SettingValue(typ.Bool(), 'true'),
|
||||
"Whether to send DNS requests over the configured proxy."),
|
||||
|
||||
('ssl-strict',
|
||||
SettingValue(typ.BoolAsk(), 'ask'),
|
||||
"Whether to validate SSL handshakes."),
|
||||
@@ -508,8 +517,7 @@ DATA = collections.OrderedDict([
|
||||
'http://winhelp2002.mvps.org/hosts.zip,'
|
||||
'http://malwaredomains.lehigh.edu/files/justdomains.zip,'
|
||||
'http://pgl.yoyo.org/adservers/serverlist.php?'
|
||||
'hostformat=hosts&mimetype=plaintext,'
|
||||
'http://hosts-file.net/ad_servers.asp'),
|
||||
'hostformat=hosts&mimetype=plaintext'),
|
||||
"List of URLs of lists which contain hosts to block.\n\n"
|
||||
"The file can be in one of the following formats:\n\n"
|
||||
"- An '/etc/hosts'-like file\n"
|
||||
|
||||
77
qutebrowser/config/configexc.py
Normal file
77
qutebrowser/config/configexc.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014-2015 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/>.
|
||||
|
||||
"""Exceptions related to config parsing."""
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
"""Base exception for config-related errors."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(Error):
|
||||
|
||||
"""Raised when a value for a config type was invalid.
|
||||
|
||||
Attributes:
|
||||
section: Section in which the error occured (added when catching and
|
||||
re-raising the exception).
|
||||
option: Option in which the error occured.
|
||||
"""
|
||||
|
||||
def __init__(self, value, msg):
|
||||
super().__init__("Invalid value '{}' - {}".format(value, msg))
|
||||
self.section = None
|
||||
self.option = None
|
||||
|
||||
|
||||
class NoSectionError(Error):
|
||||
|
||||
"""Raised when no section matches a requested option."""
|
||||
|
||||
def __init__(self, section):
|
||||
super().__init__("Section {!r} does not exist!".format(section))
|
||||
self.section = section
|
||||
|
||||
|
||||
class NoOptionError(Error):
|
||||
|
||||
"""Raised when an option was not found."""
|
||||
|
||||
def __init__(self, option, section):
|
||||
super().__init__("No option {!r} in section: {!r}".format(
|
||||
option, section))
|
||||
self.option = option
|
||||
self.section = section
|
||||
|
||||
|
||||
class InterpolationSyntaxError(Error):
|
||||
|
||||
"""Raised when the source text contains invalid syntax.
|
||||
|
||||
Current implementation raises this exception when the source text into
|
||||
which substitutions are made does not conform to the required syntax.
|
||||
"""
|
||||
|
||||
def __init__(self, option, section, msg):
|
||||
super().__init__(msg)
|
||||
self.option = option
|
||||
self.section = section
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -32,28 +32,12 @@ from PyQt5.QtNetwork import QNetworkProxy
|
||||
from PyQt5.QtWidgets import QTabWidget, QTabBar
|
||||
|
||||
from qutebrowser.commands import cmdutils
|
||||
from qutebrowser.config import configexc
|
||||
|
||||
|
||||
SYSTEM_PROXY = object() # Return value for Proxy type
|
||||
|
||||
|
||||
class ValidationError(ValueError):
|
||||
|
||||
"""Exception raised when a value for a config type was invalid.
|
||||
|
||||
Class attributes:
|
||||
section: Section in which the error occured (added when catching and
|
||||
re-raising the exception).
|
||||
option: Option in which the error occured.
|
||||
"""
|
||||
|
||||
section = None
|
||||
option = None
|
||||
|
||||
def __init__(self, value, msg):
|
||||
super().__init__("Invalid value '{}' - {}".format(value, msg))
|
||||
|
||||
|
||||
class ValidValues:
|
||||
|
||||
"""Container for valid values for a given type.
|
||||
@@ -133,8 +117,9 @@ class BaseType:
|
||||
return
|
||||
if self.valid_values is not None:
|
||||
if value not in self.valid_values:
|
||||
raise ValidationError(value, "valid values: {}".format(
|
||||
', '.join(self.valid_values)))
|
||||
raise configexc.ValidationError(
|
||||
value, "valid values: {}".format(', '.join(
|
||||
self.valid_values)))
|
||||
else:
|
||||
return
|
||||
else:
|
||||
@@ -196,17 +181,17 @@ class String(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if self.forbidden is not None and any(c in value
|
||||
for c in self.forbidden):
|
||||
raise ValidationError(value, "may not contain the chars "
|
||||
"'{}'".format(self.forbidden))
|
||||
raise configexc.ValidationError(value, "may not contain the chars "
|
||||
"'{}'".format(self.forbidden))
|
||||
if self.minlen is not None and len(value) < self.minlen:
|
||||
raise ValidationError(value, "must be at least {} chars "
|
||||
"long!".format(self.minlen))
|
||||
raise configexc.ValidationError(value, "must be at least {} chars "
|
||||
"long!".format(self.minlen))
|
||||
if self.maxlen is not None and len(value) > self.maxlen:
|
||||
raise ValidationError(value, "must be at most {} long!".format(
|
||||
self.maxlen))
|
||||
raise configexc.ValidationError(value, "must be at most {} chars "
|
||||
"long!".format(self.maxlen))
|
||||
|
||||
|
||||
class List(BaseType):
|
||||
@@ -226,10 +211,11 @@ class List(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "list may not be empty!")
|
||||
raise configexc.ValidationError(value, "list may not be "
|
||||
"empty!")
|
||||
vals = self.transform(value)
|
||||
if None in vals:
|
||||
raise ValidationError(value, "items may not be empty!")
|
||||
raise configexc.ValidationError(value, "items may not be empty!")
|
||||
|
||||
|
||||
class Bool(BaseType):
|
||||
@@ -259,9 +245,9 @@ class Bool(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if value.lower() not in Bool._BOOLEAN_STATES:
|
||||
raise ValidationError(value, "must be a boolean!")
|
||||
raise configexc.ValidationError(value, "must be a boolean!")
|
||||
|
||||
|
||||
class BoolAsk(Bool):
|
||||
@@ -313,17 +299,17 @@ class Int(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
try:
|
||||
intval = int(value)
|
||||
except ValueError:
|
||||
raise ValidationError(value, "must be an integer!")
|
||||
raise configexc.ValidationError(value, "must be an integer!")
|
||||
if self.minval is not None and intval < self.minval:
|
||||
raise ValidationError(value, "must be {} or bigger!".format(
|
||||
self.minval))
|
||||
raise configexc.ValidationError(value, "must be {} or "
|
||||
"bigger!".format(self.minval))
|
||||
if self.maxval is not None and intval > self.maxval:
|
||||
raise ValidationError(value, "must be {} or smaller!".format(
|
||||
self.maxval))
|
||||
raise configexc.ValidationError(value, "must be {} or "
|
||||
"smaller!".format(self.maxval))
|
||||
|
||||
|
||||
class IntList(List):
|
||||
@@ -340,9 +326,10 @@ class IntList(List):
|
||||
try:
|
||||
vals = self.transform(value)
|
||||
except ValueError:
|
||||
raise ValidationError(value, "must be a list of integers!")
|
||||
raise configexc.ValidationError(value, "must be a list of "
|
||||
"integers!")
|
||||
if None in vals and not self._none_ok:
|
||||
raise ValidationError(value, "items may not be empty!")
|
||||
raise configexc.ValidationError(value, "items may not be empty!")
|
||||
|
||||
|
||||
class Float(BaseType):
|
||||
@@ -375,17 +362,17 @@ class Float(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
try:
|
||||
floatval = float(value)
|
||||
except ValueError:
|
||||
raise ValidationError(value, "must be a float!")
|
||||
raise configexc.ValidationError(value, "must be a float!")
|
||||
if self.minval is not None and floatval < self.minval:
|
||||
raise ValidationError(value, "must be {} or bigger!".format(
|
||||
self.minval))
|
||||
raise configexc.ValidationError(value, "must be {} or "
|
||||
"bigger!".format(self.minval))
|
||||
if self.maxval is not None and floatval > self.maxval:
|
||||
raise ValidationError(value, "must be {} or smaller!".format(
|
||||
self.maxval))
|
||||
raise configexc.ValidationError(value, "must be {} or "
|
||||
"smaller!".format(self.maxval))
|
||||
|
||||
|
||||
class Perc(BaseType):
|
||||
@@ -418,19 +405,19 @@ class Perc(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty")
|
||||
raise configexc.ValidationError(value, "may not be empty")
|
||||
if not value.endswith('%'):
|
||||
raise ValidationError(value, "does not end with %")
|
||||
raise configexc.ValidationError(value, "does not end with %")
|
||||
try:
|
||||
intval = int(value[:-1])
|
||||
except ValueError:
|
||||
raise ValidationError(value, "invalid percentage!")
|
||||
raise configexc.ValidationError(value, "invalid percentage!")
|
||||
if self.minval is not None and intval < self.minval:
|
||||
raise ValidationError(value, "must be {}% or more!".format(
|
||||
self.minval))
|
||||
raise configexc.ValidationError(value, "must be {}% or "
|
||||
"more!".format(self.minval))
|
||||
if self.maxval is not None and intval > self.maxval:
|
||||
raise ValidationError(value, "must be {}% or less!".format(
|
||||
self.maxval))
|
||||
raise configexc.ValidationError(value, "must be {}% or "
|
||||
"less!".format(self.maxval))
|
||||
|
||||
|
||||
class PercList(List):
|
||||
@@ -465,11 +452,13 @@ class PercList(List):
|
||||
if self._none_ok:
|
||||
continue
|
||||
else:
|
||||
raise ValidationError(value, "items may not be empty!")
|
||||
raise configexc.ValidationError(value, "items may not "
|
||||
"be empty!")
|
||||
else:
|
||||
perctype.validate(val)
|
||||
except ValidationError:
|
||||
raise ValidationError(value, "must be a list of percentages!")
|
||||
except configexc.ValidationError:
|
||||
raise configexc.ValidationError(value, "must be a list of "
|
||||
"percentages!")
|
||||
|
||||
|
||||
class PercOrInt(BaseType):
|
||||
@@ -504,29 +493,30 @@ class PercOrInt(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if value.endswith('%'):
|
||||
try:
|
||||
intval = int(value[:-1])
|
||||
except ValueError:
|
||||
raise ValidationError(value, "invalid percentage!")
|
||||
raise configexc.ValidationError(value, "invalid percentage!")
|
||||
if self.minperc is not None and intval < self.minperc:
|
||||
raise ValidationError(value, "must be {}% or more!".format(
|
||||
self.minperc))
|
||||
raise configexc.ValidationError(value, "must be {}% or "
|
||||
"more!".format(self.minperc))
|
||||
if self.maxperc is not None and intval > self.maxperc:
|
||||
raise ValidationError(value, "must be {}% or less!".format(
|
||||
self.maxperc))
|
||||
raise configexc.ValidationError(value, "must be {}% or "
|
||||
"less!".format(self.maxperc))
|
||||
else:
|
||||
try:
|
||||
intval = int(value)
|
||||
except ValueError:
|
||||
raise ValidationError(value, "must be integer or percentage!")
|
||||
raise configexc.ValidationError(value, "must be integer or "
|
||||
"percentage!")
|
||||
if self.minint is not None and intval < self.minint:
|
||||
raise ValidationError(value, "must be {} or bigger!".format(
|
||||
self.minint))
|
||||
raise configexc.ValidationError(value, "must be {} or "
|
||||
"bigger!".format(self.minint))
|
||||
if self.maxint is not None and intval > self.maxint:
|
||||
raise ValidationError(value, "must be {} or smaller!".format(
|
||||
self.maxint))
|
||||
raise configexc.ValidationError(value, "must be {} or "
|
||||
"smaller!".format(self.maxint))
|
||||
|
||||
|
||||
class Command(BaseType):
|
||||
@@ -540,9 +530,9 @@ class Command(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if value.split()[0] not in cmdutils.cmd_dict:
|
||||
raise ValidationError(value, "must be a valid command!")
|
||||
raise configexc.ValidationError(value, "must be a valid command!")
|
||||
|
||||
def complete(self):
|
||||
out = []
|
||||
@@ -585,11 +575,11 @@ class QtColor(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
elif QColor.isValidColor(value):
|
||||
pass
|
||||
else:
|
||||
raise ValidationError(value, "must be a valid color")
|
||||
raise configexc.ValidationError(value, "must be a valid color")
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -609,14 +599,14 @@ class CssColor(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if value.startswith('-'):
|
||||
# custom function name, won't validate.
|
||||
pass
|
||||
elif QColor.isValidColor(value):
|
||||
pass
|
||||
else:
|
||||
raise ValidationError(value, "must be a valid color")
|
||||
raise configexc.ValidationError(value, "must be a valid color")
|
||||
|
||||
|
||||
class QssColor(CssColor):
|
||||
@@ -644,14 +634,14 @@ class QssColor(CssColor):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
elif any(re.match(r, value) for r in self.color_func_regexes):
|
||||
# QColor doesn't handle these, so we do the best we can easily
|
||||
pass
|
||||
elif QColor.isValidColor(value):
|
||||
pass
|
||||
else:
|
||||
raise ValidationError(value, "must be a valid color")
|
||||
raise configexc.ValidationError(value, "must be a valid color")
|
||||
|
||||
|
||||
class Font(BaseType):
|
||||
@@ -680,9 +670,9 @@ class Font(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if not self.font_regex.match(value):
|
||||
raise ValidationError(value, "must be a valid font")
|
||||
raise configexc.ValidationError(value, "must be a valid font")
|
||||
|
||||
|
||||
class QtFont(Font):
|
||||
@@ -747,11 +737,12 @@ class Regex(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
try:
|
||||
re.compile(value, self.flags)
|
||||
except sre_constants.error as e:
|
||||
raise ValidationError(value, "must be a valid regex - " + str(e))
|
||||
raise configexc.ValidationError(value, "must be a valid regex - " +
|
||||
str(e))
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -779,10 +770,10 @@ class RegexList(List):
|
||||
try:
|
||||
vals = self.transform(value)
|
||||
except sre_constants.error as e:
|
||||
raise ValidationError(value, "must be a list valid regexes - " +
|
||||
str(e))
|
||||
raise configexc.ValidationError(value, "must be a list valid "
|
||||
"regexes - " + str(e))
|
||||
if not self._none_ok and None in vals:
|
||||
raise ValidationError(value, "items may not be empty!")
|
||||
raise configexc.ValidationError(value, "items may not be empty!")
|
||||
|
||||
|
||||
class File(BaseType):
|
||||
@@ -796,12 +787,12 @@ class File(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
value = os.path.expanduser(value)
|
||||
if not os.path.isfile(value):
|
||||
raise ValidationError(value, "must be a valid file!")
|
||||
raise configexc.ValidationError(value, "must be a valid file!")
|
||||
if not os.path.isabs(value):
|
||||
raise ValidationError(value, "must be an absolute path!")
|
||||
raise configexc.ValidationError(value, "must be an absolute path!")
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -820,12 +811,13 @@ class Directory(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
value = os.path.expanduser(value)
|
||||
if not os.path.isdir(value):
|
||||
raise ValidationError(value, "must be a valid directory!")
|
||||
raise configexc.ValidationError(value, "must be a valid "
|
||||
"directory!")
|
||||
if not os.path.isabs(value):
|
||||
raise ValidationError(value, "must be an absolute path!")
|
||||
raise configexc.ValidationError(value, "must be an absolute path!")
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -868,13 +860,13 @@ class WebKitBytes(BaseType):
|
||||
try:
|
||||
val = self.transform(value)
|
||||
except ValueError:
|
||||
raise ValidationError(value, "must be a valid integer with "
|
||||
"optional suffix!")
|
||||
raise configexc.ValidationError(value, "must be a valid integer "
|
||||
"with optional suffix!")
|
||||
if self.maxsize is not None and val > self.maxsize:
|
||||
raise ValidationError(value, "must be {} "
|
||||
"maximum!".format(self.maxsize))
|
||||
raise configexc.ValidationError(value, "must be {} "
|
||||
"maximum!".format(self.maxsize))
|
||||
if val < 0:
|
||||
raise ValidationError(value, "must be 0 minimum!")
|
||||
raise configexc.ValidationError(value, "must be 0 minimum!")
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -919,10 +911,10 @@ class WebKitBytesList(List):
|
||||
for val in vals:
|
||||
self.bytestype.validate(val)
|
||||
if None in vals and not self._none_ok:
|
||||
raise ValidationError(value, "items may not be empty!")
|
||||
raise configexc.ValidationError(value, "items may not be empty!")
|
||||
if self.length is not None and len(vals) != self.length:
|
||||
raise ValidationError(value, "exactly {} values need to be "
|
||||
"set!".format(self.length))
|
||||
raise configexc.ValidationError(value, "exactly {} values need to "
|
||||
"be set!".format(self.length))
|
||||
|
||||
|
||||
class ShellCommand(BaseType):
|
||||
@@ -944,13 +936,14 @@ class ShellCommand(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
if self.placeholder and '{}' not in self.transform(value):
|
||||
raise ValidationError(value, "needs to contain a {}-placeholder.")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
try:
|
||||
shlex.split(value)
|
||||
except ValueError as e:
|
||||
raise ValidationError(value, str(e))
|
||||
raise configexc.ValidationError(value, str(e))
|
||||
if self.placeholder and '{}' not in self.transform(value):
|
||||
raise configexc.ValidationError(value, "needs to contain a "
|
||||
"{}-placeholder.")
|
||||
|
||||
def transform(self, value):
|
||||
if not value:
|
||||
@@ -986,16 +979,17 @@ class Proxy(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if value in self.valid_values:
|
||||
return
|
||||
url = QUrl(value)
|
||||
if not url.isValid():
|
||||
raise ValidationError(value, "invalid url, {}".format(
|
||||
raise configexc.ValidationError(value, "invalid url, {}".format(
|
||||
url.errorString()))
|
||||
elif url.scheme() not in self.PROXY_TYPES:
|
||||
raise ValidationError(value, "must be a proxy URL (http://... or "
|
||||
"socks://...) or system/none!")
|
||||
raise configexc.ValidationError(value, "must be a proxy URL "
|
||||
"(http://... or socks://...) or "
|
||||
"system/none!")
|
||||
|
||||
def complete(self):
|
||||
out = []
|
||||
@@ -1033,7 +1027,7 @@ class SearchEngineName(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
|
||||
|
||||
class SearchEngineUrl(BaseType):
|
||||
@@ -1045,12 +1039,12 @@ class SearchEngineUrl(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if '{}' not in value:
|
||||
raise ValidationError(value, "must contain \"{}\"")
|
||||
raise configexc.ValidationError(value, "must contain \"{}\"")
|
||||
url = QUrl(value.replace('{}', 'foobar'))
|
||||
if not url.isValid():
|
||||
raise ValidationError(value, "invalid url, {}".format(
|
||||
raise configexc.ValidationError(value, "invalid url, {}".format(
|
||||
url.errorString()))
|
||||
|
||||
|
||||
@@ -1065,11 +1059,11 @@ class Encoding(BaseType):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
try:
|
||||
codecs.lookup(value)
|
||||
except LookupError:
|
||||
raise ValidationError(value, "is not a valid encoding!")
|
||||
raise configexc.ValidationError(value, "is not a valid encoding!")
|
||||
|
||||
|
||||
class UserStyleSheet(File):
|
||||
@@ -1086,7 +1080,7 @@ class UserStyleSheet(File):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "may not be empty!")
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
value = os.path.expanduser(value)
|
||||
if not os.path.isabs(value):
|
||||
# probably a CSS, so we don't handle it as filename.
|
||||
@@ -1096,10 +1090,10 @@ class UserStyleSheet(File):
|
||||
try:
|
||||
value.encode('utf-8')
|
||||
except UnicodeEncodeError as e:
|
||||
raise ValidationError(value, str(e))
|
||||
raise configexc.ValidationError(value, str(e))
|
||||
return
|
||||
elif not os.path.isfile(value):
|
||||
raise ValidationError(value, "must be a valid file!")
|
||||
raise configexc.ValidationError(value, "must be a valid file!")
|
||||
|
||||
def transform(self, value):
|
||||
path = os.path.expanduser(value)
|
||||
@@ -1179,14 +1173,16 @@ class UrlList(List):
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise ValidationError(value, "list may not be empty!")
|
||||
raise configexc.ValidationError(value, "list may not be "
|
||||
"empty!")
|
||||
vals = self.transform(value)
|
||||
for val in vals:
|
||||
if val is None:
|
||||
raise ValidationError(value, "values may not be empty!")
|
||||
raise configexc.ValidationError(value, "values may not be "
|
||||
"empty!")
|
||||
elif not val.isValid():
|
||||
raise ValidationError(value, "invalid URL - {}".format(
|
||||
val.errorString()))
|
||||
raise configexc.ValidationError(value, "invalid URL - "
|
||||
"{}".format(val.errorString()))
|
||||
|
||||
|
||||
class SelectOnRemove(BaseType):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -29,7 +29,7 @@ Module attributes:
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
from PyQt5.QtCore import QStandardPaths, QUrl
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, standarddir, objreg
|
||||
@@ -71,34 +71,40 @@ MAPPINGS = {
|
||||
'fonts': {
|
||||
'web-family-standard':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.StandardFont, v)),
|
||||
qws.setFontFamily(QWebSettings.StandardFont, v),
|
||||
""),
|
||||
'web-family-fixed':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.FixedFont, v)),
|
||||
qws.setFontFamily(QWebSettings.FixedFont, v),
|
||||
""),
|
||||
'web-family-serif':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.SerifFont, v)),
|
||||
qws.setFontFamily(QWebSettings.SerifFont, v),
|
||||
""),
|
||||
'web-family-sans-serif':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.SansSerifFont, v)),
|
||||
qws.setFontFamily(QWebSettings.SansSerifFont, v),
|
||||
""),
|
||||
'web-family-cursive':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.CursiveFont, v)),
|
||||
qws.setFontFamily(QWebSettings.CursiveFont, v),
|
||||
""),
|
||||
'web-family-fantasy':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.FantasyFont, v)),
|
||||
qws.setFontFamily(QWebSettings.FantasyFont, v),
|
||||
""),
|
||||
'web-size-minimum':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.MinimumFontSize, v)),
|
||||
qws.setFontSize(QWebSettings.MinimumFontSize, v)),
|
||||
'web-size-minimum-logical':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.MinimumLogicalFontSize, v)),
|
||||
qws.setFontSize(QWebSettings.MinimumLogicalFontSize, v)),
|
||||
'web-size-default':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.DefaultFontSize, v)),
|
||||
qws.setFontSize(QWebSettings.DefaultFontSize, v)),
|
||||
'web-size-default-fixed':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.DefaultFixedFontSize, v)),
|
||||
qws.setFontSize(QWebSettings.DefaultFixedFontSize, v)),
|
||||
},
|
||||
'ui': {
|
||||
'zoom-text-only':
|
||||
@@ -106,9 +112,12 @@ MAPPINGS = {
|
||||
'frame-flattening':
|
||||
(MapType.attribute, QWebSettings.FrameFlatteningEnabled),
|
||||
'user-stylesheet':
|
||||
(MapType.setter, lambda qws, v: qws.setUserStyleSheetUrl(v)),
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setUserStyleSheetUrl(v),
|
||||
QUrl()),
|
||||
'css-media-type':
|
||||
(MapType.setter, lambda qws, v: qws.setCSSMediaType(v)),
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setCSSMediaType(v)),
|
||||
#'accelerated-compositing':
|
||||
# (MapType.attribute, QWebSettings.AcceleratedCompositingEnabled),
|
||||
#'tiled-backing-store':
|
||||
@@ -124,16 +133,16 @@ MAPPINGS = {
|
||||
(MapType.attribute, QWebSettings.LocalStorageEnabled),
|
||||
'maximum-pages-in-cache':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setMaximumPagesInCache(v)),
|
||||
QWebSettings.setMaximumPagesInCache(v)),
|
||||
'object-cache-capacities':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setObjectCacheCapacities(*v)),
|
||||
QWebSettings.setObjectCacheCapacities(*v)),
|
||||
'offline-storage-default-quota':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setOfflineStorageDefaultQuota(v)),
|
||||
QWebSettings.setOfflineStorageDefaultQuota(v)),
|
||||
'offline-web-application-cache-quota':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setOfflineWebApplicationCacheQuota(v)),
|
||||
QWebSettings.setOfflineWebApplicationCacheQuota(v)),
|
||||
},
|
||||
'general': {
|
||||
'private-browsing':
|
||||
@@ -147,30 +156,39 @@ MAPPINGS = {
|
||||
'site-specific-quirks':
|
||||
(MapType.attribute, QWebSettings.SiteSpecificQuirksEnabled),
|
||||
'default-encoding':
|
||||
(MapType.setter, lambda qws, v: qws.setDefaultTextEncoding(v)),
|
||||
(MapType.setter, lambda qws, v: qws.setDefaultTextEncoding(v), ""),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
settings = None
|
||||
UNSET = object()
|
||||
|
||||
|
||||
def _set_setting(typ, arg, value):
|
||||
def _set_setting(typ, arg, default=UNSET, value=UNSET):
|
||||
"""Set a QWebSettings setting.
|
||||
|
||||
Args:
|
||||
typ: The type of the item
|
||||
(MapType.attribute/MapType.setter/MapType.static_setter)
|
||||
typ: The type of the item.
|
||||
arg: The argument (attribute/handler)
|
||||
default: The value to use if the user set an empty string.
|
||||
value: The value to set.
|
||||
"""
|
||||
if not isinstance(typ, MapType):
|
||||
raise TypeError("Type {} is no MapType member!".format(typ))
|
||||
if value is UNSET:
|
||||
raise TypeError("No value given!")
|
||||
if value is None:
|
||||
if default is UNSET:
|
||||
return
|
||||
else:
|
||||
value = default
|
||||
|
||||
if typ == MapType.attribute:
|
||||
settings.setAttribute(arg, value)
|
||||
elif typ == MapType.setter and value is not None:
|
||||
elif typ == MapType.setter:
|
||||
arg(settings, value)
|
||||
elif typ == MapType.static_setter and value is not None:
|
||||
elif typ == MapType.static_setter:
|
||||
arg(value)
|
||||
|
||||
|
||||
@@ -189,17 +207,17 @@ def init():
|
||||
global settings
|
||||
settings = QWebSettings.globalSettings()
|
||||
for sectname, section in MAPPINGS.items():
|
||||
for optname, (typ, arg) in section.items():
|
||||
for optname, mapping in section.items():
|
||||
value = config.get(sectname, optname)
|
||||
_set_setting(typ, arg, value)
|
||||
_set_setting(*mapping, value=value)
|
||||
objreg.get('config').changed.connect(update_settings)
|
||||
|
||||
|
||||
def update_settings(section, option):
|
||||
"""Update global settings when qwebsettings changed."""
|
||||
try:
|
||||
typ, arg = MAPPINGS[section][option]
|
||||
mapping = MAPPINGS[section][option]
|
||||
except KeyError:
|
||||
return
|
||||
value = config.get(section, option)
|
||||
_set_setting(typ, arg, value)
|
||||
_set_setting(*mapping, value=value)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -35,11 +35,6 @@ from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.utils import usertypes, log, objreg, utils
|
||||
|
||||
|
||||
class ModeLockedError(Exception):
|
||||
|
||||
"""Exception raised when the mode is currently locked."""
|
||||
|
||||
|
||||
class NotInModeError(Exception):
|
||||
|
||||
"""Exception raised when we want to leave a mode we're not in."""
|
||||
@@ -82,9 +77,9 @@ def _get_modeman(win_id):
|
||||
return objreg.get('mode-manager', scope='window', window=win_id)
|
||||
|
||||
|
||||
def enter(win_id, mode, reason=None, override=False):
|
||||
def enter(win_id, mode, reason=None, only_if_normal=False):
|
||||
"""Enter the mode 'mode'."""
|
||||
_get_modeman(win_id).enter(mode, reason, override)
|
||||
_get_modeman(win_id).enter(mode, reason, only_if_normal)
|
||||
|
||||
|
||||
def leave(win_id, mode, reason=None):
|
||||
@@ -92,14 +87,6 @@ def leave(win_id, mode, reason=None):
|
||||
_get_modeman(win_id).leave(mode, reason)
|
||||
|
||||
|
||||
def maybe_enter(win_id, mode, reason=None, override=False):
|
||||
"""Convenience method to enter 'mode' without exceptions."""
|
||||
try:
|
||||
_get_modeman(win_id).enter(mode, reason, override)
|
||||
except ModeLockedError:
|
||||
pass
|
||||
|
||||
|
||||
def maybe_leave(win_id, mode, reason=None):
|
||||
"""Convenience method to leave 'mode' without exceptions."""
|
||||
try:
|
||||
@@ -141,11 +128,7 @@ class ModeManager(QObject):
|
||||
|
||||
Attributes:
|
||||
passthrough: A list of modes in which to pass through events.
|
||||
locked: Whether current mode is locked. This means the current mode can
|
||||
still be left (then locked will be reset), but no new mode can
|
||||
be entered while the current mode is active.
|
||||
mode_stack: A list of the modes we're currently in, with the active
|
||||
one on the right.
|
||||
mode: The mode we're currently in.
|
||||
_win_id: The window ID of this ModeManager
|
||||
_handlers: A dictionary of modes and their handlers.
|
||||
_forward_unbound_keys: If we should forward unbound keys.
|
||||
@@ -169,25 +152,18 @@ class ModeManager(QObject):
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self.locked = False
|
||||
self._handlers = {}
|
||||
self.passthrough = []
|
||||
self.mode_stack = []
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self._releaseevents_to_pass = []
|
||||
self._forward_unbound_keys = config.get(
|
||||
'input', 'forward-unbound-keys')
|
||||
objreg.get('config').changed.connect(self.set_forward_unbound_keys)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, mode=self.mode(), locked=self.locked,
|
||||
return utils.get_repr(self, mode=self.mode,
|
||||
passthrough=self.passthrough)
|
||||
|
||||
def mode(self):
|
||||
"""Get the current mode.."""
|
||||
if not self.mode_stack:
|
||||
return None
|
||||
return self.mode_stack[-1]
|
||||
|
||||
def _eventFilter_keypress(self, event):
|
||||
"""Handle filtering of KeyPress events.
|
||||
|
||||
@@ -197,7 +173,7 @@ class ModeManager(QObject):
|
||||
Return:
|
||||
True if event should be filtered, False otherwise.
|
||||
"""
|
||||
curmode = self.mode()
|
||||
curmode = self.mode
|
||||
handler = self._handlers[curmode]
|
||||
if curmode != usertypes.KeyMode.insert:
|
||||
log.modes.debug("got keypress in mode {} - calling handler "
|
||||
@@ -245,7 +221,7 @@ class ModeManager(QObject):
|
||||
filter_this = False
|
||||
else:
|
||||
filter_this = True
|
||||
if self.mode() != usertypes.KeyMode.insert:
|
||||
if self.mode != usertypes.KeyMode.insert:
|
||||
log.modes.debug("filter: {}".format(filter_this))
|
||||
return filter_this
|
||||
|
||||
@@ -264,34 +240,35 @@ class ModeManager(QObject):
|
||||
if passthrough:
|
||||
self.passthrough.append(mode)
|
||||
|
||||
def enter(self, mode, reason=None, override=False):
|
||||
def enter(self, mode, reason=None, only_if_normal=False):
|
||||
"""Enter a new mode.
|
||||
|
||||
Args:
|
||||
mode: The mode to enter as a KeyMode member.
|
||||
reason: Why the mode was entered.
|
||||
override: Override a locked mode.
|
||||
only_if_normal: Only enter the new mode if we're in normal mode.
|
||||
"""
|
||||
if not isinstance(mode, usertypes.KeyMode):
|
||||
raise TypeError("Mode {} is no KeyMode member!".format(mode))
|
||||
if self.locked:
|
||||
if override:
|
||||
log.modes.debug("Locked to mode {}, but overriding to "
|
||||
"{}.".format(self.mode(), mode))
|
||||
else:
|
||||
log.modes.debug("Not entering mode {} because mode is locked "
|
||||
"to {}.".format(mode, self.mode()))
|
||||
raise ModeLockedError("Mode is currently locked to {}".format(
|
||||
self.mode()))
|
||||
log.modes.debug("Entering mode {}{}".format(
|
||||
mode, '' if reason is None else ' (reason: {})'.format(reason)))
|
||||
if mode not in self._handlers:
|
||||
raise ValueError("No handler for mode {}".format(mode))
|
||||
if self.mode_stack and self.mode_stack[-1] == mode:
|
||||
log.modes.debug("Already at end of stack, doing nothing")
|
||||
prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno)
|
||||
if self.mode == mode or (self.mode in prompt_modes and
|
||||
mode in prompt_modes):
|
||||
log.modes.debug("Ignoring request as we're in mode {} "
|
||||
"already.".format(self.mode))
|
||||
return
|
||||
self.mode_stack.append(mode)
|
||||
log.modes.debug("New mode stack: {}".format(self.mode_stack))
|
||||
if self.mode != usertypes.KeyMode.normal:
|
||||
if only_if_normal:
|
||||
log.modes.debug("Ignoring request as we're in mode {} "
|
||||
"and only_if_normal is set..".format(
|
||||
self.mode))
|
||||
return
|
||||
log.modes.debug("Overriding mode {}.".format(self.mode))
|
||||
self.left.emit(self.mode, mode, self._win_id)
|
||||
self.mode = mode
|
||||
self.entered.emit(mode, self._win_id)
|
||||
|
||||
@cmdutils.register(instance='mode-manager', hide=True, scope='window')
|
||||
@@ -314,24 +291,21 @@ class ModeManager(QObject):
|
||||
mode: The name of the mode to leave.
|
||||
reason: Why the mode was left.
|
||||
"""
|
||||
try:
|
||||
self.mode_stack.remove(mode)
|
||||
except ValueError:
|
||||
raise NotInModeError("Mode {} not on mode stack!".format(mode))
|
||||
self.locked = False
|
||||
log.modes.debug("Leaving mode {}{}, new mode stack {}".format(
|
||||
mode, '' if reason is None else ' (reason: {})'.format(reason),
|
||||
self.mode_stack))
|
||||
self.left.emit(mode, self.mode(), self._win_id)
|
||||
if self.mode != mode:
|
||||
raise NotInModeError("Not in mode {}!".format(mode))
|
||||
log.modes.debug("Leaving mode {}{}".format(
|
||||
mode, '' if reason is None else ' (reason: {})'.format(reason)))
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self.left.emit(mode, self.mode, self._win_id)
|
||||
|
||||
@cmdutils.register(instance='mode-manager', name='leave-mode',
|
||||
not_modes=[usertypes.KeyMode.normal], hide=True,
|
||||
scope='window')
|
||||
def leave_current_mode(self):
|
||||
"""Leave the mode we're currently in."""
|
||||
if self.mode() == usertypes.KeyMode.normal:
|
||||
if self.mode == usertypes.KeyMode.normal:
|
||||
raise ValueError("Can't leave normal mode!")
|
||||
self.leave(self.mode(), 'leave current')
|
||||
self.leave(self.mode, 'leave current')
|
||||
|
||||
@config.change_filter('input', 'forward-unbound-keys')
|
||||
def set_forward_unbound_keys(self):
|
||||
@@ -350,7 +324,7 @@ class ModeManager(QObject):
|
||||
Return:
|
||||
True if event should be filtered, False otherwise.
|
||||
"""
|
||||
if self.mode() is None:
|
||||
if self.mode is None:
|
||||
# We got events before mode is set, so just pass them through.
|
||||
return False
|
||||
typ = event.type()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -22,7 +22,6 @@
|
||||
import binascii
|
||||
import base64
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||
@@ -112,8 +111,6 @@ class MainWindow(QWidget):
|
||||
modeman.init(self.win_id, self)
|
||||
|
||||
self._connect_signals()
|
||||
QTimer.singleShot(0, functools.partial(
|
||||
modeman.enter, win_id, usertypes.KeyMode.normal, 'init'))
|
||||
|
||||
# When we're here the statusbar might not even really exist yet, so
|
||||
# resizing will fail. Therefore, we use singleShot QTimers to make sure
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -90,6 +90,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
"""
|
||||
self.setText(text)
|
||||
log.modes.debug("Setting command text, focusing {!r}".format(self))
|
||||
modeman.enter(self._win_id, usertypes.KeyMode.command, 'cmd focus')
|
||||
self.setFocus()
|
||||
self.show_cmd.emit()
|
||||
|
||||
@@ -183,12 +184,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
self.clear_completion_selection.emit()
|
||||
self.hide_completion.emit()
|
||||
|
||||
def focusInEvent(self, e):
|
||||
"""Extend focusInEvent to enter command mode."""
|
||||
modeman.maybe_enter(self._win_id, usertypes.KeyMode.command,
|
||||
'cmd focus')
|
||||
super().focusInEvent(e)
|
||||
|
||||
def setText(self, text):
|
||||
"""Extend setText to set prefix and make sure the prompt is ok."""
|
||||
if not text:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
"""Manager for questions to be shown in the statusbar."""
|
||||
|
||||
import sip
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, QObject
|
||||
@@ -56,6 +57,9 @@ class Prompter(QObject):
|
||||
things happen is clear, e.g. on_mode_left can't happen after we already set
|
||||
up the *new* question.
|
||||
|
||||
Class Attributes:
|
||||
KEY_MODES: A mapping of PromptModes to KeyModes.
|
||||
|
||||
Attributes:
|
||||
_shutting_down: Whether we're currently shutting down the prompter and
|
||||
should ignore future questions to avoid segfaults.
|
||||
@@ -70,6 +74,13 @@ class Prompter(QObject):
|
||||
hide_prompt: Emitted when the prompt widget should be hidden.
|
||||
"""
|
||||
|
||||
KEY_MODES = {
|
||||
usertypes.PromptMode.yesno: usertypes.KeyMode.yesno,
|
||||
usertypes.PromptMode.text: usertypes.KeyMode.prompt,
|
||||
usertypes.PromptMode.user_pwd: usertypes.KeyMode.prompt,
|
||||
usertypes.PromptMode.alert: usertypes.KeyMode.prompt,
|
||||
}
|
||||
|
||||
show_prompt = pyqtSignal()
|
||||
hide_prompt = pyqtSignal()
|
||||
|
||||
@@ -95,7 +106,12 @@ class Prompter(QObject):
|
||||
"""Pop a question from the queue and ask it, if there are any."""
|
||||
log.statusbar.debug("Popping from queue {}".format(self._queue))
|
||||
if self._queue:
|
||||
self.ask_question(self._queue.popleft(), blocking=False)
|
||||
question = self._queue.popleft()
|
||||
if not sip.isdeleted(question):
|
||||
# the question could already be deleted, e.g. by a cancelled
|
||||
# download. See
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/415
|
||||
self.ask_question(question, blocking=False)
|
||||
|
||||
def _get_ctx(self):
|
||||
"""Get a PromptContext based on the current state."""
|
||||
@@ -129,14 +145,14 @@ class Prompter(QObject):
|
||||
prompt.lineedit.setEchoMode(ctx.echo_mode)
|
||||
prompt.lineedit.setVisible(ctx.input_visible)
|
||||
self.show_prompt.emit()
|
||||
mode = self.KEY_MODES[ctx.question.mode]
|
||||
ctx.question.aborted.connect(
|
||||
lambda: modeman.maybe_leave(self._win_id, mode, 'aborted'))
|
||||
modeman.enter(self._win_id, mode, 'question asked')
|
||||
return True
|
||||
|
||||
def _display_question(self):
|
||||
"""Display the question saved in self._question.
|
||||
|
||||
Return:
|
||||
The mode which should be entered.
|
||||
"""
|
||||
"""Display the question saved in self._question."""
|
||||
prompt = objreg.get('prompt', scope='window', window=self._win_id)
|
||||
if self._question.mode == usertypes.PromptMode.yesno:
|
||||
if self._question.default is None:
|
||||
@@ -147,23 +163,19 @@ class Prompter(QObject):
|
||||
suffix = " (no)"
|
||||
prompt.txt.setText(self._question.text + suffix)
|
||||
prompt.lineedit.hide()
|
||||
mode = usertypes.KeyMode.yesno
|
||||
elif self._question.mode == usertypes.PromptMode.text:
|
||||
prompt.txt.setText(self._question.text)
|
||||
if self._question.default:
|
||||
prompt.lineedit.setText(self._question.default)
|
||||
prompt.lineedit.show()
|
||||
mode = usertypes.KeyMode.prompt
|
||||
elif self._question.mode == usertypes.PromptMode.user_pwd:
|
||||
prompt.txt.setText(self._question.text)
|
||||
if self._question.default:
|
||||
prompt.lineedit.setText(self._question.default)
|
||||
prompt.lineedit.show()
|
||||
mode = usertypes.KeyMode.prompt
|
||||
elif self._question.mode == usertypes.PromptMode.alert:
|
||||
prompt.txt.setText(self._question.text + ' (ok)')
|
||||
prompt.lineedit.hide()
|
||||
mode = usertypes.KeyMode.prompt
|
||||
else:
|
||||
raise ValueError("Invalid prompt mode!")
|
||||
log.modes.debug("Question asked, focusing {!r}".format(
|
||||
@@ -171,7 +183,6 @@ class Prompter(QObject):
|
||||
prompt.lineedit.setFocus()
|
||||
self.show_prompt.emit()
|
||||
self._busy = True
|
||||
return mode
|
||||
|
||||
def shutdown(self):
|
||||
"""Cancel all blocking questions.
|
||||
@@ -227,26 +238,26 @@ class Prompter(QObject):
|
||||
# User just entered a password
|
||||
password = prompt.lineedit.text()
|
||||
self._question.answer = (self._question.user, password)
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'prompt accept')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'prompt accept')
|
||||
self._question.done()
|
||||
elif self._question.mode == usertypes.PromptMode.text:
|
||||
# User just entered text.
|
||||
self._question.answer = prompt.lineedit.text()
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'prompt accept')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'prompt accept')
|
||||
self._question.done()
|
||||
elif self._question.mode == usertypes.PromptMode.yesno:
|
||||
# User wants to accept the default of a yes/no question.
|
||||
self._question.answer = self._question.default
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.yesno,
|
||||
'yesno accept')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.yesno,
|
||||
'yesno accept')
|
||||
self._question.done()
|
||||
elif self._question.mode == usertypes.PromptMode.alert:
|
||||
# User acknowledged an alert
|
||||
self._question.answer = None
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'alert accept')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.prompt,
|
||||
'alert accept')
|
||||
self._question.done()
|
||||
else:
|
||||
raise ValueError("Invalid question mode!")
|
||||
@@ -259,7 +270,8 @@ class Prompter(QObject):
|
||||
# We just ignore this if we don't have a yes/no question.
|
||||
return
|
||||
self._question.answer = True
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.yesno, 'yesno accept')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.yesno,
|
||||
'yesno accept')
|
||||
self._question.done()
|
||||
|
||||
@cmdutils.register(instance='prompter', hide=True, scope='window',
|
||||
@@ -270,7 +282,8 @@ class Prompter(QObject):
|
||||
# We just ignore this if we don't have a yes/no question.
|
||||
return
|
||||
self._question.answer = False
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.yesno, 'prompt accept')
|
||||
modeman.maybe_leave(self._win_id, usertypes.KeyMode.yesno,
|
||||
'prompt accept')
|
||||
self._question.done()
|
||||
|
||||
@pyqtSlot(usertypes.Question, bool)
|
||||
@@ -311,18 +324,11 @@ class Prompter(QObject):
|
||||
context = self._get_ctx()
|
||||
|
||||
self._question = question
|
||||
mode = self._display_question()
|
||||
self._display_question()
|
||||
mode = self.KEY_MODES[self._question.mode]
|
||||
question.aborted.connect(
|
||||
lambda: modeman.maybe_leave(self._win_id, mode, 'aborted'))
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=self._win_id)
|
||||
try:
|
||||
modeman.enter(self._win_id, mode, 'question asked', override=True)
|
||||
except modeman.ModeLockedError:
|
||||
if mode_manager.mode() != usertypes.KeyMode.prompt:
|
||||
question.abort()
|
||||
return None
|
||||
mode_manager.locked = True
|
||||
modeman.enter(self._win_id, mode, 'question asked')
|
||||
if blocking:
|
||||
loop = qtutils.EventLoop()
|
||||
self._loops.append(loop)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -259,7 +259,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
def undo(self):
|
||||
"""Undo removing of a tab."""
|
||||
url, history_data = self._undo_stack.pop()
|
||||
newtab = self.tabopen(url)
|
||||
newtab = self.tabopen(url, background=False)
|
||||
qtutils.deserialize(history_data, newtab.history())
|
||||
|
||||
@pyqtSlot('QUrl', bool)
|
||||
@@ -508,9 +508,11 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
"""Give focus to current tab if command mode was left."""
|
||||
if mode == usertypes.KeyMode.command:
|
||||
if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt,
|
||||
usertypes.KeyMode.yesno):
|
||||
widget = self.currentWidget()
|
||||
log.modes.debug("Left command mode, focusing {!r}".format(widget))
|
||||
log.modes.debug("Left status-input mode, focusing {!r}".format(
|
||||
widget))
|
||||
if widget is None:
|
||||
return
|
||||
widget.setFocus()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -34,6 +34,7 @@ from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
|
||||
from qutebrowser.utils import version, log, utils, objreg
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.browser.network import pastebin
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
class _CrashDialog(QDialog):
|
||||
@@ -298,6 +299,11 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
if debug:
|
||||
self._chk_log.setChecked(False)
|
||||
self._chk_log.setEnabled(False)
|
||||
try:
|
||||
if config.get('general', 'private-browsing'):
|
||||
self._chk_log.setChecked(False)
|
||||
except Exception:
|
||||
log.misc.exception("Error while checking private browsing mode")
|
||||
self._chk_log.toggled.connect(self._set_crash_info)
|
||||
self._vbox.addWidget(self._chk_log)
|
||||
info_label = QLabel("<i>This makes it a lot easier to diagnose the "
|
||||
@@ -318,13 +324,14 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
("Command history", '\n'.join(self._cmdhist)),
|
||||
("Objects", self._objects),
|
||||
]
|
||||
super()._gather_crash_info()
|
||||
if self._chk_log.isChecked():
|
||||
try:
|
||||
self._crash_info.append(
|
||||
("Debug log", log.ram_handler.dump_log()))
|
||||
except Exception:
|
||||
self._crash_info.append(
|
||||
("Debug log", traceback.format_exc()))
|
||||
super()._gather_crash_info()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_chk_report_toggled(self):
|
||||
@@ -407,6 +414,7 @@ class ReportDialog(_CrashDialog):
|
||||
self._btn_report.clicked.connect(
|
||||
functools.partial(self.on_button_clicked, self._btn_report, True))
|
||||
self._hbox.addWidget(self._btn_report)
|
||||
self._buttons = [self._btn_report]
|
||||
|
||||
def _init_checkboxes(self, _debug):
|
||||
"""We don't want any checkboxes as the user wanted to report."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -152,11 +152,11 @@ class IPCServer(QObject):
|
||||
return
|
||||
try:
|
||||
args = json_data['args']
|
||||
cwd = json_data['cwd']
|
||||
except KeyError:
|
||||
log.ipc.error("Ignoring invalid IPC data.")
|
||||
log.ipc.debug("no args/cwd: {}".format(decoded.strip()))
|
||||
log.ipc.debug("no args: {}".format(decoded.strip()))
|
||||
return
|
||||
cwd = json_data.get('cwd', None)
|
||||
app = objreg.get('app')
|
||||
app.process_args(args, via_ipc=True, cwd=cwd)
|
||||
|
||||
@@ -211,7 +211,13 @@ def send_to_running_instance(cmdlist):
|
||||
connected = socket.waitForConnected(100)
|
||||
if connected:
|
||||
log.ipc.info("Opening in existing instance")
|
||||
json_data = {'args': cmdlist, 'cwd': os.getcwd()}
|
||||
json_data = {'args': cmdlist}
|
||||
try:
|
||||
cwd = os.getcwd()
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
json_data['cwd'] = cwd
|
||||
line = json.dumps(json_data) + '\n'
|
||||
data = line.encode('utf-8')
|
||||
log.ipc.debug("Writing: {}".format(data))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
5707
qutebrowser/resources.py
Normal file
5707
qutebrowser/resources.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user