Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
130d2cb33b | ||
|
|
d36a0d5d15 | ||
|
|
a126924c42 | ||
|
|
a0381e1683 | ||
|
|
891bb86175 | ||
|
|
d9f356652f | ||
|
|
a4a6099515 | ||
|
|
44dd4da33f | ||
|
|
f69470ddcd | ||
|
|
71dbdb37a2 | ||
|
|
6c0e470b60 | ||
|
|
2322ee4f2c | ||
|
|
669760ed8f | ||
|
|
2b34fbc073 | ||
|
|
e8b689ab50 | ||
|
|
20c3e8dd52 | ||
|
|
4ec618386b | ||
|
|
05f5083c9c | ||
|
|
1251c28509 | ||
|
|
baa3dfd520 | ||
|
|
8f10a97b1e | ||
|
|
202b267bd0 | ||
|
|
d0a0e39323 | ||
|
|
bfcce19308 | ||
|
|
d24360d850 | ||
|
|
415c291345 | ||
|
|
d929590cff | ||
|
|
8291090b43 | ||
|
|
a6f77d5e0b | ||
|
|
efb082828b | ||
|
|
471e62ffab | ||
|
|
f3b55d68db | ||
|
|
4bad99e394 | ||
|
|
6fe816008f | ||
|
|
0d1f4c08f6 | ||
|
|
7dbdc1b383 | ||
|
|
51276c6cea | ||
|
|
e02897ec80 | ||
|
|
ab011cde5b | ||
|
|
a8371d354b | ||
|
|
d618892e09 | ||
|
|
612afc45ef | ||
|
|
3050c09150 | ||
|
|
b44d7c0b84 | ||
|
|
b852daeeae | ||
|
|
f146003858 | ||
|
|
a09503d5b5 | ||
|
|
659fa02613 | ||
|
|
ecfdf7b077 | ||
|
|
cacc42417b | ||
|
|
2403e5b792 | ||
|
|
9b05455c40 | ||
|
|
e957796915 | ||
|
|
1de82a2e63 | ||
|
|
54eed6a88f | ||
|
|
c0bc6368d1 | ||
|
|
51549fc17f | ||
|
|
38a236a31a | ||
|
|
a065568549 | ||
|
|
4abd7089ed | ||
|
|
d37064aa5a | ||
|
|
9625791018 | ||
|
|
5c9718c8cd | ||
|
|
7655313d39 | ||
|
|
c42c19683a | ||
|
|
2f55e216b0 | ||
|
|
a5264bdebd | ||
|
|
5f310b4385 | ||
|
|
b2a01934b6 | ||
|
|
00003e4ff6 | ||
|
|
30ac341b8e | ||
|
|
49943efa87 | ||
|
|
bfd2ef830e | ||
|
|
6f831c292e | ||
|
|
0842dc1bf3 | ||
|
|
302929be9d | ||
|
|
8011a3c63d | ||
|
|
d9d5b2df0c | ||
|
|
d6fd5a817e | ||
|
|
301186d407 | ||
|
|
ab121a98da | ||
|
|
a463038834 | ||
|
|
22761b4373 | ||
|
|
78f6f3a0e1 | ||
|
|
6166ea51e2 | ||
|
|
4d4065dfac | ||
|
|
81f350ee99 | ||
|
|
4b98e6e9ce | ||
|
|
11f8ab1f85 | ||
|
|
0449da048f | ||
|
|
c78e938dea | ||
|
|
99fb8a5d87 | ||
|
|
b1e0b8f119 | ||
|
|
8d49e001e9 | ||
|
|
965c176acf | ||
|
|
896da1c27e | ||
|
|
8f33fcfc52 | ||
|
|
91b0a33ab0 | ||
|
|
b059f4058f | ||
|
|
b63ce438b4 |
17
.flake8
17
.flake8
@@ -1,19 +1,12 @@
|
||||
# vim: ft=dosini fileencoding=utf-8:
|
||||
|
||||
[flake8]
|
||||
# E241: Multiple spaces after ,
|
||||
# E265: Block comment should start with '#'
|
||||
# checked by pylint:
|
||||
# F401: Unused import
|
||||
# E501: Line too long
|
||||
# F821: undefined name
|
||||
# F841: unused variable
|
||||
# E222: Multiple spaces after operator
|
||||
# F811: Redifiniton
|
||||
# W292: No newline at end of file
|
||||
# E701: multiple statements on one line
|
||||
# E702: multiple statements on one line
|
||||
# E225: missing whitespace around operator
|
||||
ignore=E241,E265,F401,E501,F821,F841,E222,F811,W292,E701,E702,E225
|
||||
# F401: Unused import
|
||||
# E402: module level import not at top of file
|
||||
# E266: too many leading '#' for block comment
|
||||
# W503: line break before binary operator
|
||||
ignore=E265,E501,F841,F401,E402,E266,W503
|
||||
max_complexity = 12
|
||||
exclude = ez_setup.py
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
[MASTER]
|
||||
ignore=ez_setup.py
|
||||
extension-pkg-whitelist=PyQt5,sip
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=no-self-use,
|
||||
@@ -23,7 +24,13 @@ disable=no-self-use,
|
||||
too-many-instance-attributes,
|
||||
unnecessary-lambda,
|
||||
blacklisted-name,
|
||||
too-many-lines
|
||||
too-many-lines,
|
||||
logging-format-interpolation,
|
||||
interface-not-implemented,
|
||||
broad-except,
|
||||
bare-except,
|
||||
eval-used,
|
||||
exec-used
|
||||
|
||||
[BASIC]
|
||||
module-rgx=(__)?[a-z][a-z0-9_]*(__)?$
|
||||
|
||||
@@ -130,12 +130,18 @@ Contributors, sorted by the number of commits in descending order:
|
||||
* rikn00
|
||||
* Brian Jackson
|
||||
* Martin Zimmermann
|
||||
* Error 800
|
||||
* Mathias Fussenegger
|
||||
* Larry Hynes
|
||||
* Johannes Altmanninger
|
||||
* Joel Torstensson
|
||||
* sbinix
|
||||
* error800
|
||||
* Thorsten Wißmann
|
||||
* Regina Hug
|
||||
* Peter Vilim
|
||||
* Patric Schmitz
|
||||
* Matthias Lisin
|
||||
* Helen Sherwood-Taylor
|
||||
// QUTE_AUTHORS_END
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|<<general-site-specific-quirks,site-specific-quirks>>|Enable workarounds for broken sites.
|
||||
|<<general-default-encoding,default-encoding>>|Default encoding to use for websites.
|
||||
|<<general-new-instance-open-target,new-instance-open-target>>|How to open links in an existing instance if a new one is launched.
|
||||
|<<general-log-javascript-console,log-javascript-console>>|Whether to log javascript console messages.
|
||||
|==============
|
||||
|
||||
.Quick reference for section ``ui''
|
||||
@@ -361,6 +362,17 @@ Valid values:
|
||||
|
||||
Default: +pass:[window]+
|
||||
|
||||
[[general-log-javascript-console]]
|
||||
=== log-javascript-console
|
||||
Whether to log javascript console messages.
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
|
||||
Default: +pass:[false]+
|
||||
|
||||
== ui
|
||||
General options related to the user interface.
|
||||
|
||||
@@ -1026,7 +1038,7 @@ The file can be in one of the following formats:
|
||||
- One host per line
|
||||
- A zip-file of any of the above, with either only one file, or a file named 'hosts' (with any extension).
|
||||
|
||||
Default: +pass:[http://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext,http://hosts-file.net/ad_servers.asp]+
|
||||
Default: +pass:[http://www.malwaredomainlist.com/hostslist/hosts.txt,http://someonewhocares.org/hosts/hosts,http://winhelp2002.mvps.org/hosts.zip,http://malwaredomains.lehigh.edu/files/justdomains.zip,http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&mimetype=plaintext]+
|
||||
|
||||
== hints
|
||||
Hinting settings.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Getting stacktraces on crashes
|
||||
==============================
|
||||
:toc:
|
||||
The Compiler <mail@qutebrowser.org>
|
||||
|
||||
When there is a fatal crash in qutebrowser - most of the times a
|
||||
@@ -14,10 +15,17 @@ https://en.wikipedia.org/wiki/Debug_symbol[debugging symbols] is required.
|
||||
The rest of this guide is quite Linux specific, though there is a
|
||||
<<windows,section for Windows>> at the end.
|
||||
|
||||
Getting debugging symbols
|
||||
-------------------------
|
||||
Crashes which can be reproduced
|
||||
-------------------------------
|
||||
|
||||
.Debian/Ubuntu/...
|
||||
If a crash can be reproduced, packages with debugging symbols should be
|
||||
installed, and the crash should be reproduced under gdb.
|
||||
|
||||
Getting debugging symbols
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Debian/Ubuntu/...
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
For Debian based systems (Debian, Ubuntu, Linux Mint, ...), debug information
|
||||
is available in the repositories:
|
||||
@@ -26,76 +34,66 @@ is available in the repositories:
|
||||
# apt-get install python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg python3-dbg libqt5webkit5-dbg
|
||||
----
|
||||
|
||||
.Archlinux
|
||||
Archlinux
|
||||
^^^^^^^^^
|
||||
|
||||
For Archlinux, no debug informations are provided. You can either compile Qt
|
||||
yourself (which will take a few hours even on a modern machine) or use
|
||||
debugging symbols compiled by me (x86_64 only).
|
||||
debugging symbols compiled/packaged by me (x86_64 only).
|
||||
|
||||
To compile by yourself:
|
||||
.To compile by yourself
|
||||
|
||||
----
|
||||
$ git clone https://github.com/The-Compiler/qt-debug-pkgbuild.git
|
||||
$ cd qt-debug-pkgbuild
|
||||
$ git checkout symbols
|
||||
$ export DEBUG_CFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
|
||||
$ export DEBUG_CXXFLAGS='-ggdb3 -fvar-tracking-assignments -Og'
|
||||
$ cd qt5
|
||||
$ makepkg -si
|
||||
$ makepkg -si --pkg qt5-base,qt5-webkit
|
||||
$ cd ../pyqt5
|
||||
$ makepkg -si
|
||||
$ makepkg -si --pkg pyqt5-common,python-pyqt5
|
||||
----
|
||||
|
||||
To install my pre-built packages:
|
||||
.To install my pre-built packages
|
||||
|
||||
First download and sign the key:
|
||||
|
||||
----
|
||||
$ mkdir qt-debug
|
||||
$ cd qt-debug
|
||||
$ wget -r -l1 -A '*.tar.xz' -L -np -nd http://www.qutebrowser.org/qt-symbols-pkg/
|
||||
# pacman -U *.pkg.tar.xz
|
||||
# pacman-key -r 0xD6A1C70FE80A0C82
|
||||
$ pacman-key -f 0xD6A1C70FE80A0C82
|
||||
Key fingerprint = 14AF EC28 70C6 4863 C5C7 ACCB D6A1 C70F E80A 0C82
|
||||
# pacman-key --lsign-key 0xD6A1C70FE80A0C82
|
||||
----
|
||||
|
||||
After you are done debugging, make sure to install the system packages again so
|
||||
you get updates. This can be done with this command:
|
||||
Then edit your `/etc/pacman.conf` to add the repository to the bottom:
|
||||
|
||||
----
|
||||
# pacman -S qt5
|
||||
[qt-debug]
|
||||
Server = http://qutebrowser.org/qt-debug/$arch
|
||||
----
|
||||
|
||||
Getting a core dump
|
||||
-------------------
|
||||
Then install the packages:
|
||||
|
||||
The next step is finding the core dump so we can get a stacktrace from it.
|
||||
----
|
||||
# pacman -Sy pyqt5-common-debug python-pyqt5-debug qt5-base-debug qt5-webkit-debug
|
||||
----
|
||||
|
||||
First of all, try to reproduce your problem. If you can, run qutebrowser
|
||||
directly inside gdb like this:
|
||||
The `-debug` packages conflict with the non-debug variants - it's safe to
|
||||
remove them.
|
||||
|
||||
Getting the stack trace
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
First install `gdb` on your system if it's not installed already.
|
||||
|
||||
Then run qutebrowser directly inside gdb like this:
|
||||
|
||||
----
|
||||
$ gdb $(which python3) -ex 'run -m qutebrowser --debug'
|
||||
----
|
||||
|
||||
If you cannot reproduce the problem, you need to check if a coredump got
|
||||
written somewhere.
|
||||
|
||||
Check the file `/proc/sys/kernel/core_pattern` on your system. If it does not
|
||||
start with a `|` character (pipe), check if there is a file named `core` or
|
||||
`core.NNNN` in the directory from that file, or in the current directory.
|
||||
|
||||
If so, execute gdb like this:
|
||||
|
||||
----
|
||||
$ gdb $(which python3) /path/to/core
|
||||
----
|
||||
|
||||
If your `/proc/sys/kernel/core_pattern` contains something like
|
||||
`|/usr/lib/systemd/systemd-coredump`, use `coredumpctl` as root to run gdb:
|
||||
|
||||
----
|
||||
# coredumpctl gdb $(which python3)
|
||||
----
|
||||
|
||||
Getting a stack trace
|
||||
---------------------
|
||||
|
||||
Regardless of the way you used to open gdb, you should now see something like:
|
||||
After you reproduce the crash, you should now see something like:
|
||||
|
||||
----
|
||||
Program received signal SIGSEGV, Segmentation fault.
|
||||
@@ -107,16 +105,58 @@ Now enter these commands at the gdb prompt:
|
||||
|
||||
----
|
||||
(gdb) set logging on
|
||||
(gdb) set logging redirect on
|
||||
(gdb) bt
|
||||
(gdb) bt full
|
||||
# you might have to press enter a few times until you get the prompt back
|
||||
(gdb) set logging redirect off
|
||||
(gdb) quit
|
||||
----
|
||||
|
||||
Now copy the last few lines of the debug log (before you got the gdb prompt)
|
||||
and the full content of `gdb.txt` into the bug report. Please also add some
|
||||
words about what you were doing (or what pages you visited) before the crash
|
||||
This will create a `gdb.txt` in your current directory.
|
||||
|
||||
Copy the last few lines of the debug log (before you got the gdb prompt) and
|
||||
the full content of `gdb.txt` into the bug report. Please also add some words
|
||||
about what you were doing (or what pages you visited) before the crash
|
||||
happened.
|
||||
|
||||
Crashes which can NOT be reproduced
|
||||
-----------------------------------
|
||||
|
||||
If you cannot reproduce the problem, you need to check if a coredump got
|
||||
written somewhere. You should not install debug symbols as they won't match the
|
||||
generated coredump.
|
||||
|
||||
First install `gdb` on your system if it's not installed already.
|
||||
|
||||
Then check the file `/proc/sys/kernel/core_pattern` on your system. If it does
|
||||
not start with a `|` character (pipe), check if there is a file named `core` or
|
||||
`core.NNNN` in the directory from that file, or in the current directory.
|
||||
|
||||
If so, execute gdb like this:
|
||||
|
||||
----
|
||||
$ gdb $(which python3) /path/to/core
|
||||
----
|
||||
|
||||
If your `/proc/sys/kernel/core_pattern` contains something like
|
||||
`|/usr/lib/systemd/systemd-coredump`, use `coredumpctl` to run gdb:
|
||||
|
||||
----
|
||||
$ coredumpctl gdb $(which python3)
|
||||
----
|
||||
|
||||
Getting the stack trace
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now enter these commands at the gdb prompt:
|
||||
|
||||
----
|
||||
(gdb) set logging on
|
||||
(gdb) bt
|
||||
# you might have to press enter a few times until you get the prompt back
|
||||
(gdb) quit
|
||||
----
|
||||
|
||||
Copy the content of `gdb.txt` into the bug report. Please also add some words
|
||||
about what you were doing (or what pages you visited) before the crash
|
||||
happened.
|
||||
|
||||
[[windows]]
|
||||
@@ -130,9 +170,9 @@ file displayed there.
|
||||
|
||||
Now install
|
||||
http://www.microsoft.com/en-us/download/details.aspx?id=42933[DebugDiag] from
|
||||
Microsoft, then run the "DebugDiag 2 Analysis" tool. There, check
|
||||
"CrashHangAnalysis" and add your crash dump via "Add Data files". Then click
|
||||
"Start analysis".
|
||||
Microsoft, then run the *DebugDiag 2 Analysis* tool. There, check
|
||||
*CrashHangAnalysis* and add your crash dump via *Add Data files*. Then click
|
||||
*Start analysis*.
|
||||
|
||||
Close the Internet Explorer which opens when it's done and use the
|
||||
folder-button at the top left to get to the reports. There find the report 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.
|
||||
#
|
||||
|
||||
@@ -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, 1)
|
||||
__version_info__ = (0, 1, 4)
|
||||
__version__ = '.'.join(map(str, __version_info__))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5 and QtWebKit."
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -34,14 +34,14 @@ import faulthandler
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox
|
||||
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
|
||||
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
|
||||
QStandardPaths, QObject, Qt)
|
||||
QStandardPaths, QObject, Qt, QSocketNotifier)
|
||||
|
||||
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
|
||||
from qutebrowser.browser.network import qutescheme, proxy
|
||||
from qutebrowser.browser.network import qutescheme, proxy, networkmanager
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.misc import crashdialog, readline, ipc, earlyinit
|
||||
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
|
||||
@@ -62,6 +62,8 @@ class Application(QApplication):
|
||||
_crashdlg: The crash dialog currently open.
|
||||
_crashlogfile: A file handler to the fatal crash logfile.
|
||||
_event_filter: The EventFilter for the application.
|
||||
_signal_notifier: A QSocketNotifier used for signals on Unix.
|
||||
_signal_timer: A QTimer used to poll for signals on Windows.
|
||||
geometry: The geometry of the last closed main window.
|
||||
"""
|
||||
|
||||
@@ -106,12 +108,11 @@ class Application(QApplication):
|
||||
print(version.GPL_BOILERPLATE.strip())
|
||||
sys.exit(0)
|
||||
|
||||
sent = ipc.send_to_running_instance(self._args.command)
|
||||
if sent:
|
||||
sys.exit(0)
|
||||
|
||||
log.init.debug("Starting IPC server...")
|
||||
try:
|
||||
sent = ipc.send_to_running_instance(self._args.command)
|
||||
if sent:
|
||||
sys.exit(0)
|
||||
log.init.debug("Starting IPC server...")
|
||||
ipc.init()
|
||||
except ipc.IPCError as e:
|
||||
text = ('{}\n\nMaybe another instance is running but '
|
||||
@@ -131,7 +132,7 @@ class Application(QApplication):
|
||||
utils.actute_warning()
|
||||
try:
|
||||
self._init_modules()
|
||||
except OSError as e:
|
||||
except (OSError, UnicodeDecodeError) as e:
|
||||
msgbox = QMessageBox(
|
||||
QMessageBox.Critical, "Error while initializing!",
|
||||
"Error while initializing: {}".format(e))
|
||||
@@ -146,8 +147,8 @@ class Application(QApplication):
|
||||
log.init.debug("Connecting signals...")
|
||||
self._connect_signals()
|
||||
|
||||
log.init.debug("Applying python hacks...")
|
||||
self._python_hacks()
|
||||
log.init.debug("Setting up signal handlers...")
|
||||
self._setup_signals()
|
||||
|
||||
QDesktopServices.setUrlHandler('http', self.open_desktopservices_url)
|
||||
QDesktopServices.setUrlHandler('https', self.open_desktopservices_url)
|
||||
@@ -163,6 +164,8 @@ class Application(QApplication):
|
||||
|
||||
def _init_modules(self):
|
||||
"""Initialize all 'modules' which need to be initialized."""
|
||||
log.init.debug("Initializing network...")
|
||||
networkmanager.init()
|
||||
log.init.debug("Initializing readline-bridge...")
|
||||
readline_bridge = readline.ReadlineBridge()
|
||||
objreg.register('readline-bridge', readline_bridge)
|
||||
@@ -224,7 +227,7 @@ class Application(QApplication):
|
||||
if data:
|
||||
# Crashlog exists and has data in it, so something crashed
|
||||
# previously.
|
||||
self._crashdlg = crashdialog.FatalCrashDialog(
|
||||
self._crashdlg = crashdialog.get_fatal_crash_dialog(
|
||||
self._args.debug, data)
|
||||
self._crashdlg.show()
|
||||
else:
|
||||
@@ -314,7 +317,7 @@ class Application(QApplication):
|
||||
win_id = self._get_window(via_ipc, force_tab=True)
|
||||
log.init.debug("Startup cmd {}".format(cmd))
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
commandrunner.run_safely_init(cmd.lstrip(':'))
|
||||
commandrunner.run_safely_init(cmd[1:])
|
||||
elif not cmd:
|
||||
log.init.debug("Empty argument")
|
||||
win_id = self._get_window(via_ipc, force_window=True)
|
||||
@@ -324,7 +327,7 @@ class Application(QApplication):
|
||||
window=win_id)
|
||||
log.init.debug("Startup URL {}".format(cmd))
|
||||
try:
|
||||
url = urlutils.fuzzy_url(cmd, cwd)
|
||||
url = urlutils.fuzzy_url(cmd, cwd, relative=True)
|
||||
except urlutils.FuzzyUrlError as e:
|
||||
message.error(0, "Error in startup argument '{}': "
|
||||
"{}".format(cmd, e))
|
||||
@@ -377,19 +380,47 @@ class Application(QApplication):
|
||||
pass
|
||||
state_config['general']['quickstart-done'] = '1'
|
||||
|
||||
def _python_hacks(self):
|
||||
"""Get around some PyQt-oddities by evil hacks.
|
||||
def _setup_signals(self):
|
||||
"""Set up signal handlers.
|
||||
|
||||
This sets up the uncaught exception hook, quits with an appropriate
|
||||
exit status, and handles Ctrl+C properly by passing control to the
|
||||
Python interpreter once all 500ms.
|
||||
On Windows this uses a QTimer to periodically hand control over to
|
||||
Python so it can handle signals.
|
||||
|
||||
On Unix, it uses a QSocketNotifier with os.set_wakeup_fd to get
|
||||
notified.
|
||||
"""
|
||||
signal.signal(signal.SIGINT, self.interrupt)
|
||||
signal.signal(signal.SIGTERM, self.interrupt)
|
||||
timer = usertypes.Timer(self, 'python_hacks')
|
||||
timer.start(500)
|
||||
timer.timeout.connect(lambda: None)
|
||||
objreg.register('python-hack-timer', timer)
|
||||
|
||||
if os.name == 'posix' and hasattr(signal, 'set_wakeup_fd'):
|
||||
import fcntl
|
||||
read_fd, write_fd = os.pipe()
|
||||
for fd in (read_fd, write_fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
self._signal_notifier = QSocketNotifier(
|
||||
read_fd, QSocketNotifier.Read, self)
|
||||
self._signal_notifier.activated.connect(self._handle_signal_wakeup)
|
||||
signal.set_wakeup_fd(write_fd)
|
||||
else:
|
||||
self._signal_timer = usertypes.Timer(self, 'python_hacks')
|
||||
self._signal_timer.start(1000)
|
||||
self._signal_timer.timeout.connect(lambda: None)
|
||||
|
||||
@pyqtSlot()
|
||||
def _handle_signal_wakeup(self):
|
||||
"""This gets called via self._signal_notifier when there's a signal.
|
||||
|
||||
Python will get control here, so the signal will get handled.
|
||||
"""
|
||||
log.destroy.debug("Handling signal wakeup!")
|
||||
self._signal_notifier.setEnabled(False)
|
||||
read_fd = self._signal_notifier.socket()
|
||||
try:
|
||||
os.read(read_fd, 1)
|
||||
except OSError:
|
||||
log.destroy.exception("Failed to read wakeup fd.")
|
||||
self._signal_notifier.setEnabled(True)
|
||||
|
||||
def _connect_signals(self):
|
||||
"""Connect all signals to their slots."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -25,7 +25,7 @@ import functools
|
||||
import posixpath
|
||||
import zipfile
|
||||
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
from PyQt5.QtCore import QStandardPaths, QTimer
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import objreg, standarddir, log, message
|
||||
@@ -108,11 +108,12 @@ class HostBlocker:
|
||||
log.misc.exception("Failed to read host blocklist!")
|
||||
else:
|
||||
if config.get('content', 'host-block-lists') is not None:
|
||||
message.info('last-focused',
|
||||
"Run :adblock-update to get adblock lists.")
|
||||
QTimer.singleShot(500, functools.partial(
|
||||
message.info, 'last-focused',
|
||||
"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,8 +126,10 @@ 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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import os.path
|
||||
import shutil
|
||||
import functools
|
||||
@@ -164,13 +165,13 @@ class DownloadItem(QObject):
|
||||
done.
|
||||
fileobj: The file object to download the file to.
|
||||
reply: The QNetworkReply associated with this download.
|
||||
retry_info: A RetryInfo instance.
|
||||
_filename: The filename of the download.
|
||||
_redirects: How many time we were redirected already.
|
||||
_buffer: A BytesIO object to buffer incoming data until we know the
|
||||
target file.
|
||||
_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:
|
||||
@@ -201,7 +202,7 @@ class DownloadItem(QObject):
|
||||
reply: The QNetworkReply to download.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._retry_info = None
|
||||
self.retry_info = None
|
||||
self.done = False
|
||||
self.stats = DownloadItemStats(self)
|
||||
self.stats.updated.connect(self.data_changed)
|
||||
@@ -275,6 +276,8 @@ class DownloadItem(QObject):
|
||||
q.answered_yes.connect(self._create_fileobj)
|
||||
q.answered_no.connect(functools.partial(self.cancel, False))
|
||||
q.cancelled.connect(functools.partial(self.cancel, False))
|
||||
self.cancelled.connect(q.abort)
|
||||
self.error.connect(q.abort)
|
||||
message_bridge = objreg.get('message-bridge', scope='window',
|
||||
window=self._win_id)
|
||||
message_bridge.ask(q, blocking=False)
|
||||
@@ -290,7 +293,11 @@ class DownloadItem(QObject):
|
||||
self.error_msg = msg
|
||||
self.stats.finish()
|
||||
self.error.emit(msg)
|
||||
self.reply.abort()
|
||||
with log.hide_qt_warning('QNetworkReplyImplPrivate::error: Internal '
|
||||
'problem, this method must only be called '
|
||||
'once.'):
|
||||
# See https://codereview.qt-project.org/#/c/107863/
|
||||
self.reply.abort()
|
||||
self.reply.deleteLater()
|
||||
self.reply = None
|
||||
self.done = True
|
||||
@@ -310,8 +317,8 @@ class DownloadItem(QObject):
|
||||
reply.finished.connect(self.on_reply_finished)
|
||||
reply.error.connect(self.on_reply_error)
|
||||
reply.readyRead.connect(self.on_ready_read)
|
||||
self._retry_info = RetryInfo(request=reply.request(),
|
||||
manager=reply.manager())
|
||||
self.retry_info = RetryInfo(request=reply.request(),
|
||||
manager=reply.manager())
|
||||
if not self.fileobj:
|
||||
self._read_timer.start()
|
||||
# We could have got signals before we connected slots to them.
|
||||
@@ -364,7 +371,7 @@ class DownloadItem(QObject):
|
||||
def retry(self):
|
||||
"""Retry a failed download."""
|
||||
self.cancel()
|
||||
new_reply = self._retry_info.manager.get(self._retry_info.request)
|
||||
new_reply = self.retry_info.manager.get(self.retry_info.request)
|
||||
self.do_retry.emit(new_reply)
|
||||
|
||||
def open_file(self):
|
||||
@@ -385,6 +392,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.
|
||||
@@ -557,7 +568,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))
|
||||
@@ -639,7 +651,10 @@ class DownloadManager(QAbstractListModel):
|
||||
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(
|
||||
@@ -719,6 +734,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)
|
||||
@@ -785,10 +803,19 @@ class DownloadManager(QAbstractListModel):
|
||||
Return:
|
||||
A boolean.
|
||||
"""
|
||||
assert nam.adopted_downloads == 0
|
||||
for download in self.downloads:
|
||||
if download.reply is not None and download.reply.manager() is nam:
|
||||
return True
|
||||
return False
|
||||
running_download = (download.reply is not None and
|
||||
download.reply.manager() is nam)
|
||||
# user could request retry after tab is closed.
|
||||
failed_download = (download.done and (not download.successful) and
|
||||
download.retry_info.manager is nam)
|
||||
if running_download or failed_download:
|
||||
log.downloads.debug("Found running/failed downloads, "
|
||||
"adopting the NAM.")
|
||||
nam.adopted_downloads += 1
|
||||
download.destroyed.connect(nam.on_adopted_download_destroyed)
|
||||
return nam.adopted_downloads
|
||||
|
||||
def can_clear(self):
|
||||
"""Check if there are finished downloads to clear."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -24,7 +24,8 @@ import functools
|
||||
import subprocess
|
||||
import collections
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QEvent, Qt, QUrl,
|
||||
QTimer)
|
||||
from PyQt5.QtGui import QMouseEvent, QClipboard
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebKit import QWebElement
|
||||
@@ -108,7 +109,9 @@ class HintManager(QObject):
|
||||
Signals:
|
||||
mouse_event: Mouse event to be posted in the web view.
|
||||
arg: A QMouseEvent
|
||||
set_open_target: Set a new target to open the links in.
|
||||
start_hinting: Emitted when hinting starts, before a link is clicked.
|
||||
arg: The hinting target name.
|
||||
stop_hinting: Emitted after a link was clicked.
|
||||
"""
|
||||
|
||||
HINT_TEXTS = {
|
||||
@@ -129,7 +132,8 @@ class HintManager(QObject):
|
||||
}
|
||||
|
||||
mouse_event = pyqtSignal('QMouseEvent')
|
||||
set_open_target = pyqtSignal(str)
|
||||
start_hinting = pyqtSignal(str)
|
||||
stop_hinting = pyqtSignal()
|
||||
|
||||
def __init__(self, win_id, tab_id, parent=None):
|
||||
"""Constructor."""
|
||||
@@ -267,6 +271,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 +287,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', '{} !important'.format(int(2 ** 32 / 2 - 1))),
|
||||
('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 +325,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.
|
||||
@@ -365,20 +377,25 @@ class HintManager(QObject):
|
||||
action = "Hovering" if target == Target.hover else "Clicking"
|
||||
log.hints.debug("{} on '{}' at {}/{}".format(
|
||||
action, elem, pos.x(), pos.y()))
|
||||
self.start_hinting.emit(target.name)
|
||||
if target in (Target.tab, Target.tab_bg, Target.window):
|
||||
modifiers = Qt.ControlModifier
|
||||
else:
|
||||
modifiers = Qt.NoModifier
|
||||
events = [
|
||||
QMouseEvent(QEvent.MouseMove, pos, Qt.NoButton, Qt.NoButton,
|
||||
Qt.NoModifier),
|
||||
]
|
||||
if target != Target.hover:
|
||||
self.set_open_target.emit(target.name)
|
||||
events += [
|
||||
QMouseEvent(QEvent.MouseButtonPress, pos, Qt.LeftButton,
|
||||
Qt.NoButton, Qt.NoModifier),
|
||||
Qt.LeftButton, modifiers),
|
||||
QMouseEvent(QEvent.MouseButtonRelease, pos, Qt.LeftButton,
|
||||
Qt.NoButton, Qt.NoModifier),
|
||||
Qt.NoButton, modifiers),
|
||||
]
|
||||
for evt in events:
|
||||
self.mouse_event.emit(evt)
|
||||
QTimer.singleShot(0, self.stop_hinting.emit)
|
||||
|
||||
def _yank(self, url, context):
|
||||
"""Yank an element to the clipboard or primary selection.
|
||||
@@ -706,10 +723,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
|
||||
|
||||
@@ -725,16 +742,19 @@ 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 = {}
|
||||
for k, e in self._context.elems.items():
|
||||
if not self._is_hidden(e.label):
|
||||
visible[k] = e
|
||||
try:
|
||||
if not self._is_hidden(e.label):
|
||||
visible[k] = e
|
||||
except webelem.IsNullError:
|
||||
pass
|
||||
if not visible:
|
||||
# Whoops, filtered all hints
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.hint, 'all filtered')
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
@@ -50,7 +50,7 @@ def parse_content_disposition(reply):
|
||||
bytes(reply.rawHeader(content_disposition_header)))
|
||||
filename = content_disposition.filename()
|
||||
except UnicodeDecodeError:
|
||||
log.misc.exception("Error while decoding filename")
|
||||
log.rfc6266.exception("Error while decoding filename")
|
||||
else:
|
||||
is_inline = content_disposition.is_inline()
|
||||
# Then try to get filename from url
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
@@ -30,7 +30,7 @@ else:
|
||||
SSL_AVAILABLE = QSslSocket.supportsSsl()
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, log, usertypes, utils, objreg
|
||||
from qutebrowser.utils import message, log, usertypes, utils, objreg, qtutils
|
||||
from qutebrowser.browser import cookies
|
||||
from qutebrowser.browser.network import qutescheme, networkreply
|
||||
|
||||
@@ -38,15 +38,32 @@ from qutebrowser.browser.network import qutescheme, networkreply
|
||||
HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%'
|
||||
|
||||
|
||||
def init():
|
||||
"""Disable insecure SSL ciphers on old Qt versions."""
|
||||
if SSL_AVAILABLE:
|
||||
if not qtutils.version_check('5.3.0'):
|
||||
# Disable weak SSL ciphers.
|
||||
# See https://codereview.qt-project.org/#/c/75943/
|
||||
good_ciphers = [c for c in QSslSocket.supportedCiphers()
|
||||
if c.usedBits() >= 128]
|
||||
QSslSocket.setDefaultCiphers(good_ciphers)
|
||||
|
||||
|
||||
class NetworkManager(QNetworkAccessManager):
|
||||
|
||||
"""Our own QNetworkAccessManager.
|
||||
|
||||
Attributes:
|
||||
adopted_downloads: If downloads are running with this QNAM but the
|
||||
associated tab gets closed already, the NAM gets
|
||||
reparented to the DownloadManager. This counts the
|
||||
still running downloads, so the QNAM can clean
|
||||
itself up when this reaches zero again.
|
||||
_requests: Pending requests.
|
||||
_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,14 +71,16 @@ 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:
|
||||
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-November/035045.html
|
||||
super().__init__(parent)
|
||||
log.init.debug("NetworkManager init done")
|
||||
self.adopted_downloads = 0
|
||||
self._win_id = win_id
|
||||
self._tab_id = tab_id
|
||||
self._requests = []
|
||||
self._scheme_handlers = {
|
||||
'qute': qutescheme.QuteSchemeHandler(win_id),
|
||||
@@ -124,6 +143,9 @@ class NetworkManager(QNetworkAccessManager):
|
||||
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()
|
||||
@@ -203,6 +225,19 @@ class NetworkManager(QNetworkAccessManager):
|
||||
# switched from private mode to normal mode
|
||||
self._set_cookiejar()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_adopted_download_destroyed(self):
|
||||
"""Check if we can clean up if an adopted download was destroyed.
|
||||
|
||||
See the description for adopted_downloads for details.
|
||||
"""
|
||||
self.adopted_downloads -= 1
|
||||
log.downloads.debug("Adopted download destroyed, {} left.".format(
|
||||
self.adopted_downloads))
|
||||
assert self.adopted_downloads >= 0
|
||||
if self.adopted_downloads == 0:
|
||||
self.deleteLater()
|
||||
|
||||
# WORKAROUND for:
|
||||
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-September/034806.html
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# Based on the Eric5 helpviewer,
|
||||
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -41,7 +41,8 @@ class SignalFilter(QObject):
|
||||
BLACKLIST: List of signal names which should not be logged.
|
||||
"""
|
||||
|
||||
BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress']
|
||||
BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress',
|
||||
'cur_statusbar_message', 'cur_link_hovered']
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, PYQT_VERSION, Qt, QUrl
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, PYQT_VERSION, Qt, QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
from PyQt5.QtWidgets import QFileDialog
|
||||
@@ -32,7 +32,7 @@ from qutebrowser.config import config
|
||||
from qutebrowser.browser import http
|
||||
from qutebrowser.browser.network import networkmanager
|
||||
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
|
||||
objreg)
|
||||
objreg, debug)
|
||||
|
||||
|
||||
class BrowserPage(QWebPage):
|
||||
@@ -41,22 +41,35 @@ class BrowserPage(QWebPage):
|
||||
|
||||
Attributes:
|
||||
error_occured: Whether an error occured while loading.
|
||||
open_target: Where to open the next navigation request.
|
||||
("normal", "tab", "tab_bg")
|
||||
_hint_target: Override for open_target while hinting, or None.
|
||||
_extension_handlers: Mapping of QWebPage extensions to their handlers.
|
||||
_networkmnager: The NetworkManager used.
|
||||
_win_id: The window ID this BrowserPage is associated with.
|
||||
_ignore_load_started: Whether to ignore the next loadStarted signal.
|
||||
_is_shutting_down: Whether the page is currently shutting down.
|
||||
|
||||
Signals:
|
||||
shutting_down: Emitted when the page is currently shutting down.
|
||||
"""
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
shutting_down = pyqtSignal()
|
||||
|
||||
def __init__(self, win_id, tab_id, parent=None):
|
||||
super().__init__(parent)
|
||||
self._win_id = win_id
|
||||
self._is_shutting_down = False
|
||||
self._extension_handlers = {
|
||||
QWebPage.ErrorPageExtension: self._handle_errorpage,
|
||||
QWebPage.ChooseMultipleFilesExtension: self._handle_multiple_files,
|
||||
}
|
||||
self._ignore_load_started = False
|
||||
self.error_occured = False
|
||||
self._networkmanager = networkmanager.NetworkManager(win_id, self)
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
self._hint_target = None
|
||||
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 +85,10 @@ 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)
|
||||
if self._is_shutting_down:
|
||||
return (False, "")
|
||||
answer = self._ask("js: {}".format(msg), usertypes.PromptMode.text,
|
||||
default)
|
||||
if answer is None:
|
||||
return (False, "")
|
||||
else:
|
||||
@@ -120,8 +135,17 @@ class BrowserPage(QWebPage):
|
||||
else:
|
||||
error_str = info.errorString
|
||||
if error_str == networkmanager.HOSTBLOCK_ERROR_STRING:
|
||||
# We don't set error_occured in this case.
|
||||
error_str = "Request blocked by host blocker."
|
||||
# we don't set error_occured in this case.
|
||||
main_frame = info.frame.page().mainFrame()
|
||||
if info.frame != main_frame:
|
||||
# Content in an iframe -> Hide the frame so it doesn't use
|
||||
# any space. We can't hide the frame's documentElement
|
||||
# directly though.
|
||||
for elem in main_frame.documentElement().findAll('iframe'):
|
||||
if QUrl(elem.attribute('src')) == info.url:
|
||||
elem.setAttribute('style', 'display: none')
|
||||
return False
|
||||
else:
|
||||
self._ignore_load_started = True
|
||||
self.error_occured = True
|
||||
@@ -157,6 +181,41 @@ 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)
|
||||
self.shutting_down.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 shutdown(self):
|
||||
"""Prepare the web page for being deleted."""
|
||||
self._is_shutting_down = True
|
||||
self.shutting_down.emit()
|
||||
download_manager = objreg.get('download-manager', scope='window',
|
||||
window=self._win_id)
|
||||
nam = self.networkAccessManager()
|
||||
if download_manager.has_downloads_with_nam(nam):
|
||||
nam.setParent(download_manager)
|
||||
else:
|
||||
nam.shutdown()
|
||||
|
||||
def display_content(self, reply, mimetype):
|
||||
"""Display a QNetworkReply with an explicitely set mimetype."""
|
||||
self.mainFrame().setContent(reply.readAll(), mimetype, reply.url())
|
||||
@@ -226,6 +285,26 @@ class BrowserPage(QWebPage):
|
||||
else:
|
||||
self.error_occured = False
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_start_hinting(self, hint_target):
|
||||
"""Emitted before a hinting-click takes place.
|
||||
|
||||
Args:
|
||||
hint_target: A string to set self._hint_target to.
|
||||
"""
|
||||
t = getattr(usertypes.ClickTarget, hint_target, None)
|
||||
if t is None:
|
||||
return
|
||||
log.webview.debug("Setting force target to {}/{}".format(
|
||||
hint_target, t))
|
||||
self._hint_target = t
|
||||
|
||||
@pyqtSlot()
|
||||
def on_stop_hinting(self):
|
||||
"""Emitted when hinting is finished."""
|
||||
log.webview.debug("Finishing hinting.")
|
||||
self._hint_target = None
|
||||
|
||||
def userAgentForUrl(self, url):
|
||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||
ua = config.get('network', 'user-agent')
|
||||
@@ -268,18 +347,22 @@ 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)
|
||||
if self._is_shutting_down:
|
||||
return
|
||||
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)
|
||||
if self._is_shutting_down:
|
||||
return False
|
||||
ans = self._ask("[js confirm] {}".format(msg),
|
||||
usertypes.PromptMode.yesno)
|
||||
return bool(ans)
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, line, source):
|
||||
"""Override javaScriptConsoleMessage to use debug log."""
|
||||
log.js.debug("[{}:{}] {}".format(source, line, msg))
|
||||
if config.get('general', 'log-javascript-console'):
|
||||
log.js.debug("[{}:{}] {}".format(source, line, msg))
|
||||
|
||||
def chooseFile(self, _frame, suggested_file):
|
||||
"""Override QWebPage's chooseFile to be able to chose a file to upload.
|
||||
@@ -293,8 +376,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
|
||||
@@ -322,17 +405,26 @@ class BrowserPage(QWebPage):
|
||||
message.error(self._win_id, "Invalid link {} clicked!".format(
|
||||
urlstr))
|
||||
log.webview.debug(url.errorString())
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
return False
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window=self._win_id)
|
||||
open_target = self.view().open_target
|
||||
if open_target == usertypes.ClickTarget.tab:
|
||||
log.webview.debug("acceptNavigationRequest, url {}, type {}, hint "
|
||||
"target {}, open_target {}".format(
|
||||
urlstr, debug.qenum_key(QWebPage, typ),
|
||||
self._hint_target, self.open_target))
|
||||
if self._hint_target is not None:
|
||||
target = self._hint_target
|
||||
else:
|
||||
target = self.open_target
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
if target == usertypes.ClickTarget.tab:
|
||||
tabbed_browser.tabopen(url, False)
|
||||
return False
|
||||
elif open_target == usertypes.ClickTarget.tab_bg:
|
||||
elif target == usertypes.ClickTarget.tab_bg:
|
||||
tabbed_browser.tabopen(url, True)
|
||||
return False
|
||||
elif open_target == usertypes.ClickTarget.window:
|
||||
elif target == usertypes.ClickTarget.window:
|
||||
main_window = objreg.get('main-window', scope='window',
|
||||
window=self._win_id)
|
||||
win_id = main_window.spawn()
|
||||
|
||||
@@ -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,11 +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
|
||||
|
||||
@@ -54,7 +55,6 @@ class WebView(QWebView):
|
||||
statusbar_message: The current javscript statusbar message.
|
||||
inspector: The QWebInspector used for this webview.
|
||||
load_status: loading status of this page (index into LoadStatus)
|
||||
open_target: Where to open the next tab ("normal", "tab", "tab_bg")
|
||||
viewing_source: Whether the webview is currently displaying source
|
||||
code.
|
||||
registry: The ObjectRegistry associated with this tab.
|
||||
@@ -63,7 +63,6 @@ class WebView(QWebView):
|
||||
_has_ssl_errors: Whether SSL errors occured during loading.
|
||||
_zoom: A NeighborList with the zoom levels.
|
||||
_old_scroll_pos: The old scroll position.
|
||||
_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.
|
||||
@@ -76,15 +75,21 @@ class WebView(QWebView):
|
||||
linkHovered: QWebPages linkHovered signal exposed.
|
||||
load_status_changed: The loading status changed
|
||||
url_text_changed: Current URL string changed.
|
||||
shutting_down: Emitted when the view is shutting down.
|
||||
"""
|
||||
|
||||
scroll_pos_changed = pyqtSignal(int, int)
|
||||
linkHovered = pyqtSignal(str, str, str)
|
||||
load_status_changed = pyqtSignal(str)
|
||||
url_text_changed = pyqtSignal(str)
|
||||
shutting_down = pyqtSignal()
|
||||
|
||||
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
|
||||
@@ -92,8 +97,6 @@ class WebView(QWebView):
|
||||
self.scroll_pos = (-1, -1)
|
||||
self.statusbar_message = ''
|
||||
self._old_scroll_pos = (-1, -1)
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
self._force_open_target = None
|
||||
self._zoom = None
|
||||
self._has_ssl_errors = False
|
||||
self.init_neighborlist()
|
||||
@@ -113,11 +116,12 @@ 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)
|
||||
hintmanager.start_hinting.connect(page.on_start_hinting)
|
||||
hintmanager.stop_hinting.connect(page.on_stop_hinting)
|
||||
objreg.register('hintmanager', hintmanager, registry=self.registry)
|
||||
mode_manager = objreg.get('mode-manager', scope='window',
|
||||
window=win_id)
|
||||
@@ -254,8 +258,8 @@ class WebView(QWebView):
|
||||
self._check_insertmode = False
|
||||
try:
|
||||
elem = webelem.focus_elem(self.page().currentFrame())
|
||||
except webelem.IsNullError:
|
||||
log.mouse.warning("Element vanished!")
|
||||
except (webelem.IsNullError, RuntimeError):
|
||||
log.mouse.warning("Element/page vanished!")
|
||||
return
|
||||
if elem.is_editable():
|
||||
log.mouse.debug("Clicked editable element (delayed)!")
|
||||
@@ -273,41 +277,30 @@ class WebView(QWebView):
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
"""
|
||||
if self._force_open_target is not None:
|
||||
self.open_target = self._force_open_target
|
||||
self._force_open_target = None
|
||||
log.mouse.debug("Setting force target: {}".format(
|
||||
self.open_target))
|
||||
elif (e.button() == Qt.MidButton or
|
||||
e.modifiers() & Qt.ControlModifier):
|
||||
if e.button() == Qt.MidButton or e.modifiers() & Qt.ControlModifier:
|
||||
background_tabs = config.get('tabs', 'background-tabs')
|
||||
if e.modifiers() & Qt.ShiftModifier:
|
||||
background_tabs = not background_tabs
|
||||
if background_tabs:
|
||||
self.open_target = usertypes.ClickTarget.tab_bg
|
||||
target = usertypes.ClickTarget.tab_bg
|
||||
else:
|
||||
self.open_target = usertypes.ClickTarget.tab
|
||||
log.mouse.debug("Middle click, setting target: {}".format(
|
||||
self.open_target))
|
||||
target = usertypes.ClickTarget.tab
|
||||
self.page().open_target = target
|
||||
log.mouse.debug("Middle click, setting target: {}".format(target))
|
||||
else:
|
||||
self.open_target = usertypes.ClickTarget.normal
|
||||
self.page().open_target = usertypes.ClickTarget.normal
|
||||
log.mouse.debug("Normal click, setting normal target")
|
||||
|
||||
def shutdown(self):
|
||||
"""Shut down the webview."""
|
||||
self.shutting_down.emit()
|
||||
# We disable javascript because that prevents some segfaults when
|
||||
# quitting it seems.
|
||||
log.destroy.debug("Shutting down {!r}.".format(self))
|
||||
settings = self.settings()
|
||||
settings.setAttribute(QWebSettings.JavascriptEnabled, False)
|
||||
self.stop()
|
||||
download_manager = objreg.get('download-manager', scope='window',
|
||||
window=self._win_id)
|
||||
nam = self.page().networkAccessManager()
|
||||
if download_manager.has_downloads_with_nam(nam):
|
||||
nam.setParent(download_manager)
|
||||
else:
|
||||
nam.shutdown()
|
||||
self.page().shutdown()
|
||||
|
||||
def openurl(self, url):
|
||||
"""Open a URL in the browser.
|
||||
@@ -433,17 +426,6 @@ class WebView(QWebView):
|
||||
"left.".format(mode))
|
||||
self.setFocusPolicy(Qt.WheelFocus)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def set_force_open_target(self, target):
|
||||
"""Change the forced link target. Setter for _force_open_target.
|
||||
|
||||
Args:
|
||||
target: A string to set self._force_open_target to.
|
||||
"""
|
||||
t = getattr(usertypes.ClickTarget, target)
|
||||
log.webview.debug("Setting force target to {}/{}".format(target, t))
|
||||
self._force_open_target = t
|
||||
|
||||
def createWindow(self, wintype):
|
||||
"""Called by Qt when a page wants to create a new window.
|
||||
|
||||
@@ -502,7 +484,7 @@ class WebView(QWebView):
|
||||
|
||||
This does the following things:
|
||||
- Check if a link was clicked with the middle button or Ctrl and
|
||||
set the open_target attribute accordingly.
|
||||
set the page's open_target attribute accordingly.
|
||||
- Emit the editable_elem_selected signal if an editable element was
|
||||
clicked.
|
||||
|
||||
@@ -526,3 +508,9 @@ class WebView(QWebView):
|
||||
# We want to make sure we check the focus element after the WebView is
|
||||
# updated completely.
|
||||
QTimer.singleShot(0, self.mouserelease_insertmode)
|
||||
|
||||
def contextMenuEvent(self, e):
|
||||
"""Save a reference to the context menu so we can close it."""
|
||||
menu = self.page().createStandardContextMenu()
|
||||
self.shutting_down.connect(menu.close)
|
||||
menu.exec_(e.globalPos())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -271,8 +271,11 @@ class CommandRunner(QObject):
|
||||
maxsplit=maxsplit)
|
||||
for s in args:
|
||||
# remove quotes and replace \" by "
|
||||
s = re.sub(r"""(^|[^\\])["']""", r'\1', s)
|
||||
s = re.sub(r"""\\(["'])""", r'\1', s)
|
||||
if s == '""' or s == "''":
|
||||
s = ''
|
||||
else:
|
||||
s = re.sub(r"""(^|[^\\])["']""", r'\1', s)
|
||||
s = re.sub(r"""\\(["'])""", r'\1', s)
|
||||
self._args.append(s)
|
||||
break
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -32,7 +32,8 @@ import configparser
|
||||
import collections
|
||||
import collections.abc
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QStandardPaths, QUrl
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QStandardPaths, QUrl,
|
||||
QSettings)
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from qutebrowser.config import configdata, configexc, textwrapper
|
||||
@@ -123,7 +124,7 @@ def init(args):
|
||||
try:
|
||||
app = objreg.get('app')
|
||||
config_obj = ConfigManager(confdir, 'qutebrowser.conf', app)
|
||||
except (configexc.Error, configparser.Error) as e:
|
||||
except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
|
||||
log.init.exception(e)
|
||||
errstr = "Error while reading config:"
|
||||
try:
|
||||
@@ -141,7 +142,7 @@ def init(args):
|
||||
objreg.register('config', config_obj)
|
||||
try:
|
||||
key_config = keyconf.KeyConfigParser(confdir, 'keys.conf')
|
||||
except keyconf.KeyConfigError as e:
|
||||
except (keyconf.KeyConfigError, UnicodeDecodeError) as e:
|
||||
log.init.exception(e)
|
||||
errstr = "Error while reading key config:\n"
|
||||
if e.lineno is not None:
|
||||
@@ -164,6 +165,18 @@ def init(args):
|
||||
('completion', 'history-length'))
|
||||
objreg.register('command-history', command_history)
|
||||
|
||||
# Set the QSettings path to something like
|
||||
# ~/.config/qutebrowser/qsettings/qutebrowser/qutebrowser.conf so it
|
||||
# doesn't overwrite our config.
|
||||
#
|
||||
# This fixes one of the corruption issues here:
|
||||
# https://github.com/The-Compiler/qutebrowser/issues/515
|
||||
|
||||
config_path = standarddir.get(QStandardPaths.ConfigLocation, args)
|
||||
path = os.path.join(config_path, 'qsettings')
|
||||
for fmt in (QSettings.NativeFormat, QSettings.IniFormat):
|
||||
QSettings.setPath(fmt, QSettings.UserScope, path)
|
||||
|
||||
|
||||
class ConfigManager(QObject):
|
||||
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
@@ -190,6 +190,10 @@ DATA = collections.OrderedDict([
|
||||
SettingValue(typ.NewInstanceOpenTarget(), 'window'),
|
||||
"How to open links in an existing instance if a new one is "
|
||||
"launched."),
|
||||
|
||||
('log-javascript-console',
|
||||
SettingValue(typ.Bool(), 'false'),
|
||||
"Whether to log javascript console messages."),
|
||||
)),
|
||||
|
||||
('ui', sect.KeyValue(
|
||||
@@ -517,8 +521,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"
|
||||
@@ -566,14 +569,7 @@ DATA = collections.OrderedDict([
|
||||
|
||||
('searchengines', sect.ValueList(
|
||||
typ.SearchEngineName(), typ.SearchEngineUrl(),
|
||||
('DEFAULT', '${duckduckgo}'),
|
||||
('duckduckgo', 'https://duckduckgo.com/?q={}'),
|
||||
('ddg', '${duckduckgo}'),
|
||||
('google', 'https://encrypted.google.com/search?q={}'),
|
||||
('g', '${google}'),
|
||||
('wikipedia', 'http://en.wikipedia.org/w/index.php?'
|
||||
'title=Special:Search&search={}'),
|
||||
('wiki', '${wikipedia}'),
|
||||
('DEFAULT', 'https://duckduckgo.com/?q={}'),
|
||||
)),
|
||||
|
||||
('aliases', sect.ValueList(
|
||||
@@ -797,27 +793,27 @@ DATA = collections.OrderedDict([
|
||||
"Font used for the debugging console."),
|
||||
|
||||
('web-family-standard',
|
||||
SettingValue(typ.String(none_ok=True), ''),
|
||||
SettingValue(typ.FontFamily(none_ok=True), ''),
|
||||
"Font family for standard fonts."),
|
||||
|
||||
('web-family-fixed',
|
||||
SettingValue(typ.String(none_ok=True), ''),
|
||||
SettingValue(typ.FontFamily(none_ok=True), ''),
|
||||
"Font family for fixed fonts."),
|
||||
|
||||
('web-family-serif',
|
||||
SettingValue(typ.String(none_ok=True), ''),
|
||||
SettingValue(typ.FontFamily(none_ok=True), ''),
|
||||
"Font family for serif fonts."),
|
||||
|
||||
('web-family-sans-serif',
|
||||
SettingValue(typ.String(none_ok=True), ''),
|
||||
SettingValue(typ.FontFamily(none_ok=True), ''),
|
||||
"Font family for sans-serif fonts."),
|
||||
|
||||
('web-family-cursive',
|
||||
SettingValue(typ.String(none_ok=True), ''),
|
||||
SettingValue(typ.FontFamily(none_ok=True), ''),
|
||||
"Font family for cursive fonts."),
|
||||
|
||||
('web-family-fantasy',
|
||||
SettingValue(typ.String(none_ok=True), ''),
|
||||
SettingValue(typ.FontFamily(none_ok=True), ''),
|
||||
"Font family for fantasy fonts."),
|
||||
|
||||
('web-size-minimum',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -661,9 +661,9 @@ class Font(BaseType):
|
||||
) |
|
||||
# size (<float>pt | <int>px)
|
||||
(?P<size>[0-9]+((\.[0-9]+)?[pP][tT]|[pP][xX]))
|
||||
)\ # size/weight/style are space-separated
|
||||
)* # 0-inf size/weight/style tags
|
||||
(?P<family>[A-Za-z, "-]*)$ # mandatory font family""", re.VERBOSE)
|
||||
)\ # size/weight/style are space-separated
|
||||
)* # 0-inf size/weight/style tags
|
||||
(?P<family>[A-Za-z0-9, "-]*)$ # mandatory font family""", re.VERBOSE)
|
||||
|
||||
def validate(self, value):
|
||||
if not value:
|
||||
@@ -675,6 +675,25 @@ class Font(BaseType):
|
||||
raise configexc.ValidationError(value, "must be a valid font")
|
||||
|
||||
|
||||
class FontFamily(Font):
|
||||
|
||||
"""A Qt font family."""
|
||||
|
||||
def validate(self, value):
|
||||
if not value:
|
||||
if self._none_ok:
|
||||
return
|
||||
else:
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
match = self.font_regex.match(value)
|
||||
if not match:
|
||||
raise configexc.ValidationError(value, "must be a valid font")
|
||||
for group in 'style', 'weight', 'namedweight', 'size':
|
||||
if match.group(group):
|
||||
raise configexc.ValidationError(value, "may not include a "
|
||||
"{}!".format(group))
|
||||
|
||||
|
||||
class QtFont(Font):
|
||||
|
||||
"""A Font which gets converted to q QFont."""
|
||||
@@ -937,13 +956,13 @@ class ShellCommand(BaseType):
|
||||
return
|
||||
else:
|
||||
raise configexc.ValidationError(value, "may not be empty!")
|
||||
if self.placeholder and '{}' not in self.transform(value):
|
||||
raise configexc.ValidationError(value, "needs to contain a "
|
||||
"{}-placeholder.")
|
||||
try:
|
||||
shlex.split(value)
|
||||
except ValueError as 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:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -22,8 +22,6 @@
|
||||
Module attributes:
|
||||
ATTRIBUTES: A mapping from internal setting names to QWebSetting enum
|
||||
constants.
|
||||
SETTERS: A mapping from setting names to QWebSetting setter method names.
|
||||
settings: The global QWebSettings singleton instance.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
@@ -32,157 +30,345 @@ from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, standarddir, objreg
|
||||
from qutebrowser.utils import standarddir, objreg, log, utils, debug
|
||||
|
||||
MapType = usertypes.enum('MapType', ['attribute', 'setter', 'setter_none',
|
||||
'static_setter'])
|
||||
UNSET = object()
|
||||
|
||||
|
||||
class Base:
|
||||
|
||||
"""Base class for QWebSetting wrappers.
|
||||
|
||||
Attributes:
|
||||
_default: The default value of this setting.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._default = UNSET
|
||||
|
||||
def _get_qws(self, qws):
|
||||
"""Get the QWebSettings object to use.
|
||||
|
||||
Args:
|
||||
qws: The QWebSettings instance to use, or None to use the global
|
||||
instance.
|
||||
"""
|
||||
if qws is None:
|
||||
return QWebSettings.globalSettings()
|
||||
else:
|
||||
return qws
|
||||
|
||||
def save_default(self, qws=None):
|
||||
"""Save the default value based on the currently set one.
|
||||
|
||||
This does nothing if no getter is configured for this setting.
|
||||
|
||||
Args:
|
||||
qws: The QWebSettings instance to use, or None to use the global
|
||||
instance.
|
||||
"""
|
||||
try:
|
||||
self._default = self.get(qws)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def restore_default(self, qws=None):
|
||||
"""Restore the default value from the saved one.
|
||||
|
||||
This does nothing if the default has never been set.
|
||||
|
||||
Args:
|
||||
qws: The QWebSettings instance to use, or None to use the global
|
||||
instance.
|
||||
"""
|
||||
if self._default is not UNSET:
|
||||
self._set(self._default, qws=qws)
|
||||
|
||||
def get(self, qws=None):
|
||||
"""Get the value of this setting.
|
||||
|
||||
Must be overridden by subclasses.
|
||||
|
||||
Args:
|
||||
qws: The QWebSettings instance to use, or None to use the global
|
||||
instance.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set(self, value, qws=None):
|
||||
"""Set the value of this setting.
|
||||
|
||||
Args:
|
||||
value: The value to set.
|
||||
qws: The QWebSettings instance to use, or None to use the global
|
||||
instance.
|
||||
"""
|
||||
if value is None:
|
||||
self.restore_default(qws)
|
||||
else:
|
||||
self._set(value, qws=qws)
|
||||
|
||||
def _set(self, value, qws):
|
||||
"""Inner function to set the value of this setting.
|
||||
|
||||
Must be overridden by subclasses.
|
||||
|
||||
Args:
|
||||
value: The value to set.
|
||||
qws: The QWebSettings instance to use, or None to use the global
|
||||
instance.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Attribute(Base):
|
||||
|
||||
"""A setting set via QWebSettings::setAttribute.
|
||||
|
||||
Attributes:
|
||||
self._attribute: A QWebSettings::WebAttribute instance.
|
||||
"""
|
||||
|
||||
def __init__(self, attribute):
|
||||
super().__init__()
|
||||
self._attribute = attribute
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(
|
||||
self, attribute=debug.qenum_key(QWebSettings, self._attribute),
|
||||
constructor=True)
|
||||
|
||||
def get(self, qws=None):
|
||||
return self._get_qws(qws).attribute(self._attribute)
|
||||
|
||||
def _set(self, value, qws=None):
|
||||
self._get_qws(qws).setAttribute(self._attribute, value)
|
||||
|
||||
|
||||
class Setter(Base):
|
||||
|
||||
"""A setting set via QWebSettings getter/setter methods.
|
||||
|
||||
This will pass the QWebSettings instance ("self") as first argument to the
|
||||
methods, so self._getter/self._setter are the *unbound* methods.
|
||||
|
||||
Attributes:
|
||||
_getter: The unbound QWebSettings method to get this value, or None.
|
||||
_setter: The unbound QWebSettings method to set this value.
|
||||
_args: An iterable of the arguments to pass to the setter/getter
|
||||
(before the value, for the setter).
|
||||
_unpack: Whether to unpack args (True) or pass them directly (False).
|
||||
"""
|
||||
|
||||
def __init__(self, getter, setter, args=(), unpack=False):
|
||||
super().__init__()
|
||||
self._getter = getter
|
||||
self._setter = setter
|
||||
self._args = args
|
||||
self._unpack = unpack
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, getter=self._getter, setter=self._setter,
|
||||
args=self._args, unpack=self._unpack,
|
||||
constructor=True)
|
||||
|
||||
def get(self, qws=None):
|
||||
if self._getter is None:
|
||||
raise AttributeError("No getter set!")
|
||||
return self._getter(self._get_qws(qws), *self._args)
|
||||
|
||||
def _set(self, value, qws=None):
|
||||
args = [self._get_qws(qws)]
|
||||
args.extend(self._args)
|
||||
if self._unpack:
|
||||
args.extend(value)
|
||||
else:
|
||||
args.append(value)
|
||||
self._setter(*args)
|
||||
|
||||
|
||||
class NullStringSetter(Setter):
|
||||
|
||||
"""A setter for settings requiring a null QString as default.
|
||||
|
||||
This overrides save_default so None is saved for an empty string. This is
|
||||
needed for the CSS media type, because it returns an empty Python string
|
||||
when getting the value, but setting it to the default requires passing None
|
||||
(a null QString) instead of an empty string.
|
||||
"""
|
||||
|
||||
def save_default(self, qws=None):
|
||||
try:
|
||||
val = self.get(qws)
|
||||
except AttributeError:
|
||||
pass
|
||||
if val == '':
|
||||
self._set(None, qws=qws)
|
||||
else:
|
||||
self._set(val, qws=qws)
|
||||
|
||||
|
||||
class GlobalSetter(Setter):
|
||||
|
||||
"""A setting set via static QWebSettings getter/setter methods.
|
||||
|
||||
self._getter/self._setter are the *bound* methods.
|
||||
"""
|
||||
|
||||
def get(self, qws=None):
|
||||
if qws is not None:
|
||||
raise ValueError("qws may not be set with GlobalSetters!")
|
||||
if self._getter is None:
|
||||
raise AttributeError("No getter set!")
|
||||
return self._getter(*self._args)
|
||||
|
||||
def _set(self, value, qws=None):
|
||||
if qws is not None:
|
||||
raise ValueError("qws may not be set with GlobalSetters!")
|
||||
args = list(self._args)
|
||||
if self._unpack:
|
||||
args.extend(value)
|
||||
else:
|
||||
args.append(value)
|
||||
self._setter(*args)
|
||||
|
||||
|
||||
MAPPINGS = {
|
||||
'content': {
|
||||
'allow-images':
|
||||
(MapType.attribute, QWebSettings.AutoLoadImages),
|
||||
Attribute(QWebSettings.AutoLoadImages),
|
||||
'allow-javascript':
|
||||
(MapType.attribute, QWebSettings.JavascriptEnabled),
|
||||
Attribute(QWebSettings.JavascriptEnabled),
|
||||
'javascript-can-open-windows':
|
||||
(MapType.attribute, QWebSettings.JavascriptCanOpenWindows),
|
||||
Attribute(QWebSettings.JavascriptCanOpenWindows),
|
||||
'javascript-can-close-windows':
|
||||
(MapType.attribute, QWebSettings.JavascriptCanCloseWindows),
|
||||
Attribute(QWebSettings.JavascriptCanCloseWindows),
|
||||
'javascript-can-access-clipboard':
|
||||
(MapType.attribute, QWebSettings.JavascriptCanAccessClipboard),
|
||||
Attribute(QWebSettings.JavascriptCanAccessClipboard),
|
||||
#'allow-java':
|
||||
# (MapType.attribute, QWebSettings.JavaEnabled),
|
||||
# Attribute(QWebSettings.JavaEnabled),
|
||||
'allow-plugins':
|
||||
(MapType.attribute, QWebSettings.PluginsEnabled),
|
||||
Attribute(QWebSettings.PluginsEnabled),
|
||||
'local-content-can-access-remote-urls':
|
||||
(MapType.attribute, QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
Attribute(QWebSettings.LocalContentCanAccessRemoteUrls),
|
||||
'local-content-can-access-file-urls':
|
||||
(MapType.attribute, QWebSettings.LocalContentCanAccessFileUrls),
|
||||
Attribute(QWebSettings.LocalContentCanAccessFileUrls),
|
||||
},
|
||||
'network': {
|
||||
'dns-prefetch':
|
||||
(MapType.attribute, QWebSettings.DnsPrefetchEnabled),
|
||||
Attribute(QWebSettings.DnsPrefetchEnabled),
|
||||
},
|
||||
'input': {
|
||||
'spatial-navigation':
|
||||
(MapType.attribute, QWebSettings.SpatialNavigationEnabled),
|
||||
Attribute(QWebSettings.SpatialNavigationEnabled),
|
||||
'links-included-in-focus-chain':
|
||||
(MapType.attribute, QWebSettings.LinksIncludedInFocusChain),
|
||||
Attribute(QWebSettings.LinksIncludedInFocusChain),
|
||||
},
|
||||
'fonts': {
|
||||
'web-family-standard':
|
||||
(MapType.setter_none, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.StandardFont, v)),
|
||||
Setter(getter=QWebSettings.fontFamily,
|
||||
setter=QWebSettings.setFontFamily,
|
||||
args=[QWebSettings.StandardFont]),
|
||||
'web-family-fixed':
|
||||
(MapType.setter_none, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.FixedFont, v)),
|
||||
Setter(getter=QWebSettings.fontFamily,
|
||||
setter=QWebSettings.setFontFamily,
|
||||
args=[QWebSettings.FixedFont]),
|
||||
'web-family-serif':
|
||||
(MapType.setter_none, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.SerifFont, v)),
|
||||
Setter(getter=QWebSettings.fontFamily,
|
||||
setter=QWebSettings.setFontFamily,
|
||||
args=[QWebSettings.SerifFont]),
|
||||
'web-family-sans-serif':
|
||||
(MapType.setter_none, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.SansSerifFont, v)),
|
||||
Setter(getter=QWebSettings.fontFamily,
|
||||
setter=QWebSettings.setFontFamily,
|
||||
args=[QWebSettings.SansSerifFont]),
|
||||
'web-family-cursive':
|
||||
(MapType.setter_none, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.CursiveFont, v)),
|
||||
Setter(getter=QWebSettings.fontFamily,
|
||||
setter=QWebSettings.setFontFamily,
|
||||
args=[QWebSettings.CursiveFont]),
|
||||
'web-family-fantasy':
|
||||
(MapType.setter_none, lambda qws, v:
|
||||
qws.setFontFamily(QWebSettings.FantasyFont, v)),
|
||||
Setter(getter=QWebSettings.fontFamily,
|
||||
setter=QWebSettings.setFontFamily,
|
||||
args=[QWebSettings.FantasyFont]),
|
||||
'web-size-minimum':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.MinimumFontSize, v)),
|
||||
Setter(getter=QWebSettings.fontSize,
|
||||
setter=QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumFontSize]),
|
||||
'web-size-minimum-logical':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.MinimumLogicalFontSize, v)),
|
||||
Setter(getter=QWebSettings.fontSize,
|
||||
setter=QWebSettings.setFontSize,
|
||||
args=[QWebSettings.MinimumLogicalFontSize]),
|
||||
'web-size-default':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.DefaultFontSize, v)),
|
||||
Setter(getter=QWebSettings.fontSize,
|
||||
setter=QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFontSize]),
|
||||
'web-size-default-fixed':
|
||||
(MapType.setter, lambda qws, v:
|
||||
qws.setFontSize(QWebSettings.DefaultFixedFontSize, v)),
|
||||
Setter(getter=QWebSettings.fontSize,
|
||||
setter=QWebSettings.setFontSize,
|
||||
args=[QWebSettings.DefaultFixedFontSize]),
|
||||
},
|
||||
'ui': {
|
||||
'zoom-text-only':
|
||||
(MapType.attribute, QWebSettings.ZoomTextOnly),
|
||||
Attribute(QWebSettings.ZoomTextOnly),
|
||||
'frame-flattening':
|
||||
(MapType.attribute, QWebSettings.FrameFlatteningEnabled),
|
||||
Attribute(QWebSettings.FrameFlatteningEnabled),
|
||||
'user-stylesheet':
|
||||
(MapType.setter_none, lambda qws, v: qws.setUserStyleSheetUrl(v)),
|
||||
Setter(getter=QWebSettings.userStyleSheetUrl,
|
||||
setter=QWebSettings.setUserStyleSheetUrl),
|
||||
'css-media-type':
|
||||
(MapType.setter, lambda qws, v: qws.setCSSMediaType(v)),
|
||||
NullStringSetter(getter=QWebSettings.cssMediaType,
|
||||
setter=QWebSettings.setCSSMediaType),
|
||||
#'accelerated-compositing':
|
||||
# (MapType.attribute, QWebSettings.AcceleratedCompositingEnabled),
|
||||
# Attribute(QWebSettings.AcceleratedCompositingEnabled),
|
||||
#'tiled-backing-store':
|
||||
# (MapType.attribute, QWebSettings.TiledBackingStoreEnabled),
|
||||
# Attribute(QWebSettings.TiledBackingStoreEnabled),
|
||||
},
|
||||
'storage': {
|
||||
'offline-storage-database':
|
||||
(MapType.attribute, QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
Attribute(QWebSettings.OfflineStorageDatabaseEnabled),
|
||||
'offline-web-application-storage':
|
||||
(MapType.attribute,
|
||||
QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
|
||||
'local-storage':
|
||||
(MapType.attribute, QWebSettings.LocalStorageEnabled),
|
||||
Attribute(QWebSettings.LocalStorageEnabled),
|
||||
'maximum-pages-in-cache':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setMaximumPagesInCache(v)),
|
||||
GlobalSetter(getter=QWebSettings.maximumPagesInCache,
|
||||
setter=QWebSettings.setMaximumPagesInCache),
|
||||
'object-cache-capacities':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setObjectCacheCapacities(*v)),
|
||||
GlobalSetter(getter=None,
|
||||
setter=QWebSettings.setObjectCacheCapacities,
|
||||
unpack=True),
|
||||
'offline-storage-default-quota':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setOfflineStorageDefaultQuota(v)),
|
||||
GlobalSetter(getter=QWebSettings.offlineStorageDefaultQuota,
|
||||
setter=QWebSettings.setOfflineStorageDefaultQuota),
|
||||
'offline-web-application-cache-quota':
|
||||
(MapType.static_setter, lambda v:
|
||||
QWebSettings.setOfflineWebApplicationCacheQuota(v)),
|
||||
GlobalSetter(
|
||||
getter=QWebSettings.offlineWebApplicationCacheQuota,
|
||||
setter=QWebSettings.setOfflineWebApplicationCacheQuota),
|
||||
},
|
||||
'general': {
|
||||
'private-browsing':
|
||||
(MapType.attribute, QWebSettings.PrivateBrowsingEnabled),
|
||||
Attribute(QWebSettings.PrivateBrowsingEnabled),
|
||||
'developer-extras':
|
||||
(MapType.attribute, QWebSettings.DeveloperExtrasEnabled),
|
||||
Attribute(QWebSettings.DeveloperExtrasEnabled),
|
||||
'print-element-backgrounds':
|
||||
(MapType.attribute, QWebSettings.PrintElementBackgrounds),
|
||||
Attribute(QWebSettings.PrintElementBackgrounds),
|
||||
'xss-auditing':
|
||||
(MapType.attribute, QWebSettings.XSSAuditingEnabled),
|
||||
Attribute(QWebSettings.XSSAuditingEnabled),
|
||||
'site-specific-quirks':
|
||||
(MapType.attribute, QWebSettings.SiteSpecificQuirksEnabled),
|
||||
Attribute(QWebSettings.SiteSpecificQuirksEnabled),
|
||||
'default-encoding':
|
||||
(MapType.setter_none, lambda qws, v:
|
||||
qws.setDefaultTextEncoding(v)),
|
||||
Setter(getter=QWebSettings.defaultTextEncoding,
|
||||
setter=QWebSettings.setDefaultTextEncoding),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
settings = None
|
||||
|
||||
|
||||
def _set_setting(typ, arg, value):
|
||||
"""Set a QWebSettings setting.
|
||||
|
||||
Args:
|
||||
typ: The type of the item.
|
||||
arg: The argument (attribute/handler)
|
||||
value: The value to set.
|
||||
"""
|
||||
if not isinstance(typ, MapType):
|
||||
raise TypeError("Type {} is no MapType member!".format(typ))
|
||||
if typ == MapType.attribute:
|
||||
settings.setAttribute(arg, value)
|
||||
elif typ == MapType.setter_none:
|
||||
if value is None:
|
||||
value = ""
|
||||
arg(settings, value)
|
||||
elif typ == MapType.setter and value is not None:
|
||||
arg(settings, value)
|
||||
elif typ == MapType.static_setter and value is not None:
|
||||
arg(value)
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the global QWebSettings."""
|
||||
cachedir = standarddir.get(QStandardPaths.CacheLocation)
|
||||
QWebSettings.setIconDatabasePath(cachedir)
|
||||
if config.get('general', 'private-browsing'):
|
||||
QWebSettings.setIconDatabasePath('')
|
||||
else:
|
||||
QWebSettings.setIconDatabasePath(cachedir)
|
||||
QWebSettings.setOfflineWebApplicationCachePath(
|
||||
os.path.join(cachedir, 'application-cache'))
|
||||
datadir = standarddir.get(QStandardPaths.DataLocation)
|
||||
@@ -191,20 +377,28 @@ def init():
|
||||
QWebSettings.setOfflineStoragePath(
|
||||
os.path.join(datadir, 'offline-storage'))
|
||||
|
||||
global settings
|
||||
settings = QWebSettings.globalSettings()
|
||||
for sectname, section in MAPPINGS.items():
|
||||
for optname, (typ, arg) in section.items():
|
||||
for optname, mapping in section.items():
|
||||
mapping.save_default()
|
||||
value = config.get(sectname, optname)
|
||||
_set_setting(typ, arg, value)
|
||||
log.misc.debug("Setting {} -> {} to {!r}".format(sectname, optname,
|
||||
value))
|
||||
mapping.set(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]
|
||||
except KeyError:
|
||||
return
|
||||
value = config.get(section, option)
|
||||
_set_setting(typ, arg, value)
|
||||
if (section, option) == ('general', 'private-browsing'):
|
||||
cachedir = standarddir.get(QStandardPaths.CacheLocation)
|
||||
if config.get('general', 'private-browsing'):
|
||||
QWebSettings.setIconDatabasePath('')
|
||||
else:
|
||||
QWebSettings.setIconDatabasePath(cachedir)
|
||||
else:
|
||||
try:
|
||||
mapping = MAPPINGS[section][option]
|
||||
except KeyError:
|
||||
return
|
||||
value = config.get(section, option)
|
||||
mapping.set(value)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -26,8 +26,9 @@ Module attributes:
|
||||
import functools
|
||||
|
||||
from PyQt5.QtGui import QWindow
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QEvent
|
||||
from PyQt5.QtCore import pyqtSignal, Qt, QObject, QEvent
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebKitWidgets import QWebView
|
||||
|
||||
from qutebrowser.keyinput import modeparsers, keyparser
|
||||
from qutebrowser.config import config
|
||||
@@ -106,15 +107,29 @@ class EventFilter(QObject):
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Forward events to the correct modeman."""
|
||||
if not self._activated:
|
||||
return False
|
||||
try:
|
||||
modeman = objreg.get('mode-manager', scope='window',
|
||||
window='current')
|
||||
return modeman.eventFilter(obj, event)
|
||||
except objreg.RegistryUnavailableError:
|
||||
# No window available yet, or not a MainWindow
|
||||
return False
|
||||
if not self._activated:
|
||||
return False
|
||||
if event.type() not in [QEvent.KeyPress, QEvent.KeyRelease]:
|
||||
# We're not interested in non-key-events so we pass them
|
||||
# through.
|
||||
return False
|
||||
if not isinstance(obj, QWindow):
|
||||
# We already handled this same event at some point earlier, so
|
||||
# we're not interested in it anymore.
|
||||
return False
|
||||
if (QApplication.instance().activeWindow() not in
|
||||
objreg.window_registry.values()):
|
||||
# Some other window (print dialog, etc.) is focused so we pass
|
||||
# the event through.
|
||||
return False
|
||||
try:
|
||||
modeman = objreg.get('mode-manager', scope='window',
|
||||
window='current')
|
||||
return modeman.eventFilter(event)
|
||||
except objreg.RegistryUnavailableError:
|
||||
# No window available yet, or not a MainWindow
|
||||
return False
|
||||
except:
|
||||
# If there is an exception in here and we leave the eventfilter
|
||||
# activated, we'll get an infinite loop and a stack overflow.
|
||||
@@ -154,7 +169,7 @@ class ModeManager(QObject):
|
||||
self._win_id = win_id
|
||||
self._handlers = {}
|
||||
self.passthrough = []
|
||||
self.mode = usertypes.KeyMode.none
|
||||
self.mode = usertypes.KeyMode.normal
|
||||
self._releaseevents_to_pass = []
|
||||
self._forward_unbound_keys = config.get(
|
||||
'input', 'forward-unbound-keys')
|
||||
@@ -181,9 +196,13 @@ class ModeManager(QObject):
|
||||
handled = handler(event) if handler is not None else False
|
||||
|
||||
is_non_alnum = bool(event.modifiers()) or not event.text().strip()
|
||||
focus_widget = QApplication.instance().focusWidget()
|
||||
is_tab = event.key() in (Qt.Key_Tab, Qt.Key_Backtab)
|
||||
|
||||
if handled:
|
||||
filter_this = True
|
||||
elif is_tab and not isinstance(focus_widget, QWebView):
|
||||
filter_this = True
|
||||
elif (curmode in self.passthrough or
|
||||
self._forward_unbound_keys == 'all' or
|
||||
(self._forward_unbound_keys == 'auto' and is_non_alnum)):
|
||||
@@ -196,12 +215,11 @@ class ModeManager(QObject):
|
||||
|
||||
if curmode != usertypes.KeyMode.insert:
|
||||
log.modes.debug("handled: {}, forward-unbound-keys: {}, "
|
||||
"passthrough: {}, is_non_alnum: {} --> filter: "
|
||||
"{} (focused: {!r})".format(
|
||||
"passthrough: {}, is_non_alnum: {}, is_tab {} --> "
|
||||
"filter: {} (focused: {!r})".format(
|
||||
handled, self._forward_unbound_keys,
|
||||
curmode in self.passthrough,
|
||||
is_non_alnum, filter_this,
|
||||
QApplication.instance().focusWidget()))
|
||||
curmode in self.passthrough, is_non_alnum,
|
||||
is_tab, filter_this, focus_widget))
|
||||
return filter_this
|
||||
|
||||
def _eventFilter_keyrelease(self, event):
|
||||
@@ -313,7 +331,7 @@ class ModeManager(QObject):
|
||||
self._forward_unbound_keys = config.get(
|
||||
'input', 'forward-unbound-keys')
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
def eventFilter(self, event):
|
||||
"""Filter all events based on the currently set mode.
|
||||
|
||||
Also calls the real keypress handler.
|
||||
@@ -327,21 +345,7 @@ class ModeManager(QObject):
|
||||
if self.mode is None:
|
||||
# We got events before mode is set, so just pass them through.
|
||||
return False
|
||||
typ = event.type()
|
||||
if typ not in [QEvent.KeyPress, QEvent.KeyRelease]:
|
||||
# We're not interested in non-key-events so we pass them through.
|
||||
return False
|
||||
if not isinstance(obj, QWindow):
|
||||
# We already handled this same event at some point earlier, so
|
||||
# we're not interested in it anymore.
|
||||
return False
|
||||
if (QApplication.instance().activeWindow() not in
|
||||
objreg.window_registry.values()):
|
||||
# Some other window (print dialog, etc.) is focused so we pass
|
||||
# the event through.
|
||||
return False
|
||||
|
||||
if typ == QEvent.KeyPress:
|
||||
if event.type() == QEvent.KeyPress:
|
||||
return self._eventFilter_keypress(event)
|
||||
else:
|
||||
return self._eventFilter_keyrelease(event)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -22,7 +22,6 @@
|
||||
import binascii
|
||||
import base64
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||
@@ -96,7 +95,10 @@ class MainWindow(QWidget):
|
||||
window=win_id)
|
||||
self._vbox.addWidget(self._tabbed_browser)
|
||||
|
||||
self.status = bar.StatusBar(win_id)
|
||||
# We need to set an explicit parent for StatusBar because it does some
|
||||
# show/hide magic immediately which would mean it'd show up as a
|
||||
# window.
|
||||
self.status = bar.StatusBar(win_id, parent=self)
|
||||
self._vbox.addWidget(self.status)
|
||||
|
||||
self._completion = completionwidget.CompletionView(win_id, self)
|
||||
@@ -112,8 +114,6 @@ class MainWindow(QWidget):
|
||||
modeman.init(self.win_id, self)
|
||||
|
||||
self._connect_signals()
|
||||
QTimer.singleShot(0, functools.partial(
|
||||
modeman.enter, win_id, usertypes.KeyMode.normal, 'init'))
|
||||
|
||||
# When we're here the statusbar might not even really exist yet, so
|
||||
# resizing will fail. Therefore, we use singleShot QTimers to make sure
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -252,7 +252,7 @@ class StatusBar(QWidget):
|
||||
if self._previous_widget == PreviousWidget.prompt:
|
||||
self._stack.setCurrentWidget(self.prompt)
|
||||
elif self._previous_widget == PreviousWidget.command:
|
||||
self._stack.setCurrentWidget(self.command)
|
||||
self._stack.setCurrentWidget(self.cmd)
|
||||
elif self._previous_widget == PreviousWidget.none:
|
||||
pass
|
||||
else:
|
||||
@@ -267,7 +267,7 @@ class StatusBar(QWidget):
|
||||
def _show_cmd_widget(self):
|
||||
"""Show command widget instead of temporary text."""
|
||||
self._set_error(False)
|
||||
self._previous_widget = PreviousWidget.prompt
|
||||
self._previous_widget = PreviousWidget.command
|
||||
if self._text_pop_timer.isActive():
|
||||
self._timer_was_active = True
|
||||
self._text_pop_timer.stop()
|
||||
|
||||
@@ -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.
|
||||
#
|
||||
@@ -164,7 +164,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
|
||||
self.history.append(text)
|
||||
modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept')
|
||||
if text[0] in signals:
|
||||
signals[text[0]].emit(text.lstrip(text[0]))
|
||||
signals[text[0]].emit(text[1:])
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def on_mode_left(self, mode):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,7 +21,8 @@
|
||||
|
||||
import functools
|
||||
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QWidget, QLineEdit
|
||||
from PyQt5.QtCore import QSize
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QWidget, QLineEdit, QSizePolicy
|
||||
|
||||
from qutebrowser.mainwindow.statusbar import textbase, prompter
|
||||
from qutebrowser.utils import objreg, utils
|
||||
@@ -35,6 +36,16 @@ class PromptLineEdit(misc.MinimalLineEditMixin, QLineEdit):
|
||||
def __init__(self, parent=None):
|
||||
QLineEdit.__init__(self, parent)
|
||||
misc.MinimalLineEditMixin.__init__(self)
|
||||
self.textChanged.connect(self.updateGeometry)
|
||||
|
||||
def sizeHint(self):
|
||||
"""Dynamically calculate the needed size."""
|
||||
height = super().sizeHint().height()
|
||||
text = self.text()
|
||||
if not text:
|
||||
text = 'x'
|
||||
width = self.fontMetrics().width(text)
|
||||
return QSize(width, height)
|
||||
|
||||
|
||||
class Prompt(QWidget):
|
||||
@@ -58,6 +69,8 @@ class Prompt(QWidget):
|
||||
self._hbox.addWidget(self.txt)
|
||||
|
||||
self.lineedit = PromptLineEdit()
|
||||
self.lineedit.setSizePolicy(QSizePolicy.MinimumExpanding,
|
||||
QSizePolicy.Fixed)
|
||||
self._hbox.addWidget(self.lineedit)
|
||||
|
||||
prompter_obj = prompter.Prompter(win_id)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -39,6 +39,11 @@ from qutebrowser.utils import (log, message, usertypes, utils, qtutils, objreg,
|
||||
UndoEntry = collections.namedtuple('UndoEntry', ['url', 'history'])
|
||||
|
||||
|
||||
class TabDeletedError(Exception):
|
||||
|
||||
"""Exception raised when _tab_index is called for a deleted tab."""
|
||||
|
||||
|
||||
class TabbedBrowser(tabwidget.TabWidget):
|
||||
|
||||
"""A TabWidget with QWebViews inside.
|
||||
@@ -59,6 +64,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
tabbar -> new-tab-position set to 'left'.
|
||||
_tab_insert_idx_right: Same as above, for 'right'.
|
||||
_undo_stack: List of UndoEntry namedtuples of closed tabs.
|
||||
_shutting_down: Whether we're currently shutting down.
|
||||
|
||||
Signals:
|
||||
cur_progress: Progress of the current tab changed (loadProgress).
|
||||
@@ -97,6 +103,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
self._win_id = win_id
|
||||
self._tab_insert_idx_left = 0
|
||||
self._tab_insert_idx_right = -1
|
||||
self._shutting_down = False
|
||||
self.tabCloseRequested.connect(self.on_tab_close_requested)
|
||||
self.currentChanged.connect(self.on_current_changed)
|
||||
self.cur_load_started.connect(self.on_cur_load_started)
|
||||
@@ -118,6 +125,21 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, count=self.count())
|
||||
|
||||
def _tab_index(self, tab):
|
||||
"""Get the index of a given tab.
|
||||
|
||||
Raises TabDeletedError if the tab doesn't exist anymore.
|
||||
"""
|
||||
try:
|
||||
idx = self.indexOf(tab)
|
||||
except RuntimeError as e:
|
||||
log.webview.debug("Got invalid tab ({})!".format(e))
|
||||
raise TabDeletedError(e)
|
||||
if idx == -1:
|
||||
log.webview.debug("Got invalid tab (index is -1)!")
|
||||
raise TabDeletedError("index is -1!")
|
||||
return idx
|
||||
|
||||
def widgets(self):
|
||||
"""Get a list of open tab widgets.
|
||||
|
||||
@@ -199,10 +221,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
|
||||
def shutdown(self):
|
||||
"""Try to shut down all tabs cleanly."""
|
||||
try:
|
||||
self.currentChanged.disconnect()
|
||||
except TypeError:
|
||||
log.destroy.exception("Error while shutting down tabs")
|
||||
self._shutting_down = True
|
||||
for tab in self.widgets():
|
||||
self._remove_tab(tab)
|
||||
|
||||
@@ -417,14 +436,10 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
tab: The tab where the signal belongs to.
|
||||
"""
|
||||
try:
|
||||
idx = self.indexOf(tab)
|
||||
except RuntimeError:
|
||||
idx = self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
if idx == -1:
|
||||
# We can get signals for tabs we already deleted...
|
||||
log.webview.debug("Got invalid tab {}!".format(tab))
|
||||
return
|
||||
self.setTabIcon(idx, QIcon())
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -449,16 +464,12 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
log.webview.debug("Ignoring title change to '{}'.".format(text))
|
||||
return
|
||||
try:
|
||||
idx = self.indexOf(tab)
|
||||
except RuntimeError:
|
||||
idx = self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
log.webview.debug("Changing title for idx {} to '{}'".format(
|
||||
idx, text))
|
||||
if idx == -1:
|
||||
# We can get signals for tabs we already deleted...
|
||||
log.webview.debug("Got invalid tab {}!".format(tab))
|
||||
return
|
||||
self.setTabText(idx, text.replace('&', '&&'))
|
||||
if idx == self.currentIndex():
|
||||
self._change_app_title(text)
|
||||
@@ -472,14 +483,10 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
url: The new URL.
|
||||
"""
|
||||
try:
|
||||
idx = self.indexOf(tab)
|
||||
except RuntimeError:
|
||||
idx = self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
if idx == -1:
|
||||
# We can get signals for tabs we already deleted...
|
||||
log.webview.debug("Got invalid tab {}!".format(tab))
|
||||
return
|
||||
if not self.tabText(idx):
|
||||
self.setTabText(idx, url)
|
||||
|
||||
@@ -495,14 +502,10 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
if not config.get('tabs', 'show-favicons'):
|
||||
return
|
||||
try:
|
||||
idx = self.indexOf(tab)
|
||||
except RuntimeError:
|
||||
idx = self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
if idx == -1:
|
||||
# We can get *_changed signals for tabs we already deleted...
|
||||
log.webview.debug("Got invalid tab {}!".format(tab))
|
||||
return
|
||||
self.setTabIcon(idx, tab.icon())
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
@@ -520,8 +523,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
@pyqtSlot(int)
|
||||
def on_current_changed(self, idx):
|
||||
"""Set last-focused-tab and leave hinting mode when focus changed."""
|
||||
if idx == -1:
|
||||
# closing the last tab (before quitting)
|
||||
if idx == -1 or self._shutting_down:
|
||||
# closing the last tab (before quitting) or shutting down
|
||||
return
|
||||
tab = self.widget(idx)
|
||||
log.modes.debug("Current tab changed, focusing {!r}".format(tab))
|
||||
@@ -545,8 +548,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
def on_load_progress(self, tab, perc):
|
||||
"""Adjust tab indicator on load progress."""
|
||||
try:
|
||||
idx = self.indexOf(tab)
|
||||
except RuntimeError:
|
||||
idx = self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
start = config.get('colors', 'tabs.indicator.start')
|
||||
@@ -563,8 +566,8 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
See https://github.com/The-Compiler/qutebrowser/issues/84
|
||||
"""
|
||||
try:
|
||||
idx = self.indexOf(tab)
|
||||
except RuntimeError:
|
||||
idx = self._tab_index(tab)
|
||||
except TabDeletedError:
|
||||
# We can get signals for tabs we already deleted...
|
||||
return
|
||||
if tab.page().error_occured:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -21,16 +21,14 @@
|
||||
|
||||
import sys
|
||||
import code
|
||||
import rlcompleter
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QStringListModel
|
||||
from PyQt5.QtWidgets import (QTextEdit, QWidget, QVBoxLayout, QApplication,
|
||||
QCompleter)
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
|
||||
from PyQt5.QtWidgets import QTextEdit, QWidget, QVBoxLayout, QApplication
|
||||
from PyQt5.QtGui import QTextCursor
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.misc import cmdhistory, miscwidgets
|
||||
from qutebrowser.utils import utils, log, objreg
|
||||
from qutebrowser.utils import utils, objreg
|
||||
|
||||
|
||||
class ConsoleLineEdit(miscwidgets.CommandLineEdit):
|
||||
@@ -39,8 +37,6 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
|
||||
|
||||
Attributes:
|
||||
_history: The command history of executed commands.
|
||||
_rlcompleter: The rlcompleter.Completer instance.
|
||||
_qcompleter: The QCompleter instance.
|
||||
|
||||
Signals:
|
||||
execute: Emitted when a commandline should be executed.
|
||||
@@ -48,46 +44,18 @@ class ConsoleLineEdit(miscwidgets.CommandLineEdit):
|
||||
|
||||
execute = pyqtSignal(str)
|
||||
|
||||
def __init__(self, namespace, parent):
|
||||
def __init__(self, _namespace, parent):
|
||||
"""Constructor.
|
||||
|
||||
Args:
|
||||
namespace: The local namespace of the interpreter.
|
||||
_namespace: The local namespace of the interpreter.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.update_font()
|
||||
objreg.get('config').changed.connect(self.update_font)
|
||||
self.textChanged.connect(self.on_text_changed)
|
||||
|
||||
self._rlcompleter = rlcompleter.Completer(namespace)
|
||||
qcompleter = QCompleter(self)
|
||||
self._model = QStringListModel(qcompleter)
|
||||
qcompleter.setModel(self._model)
|
||||
qcompleter.setCompletionMode(
|
||||
QCompleter.UnfilteredPopupCompletion)
|
||||
qcompleter.setModelSorting(
|
||||
QCompleter.CaseSensitivelySortedModel)
|
||||
self.setCompleter(qcompleter)
|
||||
|
||||
self._history = cmdhistory.History()
|
||||
self.returnPressed.connect(self.on_return_pressed)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_text_changed(self, text):
|
||||
"""Update completion when text changed."""
|
||||
strings = set()
|
||||
i = 0
|
||||
while True:
|
||||
s = self._rlcompleter.complete(text, i)
|
||||
if s is None:
|
||||
break
|
||||
else:
|
||||
strings.add(s)
|
||||
i += 1
|
||||
strings = sorted(list(strings))
|
||||
self._model.setStringList(strings)
|
||||
log.misc.vdebug('completions: {!r}'.format(strings))
|
||||
|
||||
@pyqtSlot(str)
|
||||
def on_return_pressed(self):
|
||||
"""Execute the line of code which was entered."""
|
||||
|
||||
@@ -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,43 +21,93 @@
|
||||
|
||||
"""The dialog which gets shown when qutebrowser crashes."""
|
||||
|
||||
import re
|
||||
import sys
|
||||
import html
|
||||
import getpass
|
||||
import traceback
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QSize
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QSize, qVersion
|
||||
from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
|
||||
QVBoxLayout, QHBoxLayout, QCheckBox)
|
||||
QVBoxLayout, QHBoxLayout, QCheckBox,
|
||||
QDialogButtonBox, QMessageBox)
|
||||
|
||||
from qutebrowser.utils import version, log, utils, objreg
|
||||
import qutebrowser
|
||||
from qutebrowser.utils import version, log, utils, objreg, qtutils
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.browser.network import pastebin
|
||||
from qutebrowser.config import config
|
||||
|
||||
|
||||
def parse_fatal_stacktrace(text):
|
||||
"""Get useful information from a fatal faulthandler stacktrace.
|
||||
|
||||
Args:
|
||||
text: The text to parse.
|
||||
|
||||
Return:
|
||||
A tuple with the first element being the error type, and the second
|
||||
element being the first stacktrace frame.
|
||||
"""
|
||||
lines = [
|
||||
r'Fatal Python error: (.*)',
|
||||
r' *',
|
||||
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
|
||||
r' File ".*", line \d+ in (.*)',
|
||||
]
|
||||
m = re.match('\n'.join(lines), text)
|
||||
if m is None:
|
||||
# We got some invalid text.
|
||||
return ('', '')
|
||||
else:
|
||||
return (m.group(1), m.group(3))
|
||||
|
||||
|
||||
def get_fatal_crash_dialog(debug, data):
|
||||
"""Get a fatal crash dialog based on a crash log.
|
||||
|
||||
If the crash is a segfault in qt_mainloop and we're on an old Qt version
|
||||
this is a simple error dialog which lets the user know they should upgrade
|
||||
if possible.
|
||||
|
||||
If it's anything else, it's a normal FatalCrashDialog with the possibility
|
||||
to report the crash.
|
||||
|
||||
Args:
|
||||
debug: Whether the debug flag (--debug) was given.
|
||||
data: The crash log data.
|
||||
"""
|
||||
errtype, frame = parse_fatal_stacktrace(data)
|
||||
if (qtutils.version_check('5.4') or errtype != 'Segmentation fault' or
|
||||
frame != 'qt_mainloop'):
|
||||
return FatalCrashDialog(debug, data)
|
||||
else:
|
||||
title = "qutebrowser was restarted after a fatal crash!"
|
||||
text = ("<b>qutebrowser was restarted after a fatal crash!</b><br/>"
|
||||
"Unfortunately, this crash occured in Qt (the library "
|
||||
"qutebrowser uses), and your version ({}) is outdated - "
|
||||
"Qt 5.4 or later is recommended. Unfortuntately Debian and "
|
||||
"Ubuntu don't ship a newer version (yet?)...".format(
|
||||
qVersion()))
|
||||
return QMessageBox(QMessageBox.Critical, title, text, QMessageBox.Ok)
|
||||
|
||||
|
||||
class _CrashDialog(QDialog):
|
||||
|
||||
"""Dialog which gets shown after there was a crash.
|
||||
|
||||
Class attributes:
|
||||
NAME: The kind of condition we report.
|
||||
|
||||
Attributes:
|
||||
These are just here to have a static reference to avoid GCing.
|
||||
_vbox: The main QVBoxLayout
|
||||
_lbl: The QLabel with the static text
|
||||
_debug_log: The QTextEdit with the crash information
|
||||
_hbox: The QHboxLayout containing the buttons
|
||||
_btn_box: The QDialogButtonBox containing the buttons.
|
||||
_url: Pastebin URL QLabel.
|
||||
_crash_info: A list of tuples with title and crash information.
|
||||
_paste_client: A PastebinClient instance to use.
|
||||
_paste_text: The text to pastebin.
|
||||
_resolution: Whether the dialog should be accepted on close.
|
||||
"""
|
||||
|
||||
NAME = None
|
||||
|
||||
def __init__(self, debug, parent=None):
|
||||
"""Constructor for CrashDialog.
|
||||
|
||||
@@ -67,12 +117,11 @@ class _CrashDialog(QDialog):
|
||||
super().__init__(parent)
|
||||
# We don't set WA_DeleteOnClose here as on an exception, we'll get
|
||||
# closed anyways, and it only could have unintended side-effects.
|
||||
self._buttons = []
|
||||
self._crash_info = []
|
||||
self._hbox = None
|
||||
self._btn_box = None
|
||||
self._btn_report = None
|
||||
self._btn_cancel = None
|
||||
self._lbl = None
|
||||
self._chk_report = None
|
||||
self._resolution = None
|
||||
self._paste_text = None
|
||||
self.setWindowTitle("Whoops!")
|
||||
self.resize(QSize(640, 600))
|
||||
@@ -80,6 +129,23 @@ class _CrashDialog(QDialog):
|
||||
self._paste_client = pastebin.PastebinClient(self)
|
||||
self._init_text()
|
||||
|
||||
contact = QLabel("I'd like to be able to follow up with you, to keep "
|
||||
"you posted on the status of this crash and get more "
|
||||
"information if I need it - how can I contact you?",
|
||||
wordWrap=True)
|
||||
self._vbox.addWidget(contact)
|
||||
self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False)
|
||||
try:
|
||||
state = objreg.get('state-config')
|
||||
try:
|
||||
self._contact.setPlainText(state['general']['contact-info'])
|
||||
except KeyError:
|
||||
self._contact.setPlaceholderText("Mail or IRC nickname")
|
||||
except Exception:
|
||||
log.misc.exception("Failed to get contact information!")
|
||||
self._contact.setPlaceholderText("Mail or IRC nickname")
|
||||
self._vbox.addWidget(self._contact, 2)
|
||||
|
||||
info = QLabel("What were you doing when this crash/bug happened?")
|
||||
self._vbox.addWidget(info)
|
||||
self._info = QTextEdit(tabChangesFocus=True, acceptRichText=False)
|
||||
@@ -87,11 +153,6 @@ class _CrashDialog(QDialog):
|
||||
"- Switched tabs\n"
|
||||
"- etc...")
|
||||
self._vbox.addWidget(self._info, 5)
|
||||
contact = QLabel("How can I contact you if I need more info?")
|
||||
self._vbox.addWidget(contact)
|
||||
self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False)
|
||||
self._contact.setPlaceholderText("Github username, mail or IRC")
|
||||
self._vbox.addWidget(self._contact, 2)
|
||||
|
||||
self._vbox.addSpacing(15)
|
||||
self._debug_log = QTextEdit(tabChangesFocus=True, acceptRichText=False,
|
||||
@@ -110,7 +171,8 @@ class _CrashDialog(QDialog):
|
||||
self._vbox.addWidget(self._debug_log, 10)
|
||||
self._vbox.addSpacing(15)
|
||||
|
||||
self._init_checkboxes(debug)
|
||||
self._init_checkboxes()
|
||||
self._init_info_text()
|
||||
self._init_buttons()
|
||||
|
||||
def __repr__(self):
|
||||
@@ -124,28 +186,29 @@ class _CrashDialog(QDialog):
|
||||
textInteractionFlags=Qt.LinksAccessibleByMouse)
|
||||
self._vbox.addWidget(self._lbl)
|
||||
|
||||
def _init_checkboxes(self, debug):
|
||||
"""Initialize the checkboxes.
|
||||
|
||||
Args:
|
||||
debug: Whether a --debug arg was given.
|
||||
"""
|
||||
self._chk_report = QCheckBox("Send a report")
|
||||
if not debug:
|
||||
self._chk_report.setChecked(True)
|
||||
self._vbox.addWidget(self._chk_report)
|
||||
info_label = QLabel("<i>Note that without your help, I can't fix the "
|
||||
"bug you encountered.</i>", wordWrap=True)
|
||||
self._vbox.addWidget(info_label)
|
||||
def _init_checkboxes(self):
|
||||
"""Initialize the checkboxes."""
|
||||
pass
|
||||
|
||||
def _init_buttons(self):
|
||||
"""Initialize the buttons.
|
||||
"""Initialize the buttons."""
|
||||
self._btn_box = QDialogButtonBox()
|
||||
self._vbox.addWidget(self._btn_box)
|
||||
|
||||
Should be extended by subclasses to provide the actual buttons.
|
||||
"""
|
||||
self._hbox = QHBoxLayout()
|
||||
self._vbox.addLayout(self._hbox)
|
||||
self._hbox.addStretch()
|
||||
self._btn_report = QPushButton("Report", default=True)
|
||||
self._btn_report.clicked.connect(self.on_report_clicked)
|
||||
self._btn_box.addButton(self._btn_report, QDialogButtonBox.AcceptRole)
|
||||
|
||||
self._btn_cancel = QPushButton("Don't report", autoDefault=False)
|
||||
self._btn_cancel.clicked.connect(self.finish)
|
||||
self._btn_box.addButton(self._btn_cancel, QDialogButtonBox.RejectRole)
|
||||
|
||||
def _init_info_text(self):
|
||||
"""Add an info text encouraging the user to report crashes."""
|
||||
info_label = QLabel("<br/><b>Note that without your help, I can't fix "
|
||||
"the bug you encountered.<br/>I read and respond "
|
||||
"to all crash reports!</b>", wordWrap=True)
|
||||
self._vbox.addWidget(info_label)
|
||||
|
||||
def _gather_crash_info(self):
|
||||
"""Gather crash information to display.
|
||||
@@ -177,6 +240,31 @@ class _CrashDialog(QDialog):
|
||||
text = '\n\n'.join(chunks)
|
||||
self._debug_log.setText(text)
|
||||
|
||||
def _get_error_type(self):
|
||||
"""Get the type of the error we're reporting."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_paste_title_desc(self):
|
||||
"""Get a short description of the paste."""
|
||||
return ''
|
||||
|
||||
def _get_paste_title(self):
|
||||
"""Get a title for the paste."""
|
||||
desc = self._get_paste_title_desc()
|
||||
title = "qute {} {}".format(
|
||||
qutebrowser.__version__, self._get_error_type())
|
||||
if desc:
|
||||
title += ' {}'.format(desc)
|
||||
return title
|
||||
|
||||
def _save_contact_info(self):
|
||||
"""Save the contact info to disk."""
|
||||
try:
|
||||
state = objreg.get('state-config')
|
||||
state['general']['contact-info'] = self._contact.toPlainText()
|
||||
except Exception:
|
||||
log.misc.exception("Failed to save contact information!")
|
||||
|
||||
def report(self):
|
||||
"""Paste the crash info into the pastebin."""
|
||||
lines = []
|
||||
@@ -194,7 +282,7 @@ class _CrashDialog(QDialog):
|
||||
user = 'unknown'
|
||||
try:
|
||||
# parent: http://p.cmpl.cc/90286958
|
||||
self._paste_client.paste(user, "qutebrowser {}".format(self.NAME),
|
||||
self._paste_client.paste(user, self._get_paste_title(),
|
||||
self._paste_text, parent='90286958')
|
||||
except Exception as e:
|
||||
log.misc.exception("Error while paste-binning")
|
||||
@@ -202,17 +290,14 @@ class _CrashDialog(QDialog):
|
||||
self.show_error(exc_text)
|
||||
|
||||
@pyqtSlot()
|
||||
def on_button_clicked(self, button, accept):
|
||||
"""Report and close dialog if button was clicked."""
|
||||
button.setText("Reporting...")
|
||||
for btn in self._buttons:
|
||||
btn.setEnabled(False)
|
||||
self._resolution = accept
|
||||
def on_report_clicked(self):
|
||||
"""Report and close dialog if report button was clicked."""
|
||||
self._btn_report.setEnabled(False)
|
||||
self._btn_cancel.setEnabled(False)
|
||||
self._btn_report.setText("Reporting...")
|
||||
self._paste_client.success.connect(self.finish)
|
||||
self._paste_client.error.connect(self.show_error)
|
||||
reported = self.maybe_report()
|
||||
if not reported:
|
||||
self.finish()
|
||||
self.report()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def show_error(self, text):
|
||||
@@ -227,24 +312,9 @@ class _CrashDialog(QDialog):
|
||||
|
||||
@pyqtSlot()
|
||||
def finish(self):
|
||||
"""Accept/reject the dialog when reporting is done."""
|
||||
if self._resolution:
|
||||
self.accept()
|
||||
else:
|
||||
self.reject()
|
||||
|
||||
@pyqtSlot()
|
||||
def maybe_report(self):
|
||||
"""Report the bug if the user allowed us to.
|
||||
|
||||
Return:
|
||||
True if a report was done, False otherwise.
|
||||
"""
|
||||
if self._chk_report.isChecked():
|
||||
self.report()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
"""Save contact info and close the dialog."""
|
||||
self._save_contact_info()
|
||||
self.accept()
|
||||
|
||||
|
||||
class ExceptionCrashDialog(_CrashDialog):
|
||||
@@ -252,17 +322,15 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
"""Dialog which gets shown on an exception.
|
||||
|
||||
Attributes:
|
||||
_buttons: A list of buttons.
|
||||
_pages: A list of lists of the open pages (URLs as strings)
|
||||
_cmdhist: A list with the command history (as strings)
|
||||
_exc: An exception tuple (type, value, traceback)
|
||||
_objects: A list of all QObjects as string.
|
||||
"""
|
||||
|
||||
NAME = 'exception'
|
||||
|
||||
def __init__(self, debug, pages, cmdhist, exc, objects, parent=None):
|
||||
self._chk_log = None
|
||||
self._chk_restore = None
|
||||
super().__init__(debug, parent)
|
||||
self._pages = pages
|
||||
self._cmdhist = cmdhist
|
||||
@@ -278,26 +346,20 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
|
||||
def _init_buttons(self):
|
||||
super()._init_buttons()
|
||||
btn_quit = QPushButton("Quit")
|
||||
btn_quit.clicked.connect(
|
||||
functools.partial(self.on_button_clicked, btn_quit, False))
|
||||
self._hbox.addWidget(btn_quit)
|
||||
|
||||
btn_restart = QPushButton("Restart", default=True)
|
||||
btn_restart.clicked.connect(
|
||||
functools.partial(self.on_button_clicked, btn_restart, True))
|
||||
self._hbox.addWidget(btn_restart)
|
||||
|
||||
self._buttons = [btn_quit, btn_restart]
|
||||
|
||||
def _init_checkboxes(self, debug):
|
||||
"""Add checkboxes to send crash report."""
|
||||
super()._init_checkboxes(debug)
|
||||
self._chk_log = QCheckBox("Include a debug log and a list of open "
|
||||
"pages", checked=True)
|
||||
if debug:
|
||||
self._chk_log.setChecked(False)
|
||||
self._chk_log.setEnabled(False)
|
||||
def _init_checkboxes(self):
|
||||
"""Add checkboxes to the dialog."""
|
||||
super()._init_checkboxes()
|
||||
self._chk_restore = QCheckBox("Restore open pages")
|
||||
self._chk_restore.setChecked(True)
|
||||
self._vbox.addWidget(self._chk_restore)
|
||||
self._chk_log = QCheckBox("Include a debug log in the report",
|
||||
checked=True)
|
||||
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 "
|
||||
@@ -305,12 +367,19 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
"information such as which pages you visited "
|
||||
"or keyboard input.</i>", wordWrap=True)
|
||||
self._vbox.addWidget(info_label)
|
||||
self._chk_report.toggled.connect(self.on_chk_report_toggled)
|
||||
|
||||
def _get_error_type(self):
|
||||
return 'exc'
|
||||
|
||||
def _get_paste_title_desc(self):
|
||||
desc = traceback.format_exception_only(self._exc[0], self._exc[1])
|
||||
return desc[0].rstrip()
|
||||
|
||||
def _gather_crash_info(self):
|
||||
self._crash_info += [
|
||||
("Exception", ''.join(traceback.format_exception(*self._exc))),
|
||||
]
|
||||
super()._gather_crash_info()
|
||||
if self._chk_log.isChecked():
|
||||
self._crash_info += [
|
||||
("Commandline args", ' '.join(sys.argv[1:])),
|
||||
@@ -318,8 +387,6 @@ 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()))
|
||||
@@ -328,11 +395,12 @@ class ExceptionCrashDialog(_CrashDialog):
|
||||
("Debug log", traceback.format_exc()))
|
||||
|
||||
@pyqtSlot()
|
||||
def on_chk_report_toggled(self):
|
||||
"""Disable log checkbox if report is disabled."""
|
||||
is_checked = self._chk_report.isChecked()
|
||||
self._chk_log.setEnabled(is_checked)
|
||||
self._chk_log.setChecked(is_checked)
|
||||
def finish(self):
|
||||
self._save_contact_info()
|
||||
if self._chk_restore.isChecked():
|
||||
self.accept()
|
||||
else:
|
||||
self.reject()
|
||||
|
||||
|
||||
class FatalCrashDialog(_CrashDialog):
|
||||
@@ -341,15 +409,25 @@ class FatalCrashDialog(_CrashDialog):
|
||||
|
||||
Attributes:
|
||||
_log: The log text to display.
|
||||
_type: The type of error which occured.
|
||||
_func: The function (top of the stack) in which the error occured.
|
||||
"""
|
||||
|
||||
NAME = 'segfault'
|
||||
|
||||
def __init__(self, debug, text, parent=None):
|
||||
super().__init__(debug, parent)
|
||||
self._log = text
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self._set_crash_info()
|
||||
self._type, self._func = parse_fatal_stacktrace(self._log)
|
||||
|
||||
def _get_error_type(self):
|
||||
if self._type == 'Segmentation fault':
|
||||
return 'segv'
|
||||
else:
|
||||
return self._type
|
||||
|
||||
def _get_paste_title_desc(self):
|
||||
return self._func
|
||||
|
||||
def _init_text(self):
|
||||
super()._init_text()
|
||||
@@ -361,18 +439,8 @@ class FatalCrashDialog(_CrashDialog):
|
||||
"stacktrace.asciidoc</a> to submit a stacktrace.<br/>")
|
||||
self._lbl.setText(text)
|
||||
|
||||
def _init_buttons(self):
|
||||
super()._init_buttons()
|
||||
btn_ok = QPushButton(text="OK", default=True)
|
||||
btn_ok.clicked.connect(
|
||||
functools.partial(self.on_button_clicked, btn_ok, True))
|
||||
self._hbox.addWidget(btn_ok)
|
||||
self._buttons = [btn_ok]
|
||||
|
||||
def _gather_crash_info(self):
|
||||
self._crash_info += [
|
||||
("Fault log", self._log),
|
||||
]
|
||||
self._crash_info.append(("Fault log", self._log))
|
||||
super()._gather_crash_info()
|
||||
|
||||
|
||||
@@ -386,12 +454,9 @@ class ReportDialog(_CrashDialog):
|
||||
_objects: A list of all QObjects as string.
|
||||
"""
|
||||
|
||||
NAME = 'report'
|
||||
|
||||
def __init__(self, pages, cmdhist, objects, parent=None):
|
||||
super().__init__(False, parent)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self._btn_report = None
|
||||
self._pages = pages
|
||||
self._cmdhist = cmdhist
|
||||
self._objects = objects
|
||||
@@ -402,18 +467,13 @@ class ReportDialog(_CrashDialog):
|
||||
text = "Please describe the bug you encountered below."
|
||||
self._lbl.setText(text)
|
||||
|
||||
def _init_buttons(self):
|
||||
super()._init_buttons()
|
||||
self._btn_report = QPushButton("Report", default=True)
|
||||
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."""
|
||||
def _init_info_text(self):
|
||||
"""We don't want an info text as the user wanted to report."""
|
||||
pass
|
||||
|
||||
def _get_error_type(self):
|
||||
return 'report'
|
||||
|
||||
def _gather_crash_info(self):
|
||||
super()._gather_crash_info()
|
||||
self._crash_info += [
|
||||
@@ -427,16 +487,6 @@ class ReportDialog(_CrashDialog):
|
||||
except Exception:
|
||||
self._crash_info.append(("Debug log", traceback.format_exc()))
|
||||
|
||||
@pyqtSlot()
|
||||
def maybe_report(self):
|
||||
"""Report the crash.
|
||||
|
||||
We don't have a "Send a report" checkbox here because it was a manual
|
||||
report, which would be pretty useless without this info.
|
||||
"""
|
||||
self.report()
|
||||
return True
|
||||
|
||||
|
||||
class ReportErrorDialog(QDialog):
|
||||
|
||||
|
||||
@@ -234,14 +234,14 @@ def check_libraries():
|
||||
'jinja2':
|
||||
_missing_str("jinja2",
|
||||
debian="apt-get install python3-jinja2",
|
||||
arch="Install python-jinja from the AUR",
|
||||
arch="Install python-jinja from community",
|
||||
windows="Install from http://www.lfd.uci.edu/"
|
||||
"~gohlke/pythonlibs/#jinja2 or via pip.",
|
||||
pip="jinja2"),
|
||||
'pygments':
|
||||
_missing_str("pygments",
|
||||
debian="apt-get install python3-pygments",
|
||||
arch="Install python-jinja from the AUR",
|
||||
arch="Install python-pygments from community",
|
||||
windows="Install from http://www.lfd.uci.edu/"
|
||||
"~gohlke/pythonlibs/#pygments or via pip.",
|
||||
pip="pygments"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -43,7 +43,7 @@ class MinimalLineEditMixin:
|
||||
self.setAttribute(Qt.WA_MacShowFocusRect, False)
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, text=self.text())
|
||||
return utils.get_repr(self)
|
||||
|
||||
|
||||
class CommandLineEdit(QLineEdit):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
@@ -58,7 +58,7 @@ def get_argparser():
|
||||
debug.add_argument('--loglines',
|
||||
help="How many lines of the debug log to keep in RAM "
|
||||
"(-1: unlimited).",
|
||||
default=1000, type=int)
|
||||
default=2000, type=int)
|
||||
debug.add_argument('--debug', help="Turn on debugging options.",
|
||||
action='store_true')
|
||||
debug.add_argument('--nocolor', help="Turn off colored logging.",
|
||||
@@ -110,7 +110,11 @@ def main():
|
||||
app = app.Application(args)
|
||||
|
||||
def qt_mainloop():
|
||||
"""Simple wrapper to get a nicer stack trace for segfaults."""
|
||||
"""Simple wrapper to get a nicer stack trace for segfaults.
|
||||
|
||||
WARNING: misc/crashdialog.py checks the stacktrace for this function
|
||||
name, so if this is changed, it should be changed there as well!
|
||||
"""
|
||||
return app.exec_() # pylint: disable=maybe-no-member
|
||||
|
||||
# We set qApp explicitely here to reduce the risk of segfaults while
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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.
|
||||
#
|
||||
@@ -1095,6 +1095,9 @@ class FontTests(unittest.TestCase):
|
||||
# (style, weight, pointsize, pixelsize, family
|
||||
'"Foobar Neue"':
|
||||
FontDesc(QFont.StyleNormal, QFont.Normal, -1, -1, 'Foobar Neue'),
|
||||
'inconsolatazi4':
|
||||
FontDesc(QFont.StyleNormal, QFont.Normal, -1, -1,
|
||||
'inconsolatazi4'),
|
||||
'10pt "Foobar Neue"':
|
||||
FontDesc(QFont.StyleNormal, QFont.Normal, 10, None, 'Foobar Neue'),
|
||||
'10PT "Foobar Neue"':
|
||||
@@ -1199,6 +1202,59 @@ class FontTests(unittest.TestCase):
|
||||
self.assertIsNone(self.t2.transform(''))
|
||||
|
||||
|
||||
class FontFamilyTests(unittest.TestCase):
|
||||
|
||||
"""Test FontFamily."""
|
||||
|
||||
TESTS = ['"Foobar Neue"', 'inconsolatazi4', 'Foobar']
|
||||
INVALID = [
|
||||
'10pt "Foobar Neue"',
|
||||
'10PT "Foobar Neue"',
|
||||
'10px "Foobar Neue"',
|
||||
'10PX "Foobar Neue"',
|
||||
'bold "Foobar Neue"',
|
||||
'italic "Foobar Neue"',
|
||||
'oblique "Foobar Neue"',
|
||||
'normal bold "Foobar Neue"',
|
||||
'bold italic "Foobar Neue"',
|
||||
'bold 10pt "Foobar Neue"',
|
||||
'italic 10pt "Foobar Neue"',
|
||||
'oblique 10pt "Foobar Neue"',
|
||||
'normal bold 10pt "Foobar Neue"',
|
||||
'bold italic 10pt "Foobar Neue"',
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
self.t = configtypes.FontFamily()
|
||||
|
||||
def test_validate_empty(self):
|
||||
"""Test validate with an empty string."""
|
||||
with self.assertRaises(configexc.ValidationError):
|
||||
self.t.validate('')
|
||||
|
||||
def test_validate_empty_none_ok(self):
|
||||
"""Test validate with an empty string and none_ok=True."""
|
||||
t = configtypes.FontFamily(none_ok=True)
|
||||
t.validate('')
|
||||
|
||||
def test_validate_valid(self):
|
||||
"""Test validate with valid values."""
|
||||
for val in self.TESTS:
|
||||
with self.subTest(val=val):
|
||||
self.t.validate(val)
|
||||
|
||||
def test_validate_invalid(self):
|
||||
"""Test validate with invalid values."""
|
||||
for val in self.INVALID:
|
||||
with self.subTest(val=val):
|
||||
with self.assertRaises(configexc.ValidationError, msg=val):
|
||||
self.t.validate(val)
|
||||
|
||||
def test_transform_empty(self):
|
||||
"""Test transform with an empty value."""
|
||||
self.assertIsNone(self.t.transform(''))
|
||||
|
||||
|
||||
class RegexTests(unittest.TestCase):
|
||||
|
||||
"""Test Regex."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>:
|
||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>:
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user