Compare commits

...

139 Commits
v0.1 ... v0.1.2

Author SHA1 Message Date
Florian Bruhin
d9d5b2df0c Release v0.1.2 2015-01-09 22:30:04 +01:00
Florian Bruhin
d6fd5a817e Regenerate docs. 2015-01-09 22:28:25 +01:00
Florian Bruhin
301186d407 Use qurl_from_user_input() in urlutils.is_url().
It seems 354018efcd broke IPv6 IPs on older Qt
versions:

======================================================================
FAIL: test_urls (qutebrowser.test.utils.test_urlutils.IsUrlTests) (url='2001:41d0:2:6c11::1')
Test things which are URLs.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/var/lib/buildbot/slaves/slave/ubuntu-utopic/build/qutebrowser/test/utils/test_urlutils.py", line 168, in test_urls
    self.assertTrue(urlutils.is_url(url), url)
AssertionError: False is not true : 2001:41d0:2:6c11::1
2015-01-09 22:26:35 +01:00
Florian Bruhin
ab121a98da Enter KeyMode.normal directly in ModeManager.
We used to enter KeyMode.none and then with a zero-time singleShot QTimer enter
the normal mode. This doesn't really make sense, and caused an exception if a
keypress was processed before the timer fired.

Fixes #433.
2015-01-09 22:26:35 +01:00
Florian Bruhin
a463038834 Make sure QUrl::fromUserInput is valid in is_url.
Fixes #460.

Without this fix, it's possible for URLs to be valid according to is_url, but
not according to QUrl::fromUserInput, e.g. "http:foo:0". This caused an
exception later because fuzzy_url runs qtutils.ensure_valid.
2015-01-09 22:26:35 +01:00
Florian Bruhin
22761b4373 Switch Qt style to Fusion on OS X on Qt 5.4.
Fixes #462.
See #459.

Upstream bugs:

https://bugreports.qt.io/browse/QTBUG-42948
https://bugreports.qt.io/browse/QTBUG-43070
2015-01-09 19:27:44 +01:00
Florian Bruhin
78f6f3a0e1 Fix error handling for local files in :adblock-update 2015-01-09 07:17:47 +01:00
Florian Bruhin
6166ea51e2 Hide 2 more Qt warnings. 2015-01-09 07:17:47 +01:00
Florian Bruhin
4d4065dfac Add !important to all hint properties. 2015-01-09 07:17:47 +01:00
Error 800
81f350ee99 Added !important to hint styles
Prevents websites from overriding hint styles
2015-01-09 07:16:44 +01:00
Florian Bruhin
4b98e6e9ce Make init_venv.py work with multiple sip .so files.
On my Debian jessie there's a sip.cpython-34m-x86_64-linux-gnu.so and a
sip.cpython-34dm-x86_64-linux-gnu.so.
2015-01-09 07:16:44 +01:00
Florian Bruhin
11f8ab1f85 Fix maxsplit-splitting with empty args (""/'').
Fixes #453.
2015-01-09 07:16:44 +01:00
Florian Bruhin
0449da048f Uncheck sending of debug log with private browsing.
Fixes #436.
2015-01-09 07:16:44 +01:00
Error 800
c78e938dea Added !important to hint styles
Prevents websites from overriding hint styles
2015-01-09 07:16:44 +01:00
Error 800
99fb8a5d87 Fixed uppercase hints option
Corrected CSS property from 'texttransform' to 'text-transform'
2015-01-09 07:16:44 +01:00
Matthias Lisin
b1e0b8f119 Commas are awesome
Fixes #438
Fixes #439
2015-01-09 07:16:43 +01:00
Florian Bruhin
8d49e001e9 Abort blocking questions when new page is loaded.
Fixes #430.
Fixes #431.
Hopefully fixes #354.
Hopefully fixes #434.

Conflicts:
	qutebrowser/browser/network/networkmanager.py
