2014-06-19 09:04:37 +02:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2016-01-04 07:12:39 +01:00
|
|
|
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
2014-02-10 15:01:05 +01:00
|
|
|
#
|
|
|
|
# This file is part of qutebrowser.
|
|
|
|
#
|
|
|
|
# qutebrowser is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# qutebrowser is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""The dialog which gets shown when qutebrowser crashes."""
|
|
|
|
|
2015-01-24 14:21:07 +01:00
|
|
|
import re
|
2015-10-01 13:11:35 +02:00
|
|
|
import os
|
2014-02-10 15:01:05 +01:00
|
|
|
import sys
|
2014-10-26 19:14:46 +01:00
|
|
|
import html
|
2014-10-31 07:57:50 +01:00
|
|
|
import getpass
|
2015-10-01 14:57:11 +02:00
|
|
|
import fnmatch
|
2014-02-10 15:01:05 +01:00
|
|
|
import traceback
|
2016-02-12 11:59:45 +01:00
|
|
|
import datetime
|
2014-02-10 15:01:05 +01:00
|
|
|
|
2015-06-18 18:45:51 +02:00
|
|
|
import pkg_resources
|
2015-01-25 21:12:50 +01:00
|
|
|
from PyQt5.QtCore import pyqtSlot, Qt, QSize, qVersion
|
2014-02-17 12:23:52 +01:00
|
|
|
from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
|
2015-01-24 20:13:18 +01:00
|
|
|
QVBoxLayout, QHBoxLayout, QCheckBox,
|
2015-08-07 20:36:38 +02:00
|
|
|
QDialogButtonBox, QMessageBox, QApplication)
|
2014-02-10 15:01:05 +01:00
|
|
|
|
2015-01-24 14:21:07 +01:00
|
|
|
import qutebrowser
|
2015-01-25 21:35:13 +01:00
|
|
|
from qutebrowser.utils import version, log, utils, objreg, qtutils
|
2015-04-10 07:52:06 +02:00
|
|
|
from qutebrowser.misc import miscwidgets, autoupdate, msgbox
|
2014-12-13 17:28:50 +01:00
|
|
|
from qutebrowser.browser.network import pastebin
|
2015-01-04 20:41:35 +01:00
|
|
|
from qutebrowser.config import config
|
2014-02-10 15:01:05 +01:00
|
|
|
|
|
|
|
|
2015-01-24 14:21:07 +01:00
|
|
|
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' *',
|
2015-02-19 21:38:02 +01:00
|
|
|
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
|
2015-01-24 14:21:07 +01:00
|
|
|
r' File ".*", line \d+ in (.*)',
|
|
|
|
]
|
|
|
|
m = re.match('\n'.join(lines), text)
|
|
|
|
if m is None:
|
|
|
|
# We got some invalid text.
|
|
|
|
return ('', '')
|
|
|
|
else:
|
2015-02-19 22:45:37 +01:00
|
|
|
return (m.group(1), m.group(3))
|
2015-01-24 14:21:07 +01:00
|
|
|
|
|
|
|
|
2015-01-25 21:35:13 +01:00
|
|
|
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/>"
|
2015-03-31 20:49:29 +02:00
|
|
|
"Unfortunately, this crash occurred in Qt (the library "
|
2015-01-25 21:35:13 +01:00
|
|
|
"qutebrowser uses), and your version ({}) is outdated - "
|
2015-10-04 15:41:42 +02:00
|
|
|
"Qt 5.4 or later is recommended. Unfortunately Debian and "
|
2015-01-25 21:35:13 +01:00
|
|
|
"Ubuntu don't ship a newer version (yet?)...".format(
|
|
|
|
qVersion()))
|
|
|
|
return QMessageBox(QMessageBox.Critical, title, text, QMessageBox.Ok)
|
|
|
|
|
|
|
|
|
2015-10-01 14:57:11 +02:00
|
|
|
def _get_environment_vars():
|
2015-10-04 15:41:42 +02:00
|
|
|
"""Gather environment variables for the crash info."""
|
2015-10-01 14:57:11 +02:00
|
|
|
masks = ('DESKTOP_SESSION', 'DE', 'QT_*', 'PYTHON*', 'LC_*', 'LANG')
|
|
|
|
info = []
|
|
|
|
for key, value in os.environ.items():
|
|
|
|
for m in masks:
|
|
|
|
if fnmatch.fnmatch(key, m):
|
2016-01-22 19:40:10 +01:00
|
|
|
info.append('{} = {}'.format(key, value))
|
2015-10-01 14:57:11 +02:00
|
|
|
return '\n'.join(sorted(info))
|
|
|
|
|
|
|
|
|
2014-05-15 11:56:22 +02:00
|
|
|
class _CrashDialog(QDialog):
|
2014-02-10 15:01:05 +01:00
|
|
|
|
2014-02-18 16:38:13 +01:00
|
|
|
"""Dialog which gets shown after there was a crash.
|
2014-02-10 15:01:05 +01:00
|
|
|
|
2014-02-18 16:38:13 +01:00
|
|
|
Attributes:
|
|
|
|
These are just here to have a static reference to avoid GCing.
|
|
|
|
_vbox: The main QVBoxLayout
|
|
|
|
_lbl: The QLabel with the static text
|
2014-10-26 19:14:46 +01:00
|
|
|
_debug_log: The QTextEdit with the crash information
|
2015-01-24 20:13:18 +01:00
|
|
|
_btn_box: The QDialogButtonBox containing the buttons.
|
2014-05-06 15:36:15 +02:00
|
|
|
_url: Pastebin URL QLabel.
|
2014-05-15 11:56:22 +02:00
|
|
|
_crash_info: A list of tuples with title and crash information.
|
2014-12-11 23:34:03 +01:00
|
|
|
_paste_client: A PastebinClient instance to use.
|
2015-04-10 07:52:06 +02:00
|
|
|
_pypi_client: A PyPIVersionClient instance to use.
|
2014-12-11 23:34:03 +01:00
|
|
|
_paste_text: The text to pastebin.
|
2014-02-18 16:38:13 +01:00
|
|
|
"""
|
2014-02-17 07:50:19 +01:00
|
|
|
|
2014-10-31 07:05:04 +01:00
|
|
|
def __init__(self, debug, parent=None):
|
|
|
|
"""Constructor for CrashDialog.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
debug: Whether --debug was given.
|
|
|
|
"""
|
2014-06-17 07:17:21 +02:00
|
|
|
super().__init__(parent)
|
2014-06-27 07:39:01 +02:00
|
|
|
# We don't set WA_DeleteOnClose here as on an exception, we'll get
|
|
|
|
# closed anyways, and it only could have unintended side-effects.
|
2014-10-26 19:14:46 +01:00
|
|
|
self._crash_info = []
|
2015-01-24 20:13:18 +01:00
|
|
|
self._btn_box = None
|
|
|
|
self._btn_report = None
|
|
|
|
self._btn_cancel = None
|
2014-05-15 11:56:22 +02:00
|
|
|
self._lbl = None
|
2014-12-11 23:34:03 +01:00
|
|
|
self._paste_text = None
|
2014-04-25 16:53:23 +02:00
|
|
|
self.setWindowTitle("Whoops!")
|
2014-10-26 19:14:46 +01:00
|
|
|
self.resize(QSize(640, 600))
|
2014-02-18 16:38:13 +01:00
|
|
|
self._vbox = QVBoxLayout(self)
|
2014-12-11 23:34:03 +01:00
|
|
|
self._paste_client = pastebin.PastebinClient(self)
|
2015-04-10 07:52:06 +02:00
|
|
|
self._pypi_client = autoupdate.PyPIVersionClient(self)
|
2014-05-15 11:56:22 +02:00
|
|
|
self._init_text()
|
2014-10-26 19:14:46 +01:00
|
|
|
|
2015-01-22 06:56:41 +01:00
|
|
|
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 "
|
2015-01-24 20:13:18 +01:00
|
|
|
"information if I need it - how can I contact you?",
|
|
|
|
wordWrap=True)
|
2014-10-26 19:14:46 +01:00
|
|
|
self._vbox.addWidget(contact)
|
2014-10-31 07:13:17 +01:00
|
|
|
self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False)
|
2015-01-16 07:36:38 +01:00
|
|
|
try:
|
|
|
|
state = objreg.get('state-config')
|
|
|
|
try:
|
|
|
|
self._contact.setPlainText(state['general']['contact-info'])
|
|
|
|
except KeyError:
|
2015-01-22 06:54:50 +01:00
|
|
|
self._contact.setPlaceholderText("Mail or IRC nickname")
|
2015-01-16 07:36:38 +01:00
|
|
|
except Exception:
|
|
|
|
log.misc.exception("Failed to get contact information!")
|
2015-01-22 06:54:50 +01:00
|
|
|
self._contact.setPlaceholderText("Mail or IRC nickname")
|
2014-10-26 19:14:46 +01:00
|
|
|
self._vbox.addWidget(self._contact, 2)
|
|
|
|
|
|
|
|
info = QLabel("What were you doing when this crash/bug happened?")
|
|
|
|
self._vbox.addWidget(info)
|
2014-10-31 07:13:17 +01:00
|
|
|
self._info = QTextEdit(tabChangesFocus=True, acceptRichText=False)
|
2014-10-26 19:14:46 +01:00
|
|
|
self._info.setPlaceholderText("- Opened http://www.example.com/\n"
|
|
|
|
"- Switched tabs\n"
|
|
|
|
"- etc...")
|
|
|
|
self._vbox.addWidget(self._info, 5)
|
|
|
|
|
|
|
|
self._vbox.addSpacing(15)
|
2014-10-31 07:13:17 +01:00
|
|
|
self._debug_log = QTextEdit(tabChangesFocus=True, acceptRichText=False,
|
|
|
|
lineWrapMode=QTextEdit.NoWrap)
|
2014-10-31 07:05:04 +01:00
|
|
|
self._debug_log.hide()
|
2014-10-26 19:14:46 +01:00
|
|
|
info = QLabel("<i>You can edit the log below to remove sensitive "
|
|
|
|
"information.</i>", wordWrap=True)
|
|
|
|
info.hide()
|
2014-12-13 17:28:50 +01:00
|
|
|
self._fold = miscwidgets.DetailFold("Show log", self)
|
2014-10-26 19:14:46 +01:00
|
|
|
self._fold.toggled.connect(self._debug_log.setVisible)
|
|
|
|
self._fold.toggled.connect(info.setVisible)
|
2014-10-31 07:05:04 +01:00
|
|
|
if debug:
|
|
|
|
self._fold.toggle()
|
2014-10-26 19:14:46 +01:00
|
|
|
self._vbox.addWidget(self._fold)
|
|
|
|
self._vbox.addWidget(info)
|
|
|
|
self._vbox.addWidget(self._debug_log, 10)
|
|
|
|
self._vbox.addSpacing(15)
|
|
|
|
|
2015-01-24 20:13:18 +01:00
|
|
|
self._init_checkboxes()
|
|
|
|
self._init_info_text()
|
2014-05-15 11:56:22 +02:00
|
|
|
self._init_buttons()
|
2014-05-06 15:36:15 +02:00
|
|
|
|
2014-06-25 23:03:33 +02:00
|
|
|
def __repr__(self):
|
2014-09-26 15:48:24 +02:00
|
|
|
return utils.get_repr(self)
|
2014-06-25 23:03:33 +02:00
|
|
|
|
2014-05-15 11:56:22 +02:00
|
|
|
def _init_text(self):
|
|
|
|
"""Initialize the main text to be displayed on an exception.
|
|
|
|
|
2015-06-01 22:45:40 +02:00
|
|
|
Should be extended by subclasses to set the actual text."""
|
2014-11-02 19:06:43 +01:00
|
|
|
self._lbl = QLabel(wordWrap=True, openExternalLinks=True,
|
|
|
|
textInteractionFlags=Qt.LinksAccessibleByMouse)
|
2014-05-15 11:56:22 +02:00
|
|
|
self._vbox.addWidget(self._lbl)
|
|
|
|
|
2015-01-24 20:13:18 +01:00
|
|
|
def _init_checkboxes(self):
|
|
|
|
"""Initialize the checkboxes."""
|
2015-01-25 21:46:48 +01:00
|
|
|
pass
|
2014-10-26 19:14:46 +01:00
|
|
|
|
2014-05-15 11:56:22 +02:00
|
|
|
def _init_buttons(self):
|
2015-01-24 20:13:18 +01:00
|
|
|
"""Initialize the buttons."""
|
|
|
|
self._btn_box = QDialogButtonBox()
|
|
|
|
self._vbox.addWidget(self._btn_box)
|
2014-05-15 11:56:22 +02:00
|
|
|
|
2015-01-24 20:13:18 +01:00
|
|
|
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 "
|
2016-01-05 07:07:45 +01:00
|
|
|
"the bug you encountered.<br/> Crash reports are "
|
|
|
|
"currently publicly accessible.</b>",
|
|
|
|
wordWrap=True)
|
2015-01-24 20:13:18 +01:00
|
|
|
self._vbox.addWidget(info_label)
|
2014-02-13 09:10:53 +01:00
|
|
|
|
2014-05-15 11:56:22 +02:00
|
|
|
def _gather_crash_info(self):
|
2014-02-19 10:58:32 +01:00
|
|
|
"""Gather crash information to display.
|
|
|
|
|
|
|
|
Args:
|
2014-09-29 22:37:46 +02:00
|
|
|
pages: A list of lists of the open pages (URLs as strings)
|
2014-02-19 10:58:32 +01:00
|
|
|
cmdhist: A list with the command history (as strings)
|
|
|
|
exc: An exception tuple (type, value, traceback)
|
|
|
|
"""
|
2015-08-07 20:36:38 +02:00
|
|
|
try:
|
2015-08-07 21:15:40 +02:00
|
|
|
application = QApplication.instance()
|
2015-08-07 22:32:02 +02:00
|
|
|
launch_time = application.launch_time.ctime()
|
2016-02-17 07:10:01 +01:00
|
|
|
crash_time = datetime.datetime.now().ctime()
|
|
|
|
text = 'Launch: {}\nCrash: {}'.format(launch_time, crash_time)
|
|
|
|
self._crash_info.append(('Timestamps', text))
|
2015-08-07 20:36:38 +02:00
|
|
|
except Exception:
|
|
|
|
self._crash_info.append(("Launch time", traceback.format_exc()))
|
2014-09-16 07:34:27 +02:00
|
|
|
try:
|
|
|
|
self._crash_info.append(("Version info", version.version()))
|
2014-09-16 08:20:19 +02:00
|
|
|
except Exception:
|
2014-09-16 07:34:27 +02:00
|
|
|
self._crash_info.append(("Version info", traceback.format_exc()))
|
2014-04-21 21:09:15 +02:00
|
|
|
try:
|
2014-09-24 07:06:45 +02:00
|
|
|
conf = objreg.get('config')
|
2014-09-23 23:17:36 +02:00
|
|
|
self._crash_info.append(("Config", conf.dump_userconfig()))
|
2014-09-16 08:20:19 +02:00
|
|
|
except Exception:
|
|
|
|
self._crash_info.append(("Config", traceback.format_exc()))
|
2015-10-01 14:57:11 +02:00
|
|
|
try:
|
|
|
|
self._crash_info.append(
|
|
|
|
("Environment", _get_environment_vars()))
|
|
|
|
except Exception:
|
|
|
|
self._crash_info.append(("Environment", traceback.format_exc()))
|
2014-05-15 11:56:22 +02:00
|
|
|
|
2014-10-26 19:14:46 +01:00
|
|
|
def _set_crash_info(self):
|
|
|
|
"""Set/update the crash info."""
|
|
|
|
self._crash_info = []
|
|
|
|
self._gather_crash_info()
|
|
|
|
chunks = []
|
2014-05-15 11:56:22 +02:00
|
|
|
for (header, body) in self._crash_info:
|
2014-03-06 00:06:28 +01:00
|
|
|
if body is not None:
|
|
|
|
h = '==== {} ===='.format(header)
|
|
|
|
chunks.append('\n'.join([h, body]))
|
2014-10-26 19:14:46 +01:00
|
|
|
text = '\n\n'.join(chunks)
|
|
|
|
self._debug_log.setText(text)
|
2014-05-06 15:36:15 +02:00
|
|
|
|
2015-01-24 14:21:07 +01:00
|
|
|
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()
|
2015-01-25 22:14:55 +01:00
|
|
|
title = "qute {} {}".format(
|
|
|
|
qutebrowser.__version__, self._get_error_type())
|
2015-01-24 14:21:07 +01:00
|
|
|
if desc:
|
2015-01-25 22:14:55 +01:00
|
|
|
title += ' {}'.format(desc)
|
2015-01-24 14:21:07 +01:00
|
|
|
return title
|
|
|
|
|
2015-01-24 20:13:18 +01:00
|
|
|
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!")
|
|
|
|
|
2014-10-26 19:14:46 +01:00
|
|
|
def report(self):
|
2014-05-06 15:36:15 +02:00
|
|
|
"""Paste the crash info into the pastebin."""
|
2014-10-26 19:14:46 +01:00
|
|
|
lines = []
|
|
|
|
lines.append("========== Report ==========")
|
|
|
|
lines.append(self._info.toPlainText())
|
|
|
|
lines.append("========== Contact ==========")
|
|
|
|
lines.append(self._contact.toPlainText())
|
|
|
|
lines.append("========== Debug log ==========")
|
|
|
|
lines.append(self._debug_log.toPlainText())
|
2014-12-11 23:34:03 +01:00
|
|
|
self._paste_text = '\n\n'.join(lines)
|
2014-05-06 15:36:15 +02:00
|
|
|
try:
|
2014-10-31 07:57:50 +01:00
|
|
|
user = getpass.getuser()
|
|
|
|
except Exception as e:
|
|
|
|
log.misc.exception("Error while getting user")
|
|
|
|
user = 'unknown'
|
|
|
|
try:
|
2014-12-11 23:34:03 +01:00
|
|
|
# parent: http://p.cmpl.cc/90286958
|
2015-01-24 14:21:07 +01:00
|
|
|
self._paste_client.paste(user, self._get_paste_title(),
|
2014-12-11 23:34:03 +01:00
|
|
|
self._paste_text, parent='90286958')
|
2014-09-16 08:20:19 +02:00
|
|
|
except Exception as e:
|
|
|
|
log.misc.exception("Error while paste-binning")
|
2014-10-26 19:14:46 +01:00
|
|
|
exc_text = '{}: {}'.format(e.__class__.__name__, e)
|
2014-12-11 23:34:03 +01:00
|
|
|
self.show_error(exc_text)
|
2014-10-26 19:14:46 +01:00
|
|
|
|
|
|
|
@pyqtSlot()
|
2015-01-24 20:13:18 +01:00
|
|
|
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...")
|
2015-04-10 07:52:06 +02:00
|
|
|
self._paste_client.success.connect(self.on_paste_success)
|
2014-12-11 23:34:03 +01:00
|
|
|
self._paste_client.error.connect(self.show_error)
|
2015-01-24 20:13:18 +01:00
|
|
|
self.report()
|
2014-12-11 23:34:03 +01:00
|
|
|
|
2015-04-10 07:52:06 +02:00
|
|
|
@pyqtSlot()
|
|
|
|
def on_paste_success(self):
|
|
|
|
"""Get the newest version from PyPI when the paste is done."""
|
|
|
|
self._pypi_client.success.connect(self.on_version_success)
|
|
|
|
self._pypi_client.error.connect(self.on_version_error)
|
|
|
|
self._pypi_client.get_version()
|
|
|
|
|
2014-12-11 23:34:03 +01:00
|
|
|
@pyqtSlot(str)
|
|
|
|
def show_error(self, text):
|
|
|
|
"""Show a paste error dialog.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
text: The paste text to show.
|
|
|
|
"""
|
|
|
|
error_dlg = ReportErrorDialog(text, self._paste_text, self)
|
|
|
|
error_dlg.finished.connect(self.finish)
|
|
|
|
error_dlg.show()
|
|
|
|
|
2015-04-10 07:52:06 +02:00
|
|
|
@pyqtSlot(str)
|
|
|
|
def on_version_success(self, newest):
|
|
|
|
"""Called when the version was obtained from self._pypi_client.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
newest: The newest version as a string.
|
|
|
|
"""
|
2015-06-18 18:45:51 +02:00
|
|
|
new_version = pkg_resources.parse_version(newest)
|
|
|
|
cur_version = pkg_resources.parse_version(qutebrowser.__version__)
|
2015-04-10 07:52:06 +02:00
|
|
|
lines = ['The report has been sent successfully. Thanks!']
|
|
|
|
if new_version > cur_version:
|
|
|
|
lines.append("<b>Note:</b> The newest available version is v{}, "
|
|
|
|
"but you're currently running v{} - please "
|
|
|
|
"update!".format(newest, qutebrowser.__version__))
|
|
|
|
text = '<br/><br/>'.join(lines)
|
2016-01-05 07:02:20 +01:00
|
|
|
self.finish()
|
2015-04-10 07:52:06 +02:00
|
|
|
msgbox.information(self, "Report successfully sent!", text,
|
|
|
|
on_finished=self.finish, plain_text=False)
|
|
|
|
|
|
|
|
@pyqtSlot(str)
|
|
|
|
def on_version_error(self, msg):
|
|
|
|
"""Called when the version was not obtained from self._pypi_client.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
msg: The error message to show.
|
|
|
|
"""
|
|
|
|
lines = ['The report has been sent successfully. Thanks!']
|
|
|
|
lines.append("There was an error while getting the newest version: "
|
|
|
|
"{}. Please check for a new version on "
|
|
|
|
"<a href=http://www.qutebrowser.org/>qutebrowser.org</a> "
|
|
|
|
"by yourself.".format(msg))
|
|
|
|
text = '<br/><br/>'.join(lines)
|
2016-01-05 07:02:20 +01:00
|
|
|
self.finish()
|
2015-04-10 07:52:06 +02:00
|
|
|
msgbox.information(self, "Report successfully sent!", text,
|
|
|
|
on_finished=self.finish, plain_text=False)
|
|
|
|
|
2014-12-11 23:34:03 +01:00
|
|
|
@pyqtSlot()
|
|
|
|
def finish(self):
|
2015-01-24 20:13:18 +01:00
|
|
|
"""Save contact info and close the dialog."""
|
|
|
|
self._save_contact_info()
|
|
|
|
self.accept()
|
2014-05-15 11:56:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ExceptionCrashDialog(_CrashDialog):
|
|
|
|
|
|
|
|
"""Dialog which gets shown on an exception.
|
|
|
|
|
|
|
|
Attributes:
|
2014-09-29 22:37:46 +02:00
|
|
|
_pages: A list of lists of the open pages (URLs as strings)
|
2014-05-15 11:56:22 +02:00
|
|
|
_cmdhist: A list with the command history (as strings)
|
|
|
|
_exc: An exception tuple (type, value, traceback)
|
2014-06-17 23:04:58 +02:00
|
|
|
_objects: A list of all QObjects as string.
|
2014-05-15 11:56:22 +02:00
|
|
|
"""
|
|
|
|
|
2014-10-31 07:05:04 +01:00
|
|
|
def __init__(self, debug, pages, cmdhist, exc, objects, parent=None):
|
2014-11-02 19:16:13 +01:00
|
|
|
self._chk_log = None
|
2015-01-25 21:46:48 +01:00
|
|
|
self._chk_restore = None
|
2014-10-31 07:05:04 +01:00
|
|
|
super().__init__(debug, parent)
|
2014-05-15 11:56:22 +02:00
|
|
|
self._pages = pages
|
|
|
|
self._cmdhist = cmdhist
|
|
|
|
self._exc = exc
|
2014-06-17 23:04:58 +02:00
|
|
|
self._objects = objects
|
2014-05-15 11:56:22 +02:00
|
|
|
self.setModal(True)
|
2014-10-26 19:14:46 +01:00
|
|
|
self._set_crash_info()
|
2014-05-15 11:56:22 +02:00
|
|
|
|
|
|
|
def _init_text(self):
|
|
|
|
super()._init_text()
|
2014-10-26 19:14:46 +01:00
|
|
|
text = "<b>Argh! qutebrowser crashed unexpectedly.</b>"
|
2014-05-15 11:56:22 +02:00
|
|
|
self._lbl.setText(text)
|
|
|
|
|
|
|
|
def _init_buttons(self):
|
|
|
|
super()._init_buttons()
|
2015-01-24 20:13:18 +01:00
|
|
|
|
|
|
|
def _init_checkboxes(self):
|
|
|
|
"""Add checkboxes to the dialog."""
|
|
|
|
super()._init_checkboxes()
|
2015-01-25 21:46:48 +01:00
|
|
|
self._chk_restore = QCheckBox("Restore open pages")
|
|
|
|
self._chk_restore.setChecked(True)
|
|
|
|
self._vbox.addWidget(self._chk_restore)
|
2015-01-24 20:13:18 +01:00
|
|
|
self._chk_log = QCheckBox("Include a debug log in the report",
|
|
|
|
checked=True)
|
2015-01-04 20:41:35 +01:00
|
|
|
try:
|
|
|
|
if config.get('general', 'private-browsing'):
|
|
|
|
self._chk_log.setChecked(False)
|
|
|
|
except Exception:
|
|
|
|
log.misc.exception("Error while checking private browsing mode")
|
2014-10-26 19:14:46 +01:00
|
|
|
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 "
|
|
|
|
"crash, but it might contain sensitive "
|
|
|
|
"information such as which pages you visited "
|
|
|
|
"or keyboard input.</i>", wordWrap=True)
|
|
|
|
self._vbox.addWidget(info_label)
|
2014-05-15 11:56:22 +02:00
|
|
|
|
2015-01-24 14:21:07 +01:00
|
|
|
def _get_error_type(self):
|
2015-01-25 22:14:55 +01:00
|
|
|
return 'exc'
|
2015-01-24 14:21:07 +01:00
|
|
|
|
|
|
|
def _get_paste_title_desc(self):
|
|
|
|
desc = traceback.format_exception_only(self._exc[0], self._exc[1])
|
|
|
|
return desc[0].rstrip()
|
|
|
|
|
2014-05-15 11:56:22 +02:00
|
|
|
def _gather_crash_info(self):
|
|
|
|
self._crash_info += [
|
|
|
|
("Exception", ''.join(traceback.format_exception(*self._exc))),
|
|
|
|
]
|
2015-01-16 09:22:22 +01:00
|
|
|
super()._gather_crash_info()
|
2014-10-26 19:14:46 +01:00
|
|
|
if self._chk_log.isChecked():
|
|
|
|
self._crash_info += [
|
|
|
|
("Commandline args", ' '.join(sys.argv[1:])),
|
|
|
|
("Open Pages", '\n\n'.join('\n'.join(e) for e in self._pages)),
|
|
|
|
("Command history", '\n'.join(self._cmdhist)),
|
|
|
|
("Objects", self._objects),
|
|
|
|
]
|
|
|
|
try:
|
|
|
|
self._crash_info.append(
|
|
|
|
("Debug log", log.ram_handler.dump_log()))
|
|
|
|
except Exception:
|
|
|
|
self._crash_info.append(
|
|
|
|
("Debug log", traceback.format_exc()))
|
|
|
|
|
|
|
|
@pyqtSlot()
|
2015-01-24 20:13:18 +01:00
|
|
|
def finish(self):
|
|
|
|
self._save_contact_info()
|
|
|
|
if self._chk_restore.isChecked():
|
|
|
|
self.accept()
|
|
|
|
else:
|
|
|
|
self.reject()
|
2014-05-15 12:20:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
class FatalCrashDialog(_CrashDialog):
|
|
|
|
|
2015-03-31 20:49:29 +02:00
|
|
|
"""Dialog which gets shown when a fatal error occurred.
|
2014-05-15 12:20:03 +02:00
|
|
|
|
|
|
|
Attributes:
|
|
|
|
_log: The log text to display.
|
2015-03-31 20:49:29 +02:00
|
|
|
_type: The type of error which occurred.
|
|
|
|
_func: The function (top of the stack) in which the error occurred.
|
2015-02-01 23:12:46 +01:00
|
|
|
_chk_history: A checkbox for the user to decide if page history should
|
|
|
|
be sent.
|
2014-05-15 12:20:03 +02:00
|
|
|
"""
|
|
|
|
|
2014-10-31 07:05:04 +01:00
|
|
|
def __init__(self, debug, text, parent=None):
|
2015-02-01 23:12:46 +01:00
|
|
|
self._chk_history = None
|
2014-10-31 07:05:04 +01:00
|
|
|
super().__init__(debug, parent)
|
2014-10-26 19:14:46 +01:00
|
|
|
self._log = text
|
2014-06-27 07:39:01 +02:00
|
|
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
2014-10-26 19:14:46 +01:00
|
|
|
self._set_crash_info()
|
2015-01-24 14:21:07 +01:00
|
|
|
self._type, self._func = parse_fatal_stacktrace(self._log)
|
|
|
|
|
|
|
|
def _get_error_type(self):
|
2015-01-25 22:14:55 +01:00
|
|
|
if self._type == 'Segmentation fault':
|
|
|
|
return 'segv'
|
|
|
|
else:
|
|
|
|
return self._type
|
2015-01-24 14:21:07 +01:00
|
|
|
|
|
|
|
def _get_paste_title_desc(self):
|
2015-01-25 22:14:55 +01:00
|
|
|
return self._func
|
2014-05-15 12:20:03 +02:00
|
|
|
|
|
|
|
def _init_text(self):
|
|
|
|
super()._init_text()
|
2014-11-02 19:06:43 +01:00
|
|
|
text = ("<b>qutebrowser was restarted after a fatal crash.</b><br/>"
|
|
|
|
"<br/>Note: Crash reports for fatal crashes sometimes don't "
|
|
|
|
"contain the information necessary to fix an issue. Please "
|
|
|
|
"follow the steps in <a href='https://github.com/The-Compiler/"
|
|
|
|
"qutebrowser/blob/master/doc/stacktrace.asciidoc'>"
|
|
|
|
"stacktrace.asciidoc</a> to submit a stacktrace.<br/>")
|
2014-05-15 12:20:03 +02:00
|
|
|
self._lbl.setText(text)
|
|
|
|
|
2015-02-01 23:12:46 +01:00
|
|
|
def _init_checkboxes(self):
|
|
|
|
"""Add checkboxes to the dialog."""
|
|
|
|
super()._init_checkboxes()
|
|
|
|
self._chk_history = QCheckBox("Include a history of the last "
|
|
|
|
"accessed pages in the report.",
|
|
|
|
checked=True)
|
|
|
|
try:
|
|
|
|
if config.get('general', 'private-browsing'):
|
|
|
|
self._chk_history.setChecked(False)
|
|
|
|
except Exception:
|
|
|
|
log.misc.exception("Error while checking private browsing mode")
|
|
|
|
self._chk_history.toggled.connect(self._set_crash_info)
|
|
|
|
self._vbox.addWidget(self._chk_history)
|
|
|
|
|
2014-05-15 12:20:03 +02:00
|
|
|
def _gather_crash_info(self):
|
2015-02-01 23:12:32 +01:00
|
|
|
self._crash_info.append(("Fault log", self._log))
|
2014-10-26 19:14:46 +01:00
|
|
|
super()._gather_crash_info()
|
2015-02-01 23:12:46 +01:00
|
|
|
if self._chk_history.isChecked():
|
|
|
|
try:
|
2015-03-02 22:44:43 +01:00
|
|
|
history = objreg.get('web-history').get_recent()
|
|
|
|
self._crash_info.append(("History", ''.join(history)))
|
2015-02-01 23:12:46 +01:00
|
|
|
except Exception:
|
|
|
|
self._crash_info.append(("History", traceback.format_exc()))
|
2014-06-25 22:22:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
class ReportDialog(_CrashDialog):
|
|
|
|
|
|
|
|
"""Dialog which gets shown when the user wants to report an issue by hand.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
_pages: A list of the open pages (URLs as strings)
|
|
|
|
_cmdhist: A list with the command history (as strings)
|
|
|
|
_objects: A list of all QObjects as string.
|
|
|
|
"""
|
|
|
|
|
2014-09-23 07:53:40 +02:00
|
|
|
def __init__(self, pages, cmdhist, objects, parent=None):
|
2014-10-31 07:05:04 +01:00
|
|
|
super().__init__(False, parent)
|
2014-10-26 19:14:46 +01:00
|
|
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
2014-06-25 22:22:30 +02:00
|
|
|
self._pages = pages
|
|
|
|
self._cmdhist = cmdhist
|
|
|
|
self._objects = objects
|
2014-10-26 19:14:46 +01:00
|
|
|
self._set_crash_info()
|
2014-06-25 22:22:30 +02:00
|
|
|
|
|
|
|
def _init_text(self):
|
|
|
|
super()._init_text()
|
2014-10-26 19:14:46 +01:00
|
|
|
text = "Please describe the bug you encountered below."
|
2014-06-25 22:22:30 +02:00
|
|
|
self._lbl.setText(text)
|
|
|
|
|
2015-01-24 20:13:18 +01:00
|
|
|
def _init_info_text(self):
|
|
|
|
"""We don't want an info text as the user wanted to report."""
|
|
|
|
pass
|
|
|
|
|
2015-01-24 14:21:07 +01:00
|
|
|
def _get_error_type(self):
|
|
|
|
return 'report'
|
|
|
|
|
2014-06-25 22:22:30 +02:00
|
|
|
def _gather_crash_info(self):
|
|
|
|
super()._gather_crash_info()
|
|
|
|
self._crash_info += [
|
|
|
|
("Commandline args", ' '.join(sys.argv[1:])),
|
2014-09-29 22:37:46 +02:00
|
|
|
("Open Pages", '\n\n'.join('\n'.join(e) for e in self._pages)),
|
2014-06-25 22:22:30 +02:00
|
|
|
("Command history", '\n'.join(self._cmdhist)),
|
|
|
|
("Objects", self._objects),
|
|
|
|
]
|
|
|
|
try:
|
2014-08-26 19:10:14 +02:00
|
|
|
self._crash_info.append(("Debug log", log.ram_handler.dump_log()))
|
2014-09-16 08:20:19 +02:00
|
|
|
except Exception:
|
|
|
|
self._crash_info.append(("Debug log", traceback.format_exc()))
|
2014-10-26 19:14:46 +01:00
|
|
|
|
|
|
|
|
|
|
|
class ReportErrorDialog(QDialog):
|
|
|
|
|
|
|
|
"""An error dialog shown on unsuccessful reports."""
|
|
|
|
|
|
|
|
def __init__(self, exc_text, text, parent=None):
|
|
|
|
super().__init__(parent)
|
|
|
|
vbox = QVBoxLayout(self)
|
|
|
|
label = QLabel("<b>There was an error while reporting the crash</b>:"
|
|
|
|
"<br/>{}<br/><br/>"
|
|
|
|
"Please copy the text below and send a mail to "
|
|
|
|
"<a href='mailto:crash@qutebrowser.org'>"
|
|
|
|
"crash@qutebrowser.org</a> - Thanks!".format(
|
|
|
|
html.escape(exc_text)))
|
|
|
|
vbox.addWidget(label)
|
2014-10-31 07:13:17 +01:00
|
|
|
txt = QTextEdit(readOnly=True, tabChangesFocus=True,
|
|
|
|
acceptRichText=False)
|
2014-10-26 19:14:46 +01:00
|
|
|
txt.setText(text)
|
|
|
|
txt.selectAll()
|
|
|
|
vbox.addWidget(txt)
|
|
|
|
|
|
|
|
hbox = QHBoxLayout()
|
|
|
|
hbox.addStretch()
|
|
|
|
btn = QPushButton("Close")
|
|
|
|
btn.clicked.connect(self.close)
|
|
|
|
hbox.addWidget(btn)
|
|
|
|
vbox.addLayout(hbox)
|
2015-03-27 07:59:13 +01:00
|
|
|
|
|
|
|
|
|
|
|
def dump_exception_info(exc, pages, cmdhist, objects):
|
|
|
|
"""Dump exception info to stderr.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
exc: An exception tuple (type, value, traceback)
|
|
|
|
pages: A list of lists of the open pages (URLs as strings)
|
|
|
|
cmdhist: A list with the command history (as strings)
|
|
|
|
objects: A list of all QObjects as string.
|
|
|
|
"""
|
|
|
|
print(file=sys.stderr)
|
|
|
|
print("\n\n===== Handling exception with --no-err-windows... =====\n\n",
|
|
|
|
file=sys.stderr)
|
|
|
|
print("\n---- Exceptions ----", file=sys.stderr)
|
|
|
|
print(''.join(traceback.format_exception(*exc)), file=sys.stderr)
|
|
|
|
print("\n---- Version info ----", file=sys.stderr)
|
|
|
|
try:
|
|
|
|
print(version.version(), file=sys.stderr)
|
|
|
|
except Exception:
|
|
|
|
traceback.print_exc()
|
|
|
|
print("\n---- Config ----", file=sys.stderr)
|
|
|
|
try:
|
|
|
|
conf = objreg.get('config')
|
|
|
|
print(conf.dump_userconfig(), file=sys.stderr)
|
|
|
|
except Exception:
|
|
|
|
traceback.print_exc()
|
|
|
|
print("\n---- Commandline args ----", file=sys.stderr)
|
|
|
|
print(' '.join(sys.argv[1:]), file=sys.stderr)
|
|
|
|
print("\n---- Open pages ----", file=sys.stderr)
|
|
|
|
print('\n\n'.join('\n'.join(e) for e in pages), file=sys.stderr)
|
|
|
|
print("\n---- Command history ----", file=sys.stderr)
|
|
|
|
print('\n'.join(cmdhist), file=sys.stderr)
|
|
|
|
print("\n---- Objects ----", file=sys.stderr)
|
|
|
|
print(objects, file=sys.stderr)
|
2015-10-01 14:57:11 +02:00
|
|
|
print("\n---- Environment ----", file=sys.stderr)
|
|
|
|
try:
|
|
|
|
print(_get_environment_vars(), file=sys.stderr)
|
|
|
|
except Exception:
|
|
|
|
traceback.print_exc()
|