Handle exceptions with a crash dialog.

This commit is contained in:
Florian Bruhin 2014-01-30 20:42:47 +01:00
parent a71684ea0f
commit 2c276b98a4
2 changed files with 93 additions and 7 deletions

View File

@ -1,7 +1,9 @@
""" Initialization of qutebrowser and application-wide things """ """ Initialization of qutebrowser and application-wide things """
import os
import sys import sys
import logging import logging
import traceback
import faulthandler import faulthandler
from signal import signal, SIGINT from signal import signal, SIGINT
from argparse import ArgumentParser from argparse import ArgumentParser
@ -12,12 +14,13 @@ from argparse import ArgumentParser
import qutebrowser.utils.harfbuzz as harfbuzz import qutebrowser.utils.harfbuzz as harfbuzz
harfbuzz.fix() harfbuzz.fix()
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5.QtCore import QUrl, QTimer from PyQt5.QtCore import QUrl, QTimer
import qutebrowser.commands.utils as cmdutils import qutebrowser.commands.utils as cmdutils
import qutebrowser.utils.config as config import qutebrowser.utils.config as config
from qutebrowser.widgets.mainwindow import MainWindow from qutebrowser.widgets.mainwindow import MainWindow
from qutebrowser.widgets import CrashDialog
from qutebrowser.commands.keys import KeyParser from qutebrowser.commands.keys import KeyParser
from qutebrowser.utils.appdirs import AppDirs from qutebrowser.utils.appdirs import AppDirs
@ -107,32 +110,57 @@ class QuteBrowser(QApplication):
fallback='http://ddg.gg/').split(','): fallback='http://ddg.gg/').split(','):
self.mainwindow.tabs.tabopen(url) self.mainwindow.tabs.tabopen(url)
def _tmp_exception_hook(self, exctype, value, traceback): def _tmp_exception_hook(self, exctype, excvalue, tb):
"""Handle exceptions while initializing by simply exiting. """Handle exceptions while initializing by simply exiting.
This is only temporary and will get replaced by exception_hook later. This is only temporary and will get replaced by exception_hook later.
It's necessary because PyQt seems to ignore exceptions by default. It's necessary because PyQt seems to ignore exceptions by default.
""" """
sys.__excepthook__(exctype, value, traceback) sys.__excepthook__(exctype, excvalue, tb)
self.exit(1) self.exit(1)
def _exception_hook(self, exctype, value, traceback): def _exception_hook(self, exctype, excvalue, tb):
"""Handle uncaught python exceptions. """Handle uncaught python exceptions.
It'll try very hard to write all open tabs to a file, and then exit It'll try very hard to write all open tabs to a file, and then exit
gracefully. gracefully.
""" """
# pylint: disable=broad-except # pylint: disable=broad-except
sys.__excepthook__(exctype, value, traceback)
exc = (exctype, excvalue, tb)
traceback.print_exception(*exc)
pages = []
try: try:
for tabidx in range(self.mainwindow.tabs.count()): for tabidx in range(self.mainwindow.tabs.count()):
try: try:
# FIXME write to some file url = self.mainwindow.tabs.widget(tabidx).url().toString()
print(self.mainwindow.tabs.widget(tabidx).url().url()) url = url.strip()
if url:
pages.append(url)
except Exception: except Exception:
pass pass
except Exception: except Exception:
pass pass
try:
history = self.mainwindow.status.cmd.history[-5:]
except Exception:
history = []
dlg = CrashDialog(pages, history, exc)
ret = dlg.exec_()
if ret == QDialog.Accepted: # restore
os.environ['PYTHONPATH'] = os.pathsep.join(sys.path)
# FIXME we might want to use argparse's features to not open pages
# again if they were opened via cmdline
argv = [sys.executable] + sys.argv + pages
logging.debug('Running {} with args {}'.format(sys.executable,
argv))
sys.stdout.flush()
# FIXME this seems broken on Windows, execv() splits on whitespace
# in arguments?!?
os.execv(sys.executable, argv)
self.exit(1) self.exit(1)
def _python_hacks(self): def _python_hacks(self):

View File

@ -1 +1,59 @@
"""The Qt widgets needed by qutebrowser.""" """The Qt widgets needed by qutebrowser."""
import sys
import traceback
from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QVBoxLayout,
QHBoxLayout, QPushButton)
import qutebrowser.utils as utils
class CrashDialog(QDialog):
"""Dialog which gets shown after there was a crash."""
def __init__(self, pages, cmdhist, exc):
super().__init__()
self.setFixedSize(500, 350)
self.setWindowTitle('Whoops!')
vbox = QVBoxLayout()
lbl = QLabel(self)
#lbl.setGeometry(5, 5, 395, 295)
lbl.setText(
'Argh! qutebrowser crashed unexpectedly.<br/>'
'Please review the info below to remove sensitive data and then '
'submit it to '
'<a href="mailto:me@the-compiler.org">me@the-compiler.org</a>.'
'<br/><br/>You can click "Restore tabs" to attempt to reopen your '
'open pages.'
)
vbox.addWidget(lbl)
txt = QTextEdit(self)
txt.setReadOnly(True)
#txt.setGeometry(5, 400, 395, 295)
txt.setText(
'==== Version info ====\n{}\n\n'.format(utils.version()) +
'==== Exception ====\n{}\n'.format(
''.join(traceback.format_exception(*exc))) +
'==== Open pages ====\n{}\n\n'.format('\n'.join(pages)) +
'==== Command history ====\n{}\n\n'.format('\n'.join(cmdhist)) +
'==== Commandline args ====\n{}'.format(' '.join(sys.argv[1:]))
)
vbox.addWidget(txt)
self.setLayout(vbox)
hbox = QHBoxLayout()
btn_quit = QPushButton(self)
btn_quit.setText('Quit')
btn_quit.clicked.connect(self.reject)
hbox.addWidget(btn_quit)
btn_restore = QPushButton(self)
btn_restore.setText('Restore tabs')
btn_restore.clicked.connect(self.accept)
btn_restore.setDefault(True)
hbox.addWidget(btn_restore)
vbox.addLayout(hbox)
self.show()