2015-01-09 07:16:32 +01:00
Florian Bruhin
965c176acf Fix validation of ShellCommand config type.
Fixes #432.
2015-01-09 07:15:44 +01:00
Florian Bruhin
896da1c27e Add SSL info to version info. 2015-01-09 07:15:44 +01:00
Florian Bruhin
8f33fcfc52 Replace unencodable chars in download filenames.
Fixes #427.
2015-01-09 07:15:44 +01:00
Florian Bruhin
91b0a33ab0 Update copyright years 2015-01-09 07:15:43 +01:00
Florian Bruhin
b059f4058f Remove hosts-file.net from blocker default lists. 2015-01-09 07:15:43 +01:00
Florian Bruhin
b63ce438b4 Fix user-stylesheet setting with an empty value. 2015-01-09 07:15:43 +01:00
Florian Bruhin
f96cf6fe27 Release v0.1.1 2014-12-28 22:47:23 +01:00
Florian Bruhin
bb1a1b80aa Fix setting of QWebSettings with empty strings. 2014-12-28 22:44:40 +01:00
Florian Bruhin
b703028411 Clean up and temporarily disable alias completion.
Fixes #358.
2014-12-28 22:08:38 +01:00
Florian Bruhin
6089d4a636 Remove ez_setup exclude. 2014-12-28 18:04:52 +01:00
Florian Bruhin
d2e550e922 Exclude resources.py from checks. 2014-12-28 18:04:27 +01:00
Florian Bruhin
d1d6fb3dce Use Qt resources for the window icon. 2014-12-28 15:10:02 +01:00
Florian Bruhin
ea5ee0e7c8 Only remove icon tree if necessary. 2014-12-28 14:52:35 +01:00
Florian Bruhin
f1435ce51f Use a dirty hack to copy icon files into package.
See #325.
2014-12-28 14:50:25 +01:00
Florian Bruhin
2a4e884e1b Set window icon. Closes #325. 2014-12-28 14:35:28 +01:00
Florian Bruhin
aef693805a Merge branch 'oed-prevent_downloading_existing_file' 2014-12-28 02:02:08 +01:00
Florian Bruhin
553a2fbcbd Regenerate authors 2014-12-28 02:01:59 +01:00
Florian Bruhin
ed253f23c6 Pass window id to DownloadItem. 2014-12-28 02:00:31 +01:00
Florian Bruhin
60cc70151c Merge branch 'prevent_downloading_existing_file' of https://github.com/oed/qutebrowser into oed-prevent_downloading_existing_file 2014-12-28 01:57:14 +01:00
Florian Bruhin
3426d843bd Merge branch 'shaggytwodope-master' 2014-12-28 01:55:23 +01:00
Florian Bruhin
13fe444c79 Regenerate authors 2014-12-28 01:53:00 +01:00
Florian Bruhin
767f043dfa Change some troubleshooting answers. 2014-12-28 01:52:48 +01:00
Florian Bruhin
53c037f2fa Merge branch 'master' of https://github.com/shaggytwodope/qutebrowser into shaggytwodope-master 2014-12-28 01:41:38 +01:00
Florian Bruhin
73d08cb60c Fix mode handling with multiple javascript prompts.
This fixes a regression introduced in 03ac8874ff.
2014-12-28 01:28:35 +01:00
Florian Bruhin
177707687c Display IPC errors to the user.
Fixes #337.
2014-12-28 01:28:35 +01:00
Florian Bruhin
89c7f3ecfe Re-focus web view when leaving prompt/yesno mode. 2014-12-28 00:41:50 +01:00
Florian Bruhin
03ac8874ff Rewrite keymode handling to use only one mode.
Fixes #417.
Fixes #418.
See 4ab5d2df28.
2014-12-28 00:01:27 +01:00
Florian Bruhin
be2c67aa19 Don't filter completion parts if there's only one.
This fixes a regression (completion not showing with :) introduced in
b1501a691d.
2014-12-27 22:50:28 +01:00
John ShaggyTwoDope Jenkins
c57f0063bc spaces 2014-12-26 17:39:04 -08:00
Joel Torstensson
6c6ae4e465 Refactored question logic. 2014-12-27 00:50:52 +01:00
Joel Torstensson
f0779f8cc0 User now asked if it wants to overwrite existing file.
Fix #318
2014-12-26 21:58:45 +01:00
John ShaggyTwoDope Jenkins
f0d0d92124 Merge branch 'master' of https://github.com/The-Compiler/qutebrowser 2014-12-26 12:11:34 -08:00
John ShaggyTwoDope Jenkins
fd5901070d adjustments added debian ubuntu crash info 2014-12-26 11:26:06 -08:00
Florian Bruhin
b1501a691d Ignore empty parts when calculating cursor part.
Fixes #389.
2014-12-26 16:57:08 +01:00
Florian Bruhin
d1e0de236d Handle :restart correctly with Python eggs.
Fixes #323.
2014-12-26 15:37:25 +01:00
Florian Bruhin
d029044787 Log full exception on restart errors. 2014-12-26 15:37:25 +01:00
Florian Bruhin
769bc65343 Fix name collision. 2014-12-26 15:09:27 +01:00
Florian Bruhin
bf4d6a5707 Handle an invalid cwd properly.
Fixes #370.
2014-12-26 15:07:18 +01:00
Florian Bruhin
dec6842370 Remove unnecessary if-branch in fuzzy_url.
The first branch already checks for `os.path.exists(path)`, so it doesn't make
sense for the second one to check that again (ANDed with some other condition).
2014-12-26 15:03:30 +01:00
Florian Bruhin
4ab5d2df28 Make it possible to enter a keymode twice.
If we don't allow this, we can get stuck e.g. when doing this:

- Press m to get a quickmark prompt.
- Click a javascript prompt button.
- Exit the javascript prompt.
- We have the quickmark prompt open but we're in normal mode.
2014-12-26 14:26:59 +01:00
Florian Bruhin
7c86693dd4 Disable report button in crash dialog on :report. 2014-12-26 13:03:47 +01:00
Florian Bruhin
0c6af7a5f3 Move debug log to bottom in crash dialog.
Closes #365.
2014-12-26 13:01:19 +01:00
Florian Bruhin
4a632f85e0 Add a warning for manual edits to the config.
Closes #373.
2014-12-26 12:49:38 +01:00
Florian Bruhin
0c5aed284b Fix popping of a dead question in prompter.
This happens when a question for a file path for an already cancelled download
gets popped.

Fixes #415.
2014-12-26 12:46:53 +01:00
Florian Bruhin
5d0dd5b11b Fix AttributeError on config changes on Ubuntu.
Fixes #390.
2014-12-26 12:25:42 +01:00
John ShaggyTwoDope Jenkins
324035240c Merge branch 'master' of https://github.com/The-Compiler/qutebrowser
Upstream.
2014-12-24 20:02:27 -08:00
John ShaggyTwoDope Jenkins
d13296fe7d workaround zzz 2014-12-24 19:44:32 -08:00
John ShaggyTwoDope Jenkins
535db9d2a2 ctrl+q for windows 2014-12-24 19:25:07 -08:00
John ShaggyTwoDope Jenkins
5562cc25e0 minor edits 2014-12-24 19:12:54 -08:00
Florian Bruhin
df3d41eb66 Merge branch 'helenst-link-to-settings-page' 2014-12-24 23:53:32 +01:00
Florian Bruhin
847d44cb31 Regenerate docs 2014-12-24 23:53:18 +01:00
Florian Bruhin
9f9f1a6d52 Merge branch 'link-to-settings-page' of https://github.com/helenst/qutebrowser into helenst-link-to-settings-page 2014-12-24 23:51:31 +01:00
Florian Bruhin
294d8dd672 Merge branch 'regines-master' 2014-12-24 23:50:20 +01:00
Florian Bruhin
6d11fc72c6 Regenerate authors 2014-12-24 23:50:12 +01:00
Florian Bruhin
fbca2be1c9 Merge branch 'master' of https://github.com/regines/qutebrowser into regines-master 2014-12-24 23:49:52 +01:00
Regina Hug
bbff9cb4f0 Add label "(6)" to cheatsheet.
Fixes #348.
2014-12-24 23:45:22 +01:00
John ShaggyTwoDope Jenkins
b0c22b9867 troubleshooting typos 2014-12-23 16:17:57 -08:00
John ShaggyTwoDope Jenkins
b02c406cb9 troubleshooting added to faq 2014-12-23 16:05:23 -08:00
Helen Sherwood-Taylor
9816f754a1 Link to settings page from quickstart (fixes #376) 2014-12-23 22:01:09 +00:00
Florian Bruhin
fe845b7af8 Add some Chrome addons 2014-12-23 17:32:32 +01:00
Florian Bruhin
bececc69c3 Save version to state config. 2014-12-22 23:47:43 +01:00
Florian Bruhin
017f143a5f Add a binary mode to utils.read_file.
This will be needed for #325.
2014-12-22 23:44:09 +01:00
Florian Bruhin
4363db90c0 Don't treat things like "31c3" as IP address.
Fixes #388.
2014-12-22 18:38:45 +01:00
Florian Bruhin
b01041e455 Set zoom to default instead of 100% with :zoom/=. 2014-12-22 18:04:28 +01:00
Florian Bruhin
f50a19a488 Adjust page zoom if default zoom changed.
Fixes #393.
2014-12-22 18:01:32 +01:00
Florian Bruhin
3752733f15 Actually connect QWebView.on_config_changed slot. 2014-12-22 18:01:17 +01:00
Florian Bruhin
89e051ff51 Ignore clicks on failed downloads.
Fixes #396.
2014-12-22 17:36:02 +01:00
Florian Bruhin
57c8dff396 Handle category being None in Qt message handler.
Fixes #397.
2014-12-22 17:33:35 +01:00
Florian Bruhin
c48727d19a Force-include pygments in freeze.py.
Fixes #398.
2014-12-22 17:30:41 +01:00
Florian Bruhin
58c991145c Revert "Use scrollRequested signal instead of paintEvent."
It seems on some pages like Twitter or blog.fdik.org, the
QWebPage::scrollRequested signal never gets emitted, so we use this for now.

Fixes #400.
See https://bugreports.qt-project.org/browse/QTBUG-43521.

This reverts commit 03fb21c476.

Conflicts:
	qutebrowser/browser/webview.py
2014-12-22 17:20:43 +01:00
Florian Bruhin
791ff36c69 Clean up _get_args in run_checks.py 2014-12-21 18:59:10 +01:00
Florian Bruhin
2d1c12f69b Turn off flake8 for exception hook. 2014-12-21 18:11:35 +01:00
Florian Bruhin
28dfd73c60 Mark Vimprobable as dead 2014-12-21 14:14:53 +01:00
Florian Bruhin
877d814815 Clean up _get_window_registry. 2014-12-21 14:13:40 +01:00
Florian Bruhin
42890b8a7f Force tabs to be focused on :undo.
Closes #394.
2014-12-21 13:06:24 +01:00
Florian Bruhin
c295486333 Regenerate authors 2014-12-20 14:11:42 +01:00
Florian Bruhin
23ee01a747 Merge branch 'posativ-improve-osx-install' 2014-12-20 14:10:35 +01:00
Florian Bruhin
51415896bc Merge branch 'improve-osx-install' of https://github.com/posativ/qutebrowser into posativ-improve-osx-install 2014-12-20 14:09:32 +01:00
Martin Zimmermann
b8752c02b4 add instructions for MacPorts 2014-12-20 12:42:18 +01:00
Florian Bruhin
ef9ddb2d5f Encode Content-Disposition header name properly.
PyQt <= 5.3 accepted a Python string containing only latin1 chars as argument
for a QByteArray. This is deprecated in 5.4 and will be removed in 5.5 so we
should encode it by hand here.
2014-12-18 23:24:50 +01:00
Florian Bruhin
05e835684d Fix item sorting in NeighborList.
See #361.
2014-12-18 23:20:38 +01:00
Florian Bruhin
07957b105d Handle data being None in download read timer.
Fixes #307.
2014-12-18 23:09:33 +01:00
Florian Bruhin
5c15f56213 Stop download read timer when reply has finished.
See #307.
2014-12-18 23:08:19 +01:00
Florian Bruhin
29ce0a5157 Fix handling of small/big fuzzyval's in NeighborList.
This fixes an exception when having a really big or small zoom (e.g. 0) and
then using +/-.

Fixes #361.
2014-12-18 23:04:43 +01:00
Martin Zimmermann
f84299e6c6 replace manual installation on OS X with homebrew + pip 2014-12-18 00:00:59 +01:00
Florian Bruhin
410d78cfa2 Add missing configexc.py file.
I forgot to add this in 512d7c4448b0610bc133d83d8280a94469841968...
2014-12-17 13:49:50 +01:00
Florian Bruhin
3cc1134e82 Fix AttributeError on HTTP/proxy authentications.
This is a regression introduced in cafb487ac9.

Fixes #355.
See #333.
2014-12-17 13:40:15 +01:00
Florian Bruhin
a714f0b70c config: Set self._initialized before validating.
With a setting with an interpolation this caused a ValueError because
validate_all called get before self._initialized was True.
2014-12-17 13:40:15 +01:00
Florian Bruhin
512d7c4448 Simplify config exception tree and handling.
Also make sure we catch all config-related errors in all related places.
Fixes #324.
2014-12-17 11:17:18 +01:00
Florian Bruhin
30b5319068 Update docs 2014-12-16 23:34:19 +01:00
Florian Bruhin
655ab31d48 Add a network -> proxy-dns-requests option.
Closes #330.
2014-12-16 23:27:55 +01:00
Florian Bruhin
7ccc69c0bc INSTALL: Use python2 virtualenv for Debian. 2014-12-16 22:06:04 +01:00
Florian Bruhin
5e4f3ed7c5 Revert "Use python -m to call virtualenv in init_venv."
This reverts commit 40781b163e.

Some platforms (e.g. Ubuntu Trusty) don't have a python3-virtualenv, so we
should instead adjust the documentation to use the python2 one.
2014-12-16 22:04:46 +01:00
Florian Bruhin
40781b163e Use python -m to call virtualenv in init_venv.
It seems python3-virtualenv on Debian Jessie doesn't contain
/usr/bin/virtualenv.
2014-12-16 17:24:00 +01:00
Florian Bruhin
cafb487ac9 Abort questions in NetworkManager on destroyed.
Hopefully fixes #333.
2014-12-16 17:22:01 +01:00
Florian Bruhin
676313e7ae Fix indent. 2014-12-16 15:09:47 +01:00
Florian Bruhin
05e6515aad Allow min-/maximizing of print preview on Windows.
Fixes #327.
2014-12-16 15:08:28 +01:00
Florian Bruhin
965a1256a3 Add "Remove finished" to the download context menu
Closes #344.
2014-12-16 14:30:47 +01:00
Florian Bruhin
ed013ac3cf Simplify generating of download context menu. 2014-12-16 14:10:25 +01:00
Florian Bruhin
e0271eff34 Open and remove clicked downloads.
Fixes #343.
2014-12-16 14:02:01 +01:00
Florian Bruhin
888a17b7c3 Fix height calculation of download view. 2014-12-16 13:53:54 +01:00
Florian Bruhin
b899d8b44d Always auto-remove adblock downloads when done.
Fixes #342.
2014-12-16 13:44:09 +01:00
Florian Bruhin
77579e7ebd Ensure the docs gets included in freeze.py.
See #346.
2014-12-16 13:26:12 +01:00
Florian Bruhin
b06d8c55c5 Improve INSTALL documentation 2014-12-16 06:20:20 +01:00
Florian Bruhin
fdb66d2e2b Add some notes from /u/angelic_sedition 2014-12-15 23:42:51 +01:00
Florian Bruhin
efd632ea73 Preserve arguments when re-splitting with aliases.
Fixes #339.
2014-12-15 23:06:42 +01:00
Florian Bruhin
512e51eeb4 Fix type annotation for :zoom. Fixes #332. 2014-12-15 22:59:45 +01:00
Florian Bruhin
33120bb780 Add .ico file to freeze.py. Fixes #322. 2014-12-15 22:45:03 +01:00
Florian Bruhin
86d91b9c3d Require BASEDIR in freeze.py. 2014-12-15 22:44:11 +01:00
Florian Bruhin
d9a38fea1a Add a qutebrowser.ico file.
See #322 and #325.
2014-12-15 22:41:57 +01:00
Florian Bruhin
b1f5c2a23c Add icon to README.asciidoc.
See #325.
2014-12-15 22:30:30 +01:00
Florian Bruhin
0d9c7049b6 Clean up ConfigManager._from_cp. 2014-12-15 22:25:06 +01:00
Florian Bruhin
3c68506665 Fix sect/opt in config validation error message.
This is a regression introduced in 0c1420112c.
See #202.
2014-12-15 22:08:33 +01:00
Florian Bruhin
1d2016d3a5 Add some tests for config loading.
This would have prevented the issue in
e87b3fd568.
2014-12-15 22:06:11 +01:00
Florian Bruhin
d84c4fab84 Remove old comment. 2014-12-15 21:49:48 +01:00
Florian Bruhin
637d31c780 Remove 'finding the correct branch' from HACKING. 2014-12-15 21:40:53 +01:00
Florian Bruhin
fff7c500ad Merge branch 'larryhynes-patch-1' 2014-12-15 19:53:29 +01:00
Florian Bruhin
d6735c69dd Regenerate authors 2014-12-15 19:53:26 +01:00
Larry Hynes
3af6279dca Amended, with asciidoc syntax. 2014-12-15 18:44:38 +00:00
Larry Hynes
5baae727b0 Update install to mention Homebrew Python on OSX
Also fixed a small typo.
2014-12-15 18:18:00 +00:00
Florian Bruhin
58e140d3cb Add missing files to MANIFEST.in. 2014-12-15 00:44:18 +01:00
Florian Bruhin
e7634dfe04 Include README.asciidoc in sdist. 2014-12-15 00:22:43 +01:00
156 changed files with 7266 additions and 890 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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!

View File

@@ -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.

View File

@@ -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
~~~~~~~~~~~~~~~

View File

@@ -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
---------

View File

@@ -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.

View File

@@ -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&amp;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&amp;mimetype=plaintext]+
== hints
Hinting settings.

View File

@@ -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.

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -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

View File

@@ -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
View 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>

View File

@@ -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."

View File

@@ -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.
#

View File

@@ -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:

View File

@@ -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.
#

View File

@@ -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))

View File

@@ -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.
#

View File

@@ -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('->', '-'))

View File

@@ -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.
#

View File

@@ -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

View File

@@ -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)

View File

@@ -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 = {}

View File

@@ -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")

View File

@@ -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')

View File

@@ -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>

View File

@@ -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.
#

View File

@@ -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

View File

@@ -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)

View File

@@ -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>

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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

View File

@@ -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().

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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(

View File

@@ -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:

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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(

View File

@@ -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())

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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)

View File

@@ -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"

View 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

View File

@@ -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):

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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)

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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()

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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:

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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)

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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()

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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."""

View File

@@ -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.
#

View File

@@ -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))

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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.
#

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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