From da0f4332608d41eb16e53c843c52bca9d355fe48 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 26 Oct 2014 19:14:46 +0100 Subject: [PATCH 1/6] Start new crash dialog --- qutebrowser/widgets/crash.py | 332 +++++++++++++++++++++-------------- qutebrowser/widgets/misc.py | 94 +++++++++- 2 files changed, 290 insertions(+), 136 deletions(-) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index a98791e78..1d0de7229 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -22,14 +22,16 @@ """The dialog which gets shown when qutebrowser crashes.""" import sys +import html import traceback -import getpass +import functools -from PyQt5.QtCore import Qt, QSize +from PyQt5.QtCore import pyqtSlot, Qt, QSize from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton, - QVBoxLayout, QHBoxLayout) + QVBoxLayout, QHBoxLayout, QCheckBox) from qutebrowser.utils import version, log, utils, objreg +from qutebrowser.widgets.misc import DetailFold class _CrashDialog(QDialog): @@ -40,41 +42,55 @@ class _CrashDialog(QDialog): These are just here to have a static reference to avoid GCing. _vbox: The main QVBoxLayout _lbl: The QLabel with the static text - _txt: The QTextEdit with the crash information + _debug_log: The QTextEdit with the crash information _hbox: The QHboxLayout containing the buttons _url: Pastebin URL QLabel. _crash_info: A list of tuples with title and crash information. - - Class attributes: - CRASHTEXT: The text to be displayed in the dialog. """ - CRASHTEXT = ("Please review and edit the info below, then either submit " - "it to " - "crash@qutebrowser.org or click 'Report'.

" - "Note that without your help, I can't fix the bug you " - "encountered. With the report, I most probably will." - "

") - def __init__(self, parent=None): """Constructor for CrashDialog.""" 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._crash_info = None + self._buttons = [] + self._crash_info = [] self._hbox = None self._lbl = None - self._gather_crash_info() + self._chk_report = None self.setWindowTitle("Whoops!") - self.resize(QSize(800, 600)) + self.resize(QSize(640, 600)) self._vbox = QVBoxLayout(self) self._init_text() - self._txt = QTextEdit() - self._txt.setText(self._format_crash_info()) - self._vbox.addWidget(self._txt) - self._url = QLabel() - self._set_text_flags(self._url) - self._vbox.addWidget(self._url) + + info = QLabel("What were you doing when this crash/bug happened?") + self._vbox.addWidget(info) + self._info = QTextEdit(tabChangesFocus=True) + self._info.setPlaceholderText("- Opened http://www.example.com/\n" + "- 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) + self._contact.setPlaceholderText("Github username, mail or IRC") + self._vbox.addWidget(self._contact, 2) + + self._vbox.addSpacing(15) + self._debug_log = QTextEdit(tabChangesFocus=True) + info = QLabel("You can edit the log below to remove sensitive " + "information.", wordWrap=True) + info.hide() + self._fold = DetailFold("Show log", self) + self._fold.toggled.connect(self._debug_log.setVisible) + self._fold.toggled.connect(info.setVisible) + self._vbox.addWidget(self._fold) + self._vbox.addWidget(info) + self._vbox.addWidget(self._debug_log, 10) + self._debug_log.hide() + self._vbox.addSpacing(15) + + self._init_checkboxes() self._init_buttons() def __repr__(self): @@ -86,29 +102,29 @@ class _CrashDialog(QDialog): Should be extended by superclass to set the actual text.""" self._lbl = QLabel() self._lbl.setWordWrap(True) - self._set_text_flags(self._lbl) self._vbox.addWidget(self._lbl) + def _init_checkboxes(self): + """Initialize the checkboxes. + + Should be overwritten by subclasses. + """ + self._chk_report = QCheckBox("Send a report") + self._chk_report.setChecked(True) + self._vbox.addWidget(self._chk_report) + info_label = QLabel("Note that without your help, I can't fix the " + "bug you encountered.", wordWrap=True) + self._vbox.addWidget(info_label) + def _init_buttons(self): """Initialize the buttons. - Should be extended by superclass to provide the actual buttons. + Should be extended by subclasses to provide the actual buttons. """ self._hbox = QHBoxLayout() self._vbox.addLayout(self._hbox) self._hbox.addStretch() - def _set_text_flags(self, obj): - """Set text interaction flags of a widget to allow link clicking. - - Args: - obj: A QLabel. - """ - obj.setTextInteractionFlags(Qt.TextSelectableByMouse | - Qt.TextSelectableByKeyboard | - Qt.LinksAccessibleByMouse | - Qt.LinksAccessibleByKeyboard) - def _gather_crash_info(self): """Gather crash information to display. @@ -117,14 +133,6 @@ class _CrashDialog(QDialog): cmdhist: A list with the command history (as strings) exc: An exception tuple (type, value, traceback) """ - self._crash_info = [ - ("How did it happen?", ""), - ] - try: - self._crash_info.append(("Contact info", - "User: {}".format(getpass.getuser()))) - except Exception: - self._crash_info.append(("Contact info", traceback.format_exc())) try: self._crash_info.append(("Version info", version.version())) except Exception: @@ -135,33 +143,53 @@ class _CrashDialog(QDialog): except Exception: self._crash_info.append(("Config", traceback.format_exc())) - def _format_crash_info(self): - """Format the gathered crash info to be displayed. - - Return: - The string to display. - """ - chunks = ["Please edit this report to remove sensitive info, and add " - "as much info as possible about how it happened.\n" - "If it's okay if I contact you about this bug report, " - "please also add your contact info (Mail/IRC/Jabber)."] + def _set_crash_info(self): + """Set/update the crash info.""" + self._crash_info = [] + self._gather_crash_info() + chunks = [] for (header, body) in self._crash_info: if body is not None: h = '==== {} ===='.format(header) chunks.append('\n'.join([h, body])) - return '\n\n'.join(chunks) + text = '\n\n'.join(chunks) + self._debug_log.setText(text) - def pastebin(self): + def report(self): """Paste the crash info into the pastebin.""" + 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()) + text = '\n\n'.join(lines) try: - url = utils.pastebin(self._txt.toPlainText()) + utils.pastebin(text) except Exception as e: log.misc.exception("Error while paste-binning") - self._url.setText('Error while reporting: {}: {}'.format( - e.__class__.__name__, e)) - return - self._btn_pastebin.setEnabled(False) - self._url.setText("Reported to: {}".format(url, url)) + exc_text = '{}: {}'.format(e.__class__.__name__, e) + error_dlg = ReportErrorDialog(exc_text, text, self) + error_dlg.exec_() + + @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.maybe_report() + if accept: + self.accept() + else: + self.reject() + + @pyqtSlot() + def maybe_report(self): + """Report the bug if the user allowed us to.""" + if self._chk_report.isChecked(): + self.report() class ExceptionCrashDialog(_CrashDialog): @@ -169,9 +197,7 @@ class ExceptionCrashDialog(_CrashDialog): """Dialog which gets shown on an exception. Attributes: - _btn_quit: The quit button - _btn_restore: the restore button - _btn_pastebin: the pastebin button + _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) @@ -179,55 +205,72 @@ class ExceptionCrashDialog(_CrashDialog): """ def __init__(self, pages, cmdhist, exc, objects, parent=None): + super().__init__(parent) self._pages = pages self._cmdhist = cmdhist self._exc = exc - self._btn_quit = None - self._btn_restore = None - self._btn_pastebin = None self._objects = objects - super().__init__(parent) self.setModal(True) + self._set_crash_info() def _init_text(self): super()._init_text() - text = ("Argh! qutebrowser crashed unexpectedly.

" + - self.CRASHTEXT) - if self._pages: - text += ("You can click 'Restore tabs' after reporting to attempt " - "to reopen your open tabs.") + text = "Argh! qutebrowser crashed unexpectedly." self._lbl.setText(text) def _init_buttons(self): super()._init_buttons() - self._btn_quit = QPushButton() - self._btn_quit.setText("Quit") - self._btn_quit.clicked.connect(self.reject) - self._hbox.addWidget(self._btn_quit) - if self._pages: - self._btn_restore = QPushButton() - self._btn_restore.setText("Restore tabs") - self._btn_restore.clicked.connect(self.accept) - self._hbox.addWidget(self._btn_restore) - self._btn_pastebin = QPushButton() - self._btn_pastebin.setText("Report") - self._btn_pastebin.clicked.connect(self.pastebin) - self._btn_pastebin.setDefault(True) - self._hbox.addWidget(self._btn_pastebin) + 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): + """Add checkboxes to send crash report.""" + super()._init_checkboxes() + self._chk_log = QCheckBox("Include a debug log and a list of open " + "pages", checked=True) + self._chk_log.toggled.connect(self._set_crash_info) + self._vbox.addWidget(self._chk_log) + info_label = QLabel("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.", wordWrap=True) + self._vbox.addWidget(info_label) + self._chk_report.toggled.connect(self.on_chk_report_toggled) def _gather_crash_info(self): - super()._gather_crash_info() self._crash_info += [ ("Exception", ''.join(traceback.format_exception(*self._exc))), - ("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())) + 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())) + super()._gather_crash_info() + + @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) class FatalCrashDialog(_CrashDialog): @@ -236,40 +279,32 @@ class FatalCrashDialog(_CrashDialog): Attributes: _log: The log text to display. - _btn_ok: The OK button. - _btn_pastebin: The pastebin button. """ def __init__(self, text, parent=None): - self._log = text - self._btn_ok = None - self._btn_pastebin = None super().__init__(parent) + self._log = text self.setAttribute(Qt.WA_DeleteOnClose) + self._set_crash_info() def _init_text(self): super()._init_text() - text = ("qutebrowser was restarted after a fatal crash.
" - "
" + self.CRASHTEXT) + text = "qutebrowser was restarted after a fatal crash." self._lbl.setText(text) def _init_buttons(self): super()._init_buttons() - self._btn_ok = QPushButton() - self._btn_ok.setText("OK") - self._btn_ok.clicked.connect(self.accept) - self._hbox.addWidget(self._btn_ok) - self._btn_pastebin = QPushButton() - self._btn_pastebin.setText("Report") - self._btn_pastebin.clicked.connect(self.pastebin) - self._btn_pastebin.setDefault(True) - self._hbox.addWidget(self._btn_pastebin) + 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): - super()._gather_crash_info() self._crash_info += [ ("Fault log", self._log), ] + super()._gather_crash_info() class ReportDialog(_CrashDialog): @@ -277,40 +312,35 @@ class ReportDialog(_CrashDialog): """Dialog which gets shown when the user wants to report an issue by hand. Attributes: - _btn_ok: The OK button. - _btn_pastebin: The pastebin button. _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. """ def __init__(self, pages, cmdhist, objects, parent=None): - self._pages = pages - self._cmdhist = cmdhist - self._btn_ok = None - self._btn_pastebin = None - self._objects = objects super().__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose) + self._btn_report = None + self._pages = pages + self._cmdhist = cmdhist + self._objects = objects + self._set_crash_info() def _init_text(self): super()._init_text() - text = ("Please describe the bug you encountered below, then either " - "submit it to " - "crash@qutebrowser.org or click 'Report'.") + text = "Please describe the bug you encountered below." self._lbl.setText(text) def _init_buttons(self): super()._init_buttons() - self._btn_ok = QPushButton() - self._btn_ok.setText("OK") - self._btn_ok.clicked.connect(self.accept) - self._hbox.addWidget(self._btn_ok) - self._btn_pastebin = QPushButton() - self._btn_pastebin.setText("Report") - self._btn_pastebin.clicked.connect(self.pastebin) - self._btn_pastebin.setDefault(True) - self._hbox.addWidget(self._btn_pastebin) + self._btn_report = QPushButton("Report", default=True) + self._btn_report.clicked.connect(self.report) + self._btn_report.clicked.connect(self.close) + self._hbox.addWidget(self._btn_report) + + def _init_checkboxes(self): + """We don't want any checkboxes as the user wanted to report.""" + pass def _gather_crash_info(self): super()._gather_crash_info() @@ -324,3 +354,39 @@ class ReportDialog(_CrashDialog): self._crash_info.append(("Debug log", log.ram_handler.dump_log())) 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() + + +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("There was an error while reporting the crash:" + "
{}

" + "Please copy the text below and send a mail to " + "" + "crash@qutebrowser.org - Thanks!".format( + html.escape(exc_text))) + vbox.addWidget(label) + txt = QTextEdit(readOnly=True, tabChangesFocus=True) + 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) diff --git a/qutebrowser/widgets/misc.py b/qutebrowser/widgets/misc.py index b212ffa78..910d1c6d3 100644 --- a/qutebrowser/widgets/misc.py +++ b/qutebrowser/widgets/misc.py @@ -19,9 +19,10 @@ """Misc. widgets used at different places.""" -from PyQt5.QtCore import pyqtSlot, Qt -from PyQt5.QtWidgets import QLineEdit, QApplication -from PyQt5.QtGui import QValidator, QClipboard +from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QSize +from PyQt5.QtWidgets import (QLineEdit, QApplication, QWidget, QHBoxLayout, + QLabel, QStyleOption, QStyle) +from PyQt5.QtGui import QValidator, QClipboard, QPainter from qutebrowser.models import cmdhistory from qutebrowser.utils import utils @@ -135,3 +136,90 @@ class _CommandValidator(QValidator): return (QValidator.Acceptable, string, pos) else: return (QValidator.Invalid, string, pos) + + +class DetailFold(QWidget): + + """A "fold" widget with an arrow to show/hide details. + + Attributes: + _folded: Whether the widget is currently folded or not. + _hbox: The HBoxLayout the arrow/label are in. + _arrow: The FoldArrow widget. + + Signals: + toggled: Emitted when the widget was folded/unfolded. + arg 0: bool, if the contents are currently visible. + """ + + toggled = pyqtSignal(bool) + + def __init__(self, text, parent=None): + super().__init__(parent) + self._folded = True + self._hbox = QHBoxLayout(self) + self._hbox.setContentsMargins(0, 0, 0, 0) + self._arrow = _FoldArrow() + self._hbox.addWidget(self._arrow) + label = QLabel(text) + self._hbox.addWidget(label) + self._hbox.addStretch() + + def toggle(self): + """Toggle the fold of the widget.""" + self._folded = not self._folded + self._arrow.fold(self._folded) + self.toggled.emit(not self._folded) + + def mousePressEvent(self, e): + """Toggle the fold if the widget was pressed. + + Args: + e: The QMouseEvent. + """ + if e.button() == Qt.LeftButton: + e.accept() + self.toggle() + else: + super().mousePressEvent(e) + + +class _FoldArrow(QWidget): + + """The arrow shown for the DetailFold widget. + + Attributes: + _folded: Whether the widget is currently folded or not. + """ + + def __init__(self, parent=None): + super().__init__(parent) + self._folded = True + + def fold(self, folded): + """Fold/unfold the widget. + + Args: + folded: The new desired state. + """ + self._folded = folded + self.update() + + def paintEvent(self, _event): + """Paint the arrow. + + Args: + _paint: The QPaintEvent (unused). + """ + opt = QStyleOption() + opt.initFrom(self) + painter = QPainter(self) + if self._folded: + elem = QStyle.PE_IndicatorArrowRight + else: + elem = QStyle.PE_IndicatorArrowDown + self.style().drawPrimitive(elem, opt, painter, self) + + def minimumSizeHint(self): + """Return a sensible size.""" + return QSize(8, 8) From a2809e76bb77ffcd169b2c63c3d8c6ac2ea2cdcc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 31 Oct 2014 07:05:04 +0100 Subject: [PATCH 2/6] Handle --debug specially --- qutebrowser/app.py | 6 +++--- qutebrowser/widgets/crash.py | 41 +++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 357da6ba5..03dcb3794 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -188,7 +188,7 @@ class Application(QApplication): if data: # Crashlog exists and has data in it, so something crashed # previously. - self._crashdlg = crash.FatalCrashDialog(data) + self._crashdlg = crash.FatalCrashDialog(self._args.debug, data) self._crashdlg.show() else: # There's no log file, so we can use this to display crashes to the @@ -485,8 +485,8 @@ class Application(QApplication): except TypeError: log.destroy.exception("Error while preventing shutdown") QApplication.closeAllWindows() - self._crashdlg = crash.ExceptionCrashDialog(pages, history, exc, - objects) + self._crashdlg = crash.ExceptionCrashDialog( + self._args.debug, pages, history, exc, objects) ret = self._crashdlg.exec_() if ret == QDialog.Accepted: # restore self.restart(shutdown=False, pages=pages) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index 1d0de7229..ca6e78324 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -48,8 +48,12 @@ class _CrashDialog(QDialog): _crash_info: A list of tuples with title and crash information. """ - def __init__(self, parent=None): - """Constructor for CrashDialog.""" + def __init__(self, debug, parent=None): + """Constructor for CrashDialog. + + Args: + debug: Whether --debug was given. + """ 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. @@ -78,19 +82,21 @@ class _CrashDialog(QDialog): self._vbox.addSpacing(15) self._debug_log = QTextEdit(tabChangesFocus=True) + self._debug_log.hide() info = QLabel("You can edit the log below to remove sensitive " "information.", wordWrap=True) info.hide() self._fold = DetailFold("Show log", self) self._fold.toggled.connect(self._debug_log.setVisible) self._fold.toggled.connect(info.setVisible) + if debug: + self._fold.toggle() self._vbox.addWidget(self._fold) self._vbox.addWidget(info) self._vbox.addWidget(self._debug_log, 10) - self._debug_log.hide() self._vbox.addSpacing(15) - self._init_checkboxes() + self._init_checkboxes(debug) self._init_buttons() def __repr__(self): @@ -104,13 +110,15 @@ class _CrashDialog(QDialog): self._lbl.setWordWrap(True) self._vbox.addWidget(self._lbl) - def _init_checkboxes(self): + def _init_checkboxes(self, debug): """Initialize the checkboxes. - Should be overwritten by subclasses. + Args: + debug: Whether a --debug arg was given. """ self._chk_report = QCheckBox("Send a report") - self._chk_report.setChecked(True) + if not debug: + self._chk_report.setChecked(True) self._vbox.addWidget(self._chk_report) info_label = QLabel("Note that without your help, I can't fix the " "bug you encountered.", wordWrap=True) @@ -204,8 +212,8 @@ class ExceptionCrashDialog(_CrashDialog): _objects: A list of all QObjects as string. """ - def __init__(self, pages, cmdhist, exc, objects, parent=None): - super().__init__(parent) + def __init__(self, debug, pages, cmdhist, exc, objects, parent=None): + super().__init__(debug, parent) self._pages = pages self._cmdhist = cmdhist self._exc = exc @@ -232,11 +240,14 @@ class ExceptionCrashDialog(_CrashDialog): self._buttons = [btn_quit, btn_restart] - def _init_checkboxes(self): + def _init_checkboxes(self, debug): """Add checkboxes to send crash report.""" - super()._init_checkboxes() + 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) self._chk_log.toggled.connect(self._set_crash_info) self._vbox.addWidget(self._chk_log) info_label = QLabel("This makes it a lot easier to diagnose the " @@ -281,8 +292,8 @@ class FatalCrashDialog(_CrashDialog): _log: The log text to display. """ - def __init__(self, text, parent=None): - super().__init__(parent) + def __init__(self, debug, text, parent=None): + super().__init__(debug, parent) self._log = text self.setAttribute(Qt.WA_DeleteOnClose) self._set_crash_info() @@ -318,7 +329,7 @@ class ReportDialog(_CrashDialog): """ def __init__(self, pages, cmdhist, objects, parent=None): - super().__init__(parent) + super().__init__(False, parent) self.setAttribute(Qt.WA_DeleteOnClose) self._btn_report = None self._pages = pages @@ -338,7 +349,7 @@ class ReportDialog(_CrashDialog): self._btn_report.clicked.connect(self.close) self._hbox.addWidget(self._btn_report) - def _init_checkboxes(self): + def _init_checkboxes(self, _debug): """We don't want any checkboxes as the user wanted to report.""" pass From 589187ee6bbc73c5cc72f24259f794f213cc4c54 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 31 Oct 2014 07:13:17 +0100 Subject: [PATCH 3/6] Set some properties. --- qutebrowser/widgets/crash.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index ca6e78324..eef99b939 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -69,19 +69,20 @@ class _CrashDialog(QDialog): info = QLabel("What were you doing when this crash/bug happened?") self._vbox.addWidget(info) - self._info = QTextEdit(tabChangesFocus=True) + self._info = QTextEdit(tabChangesFocus=True, acceptRichText=False) self._info.setPlaceholderText("- Opened http://www.example.com/\n" "- 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) + 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) + self._debug_log = QTextEdit(tabChangesFocus=True, acceptRichText=False, + lineWrapMode=QTextEdit.NoWrap) self._debug_log.hide() info = QLabel("You can edit the log below to remove sensitive " "information.", wordWrap=True) @@ -390,7 +391,8 @@ class ReportErrorDialog(QDialog): "crash@qutebrowser.org - Thanks!".format( html.escape(exc_text))) vbox.addWidget(label) - txt = QTextEdit(readOnly=True, tabChangesFocus=True) + txt = QTextEdit(readOnly=True, tabChangesFocus=True, + acceptRichText=False) txt.setText(text) txt.selectAll() vbox.addWidget(txt) From 1c32e72e37e93fa2453eb4838003a52cdad042c7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 31 Oct 2014 07:36:35 +0100 Subject: [PATCH 4/6] Hide crash dialog before reporting. This makes it feel snappier. --- qutebrowser/widgets/crash.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index eef99b939..ddc4c96ce 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -188,6 +188,7 @@ class _CrashDialog(QDialog): button.setText("Reporting...") for btn in self._buttons: btn.setEnabled(False) + self.hide() self.maybe_report() if accept: self.accept() From 6e4759b65a70c1adadd3bbc43ab9fcb61a4ef940 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 31 Oct 2014 07:57:50 +0100 Subject: [PATCH 5/6] Better paste metainfo --- qutebrowser/utils/utils.py | 17 +++++++++++++---- qutebrowser/widgets/crash.py | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 536c0034c..3a96bc09d 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -147,14 +147,23 @@ def safe_shlex_split(s): orig_s, i, s)) # pylint: disable=undefined-loop-variable -def pastebin(text): - """Paste the text into a pastebin and return the URL.""" +def pastebin(name, title, text, parent=None): + """Paste the text into a pastebin and return the URL. + + Args: + name: The username to post as. + title: The post title. + text: The text to post. + parent: The parent paste to reply to. + """ api_url = 'http://paste.the-compiler.org/api/' data = { 'text': text, - 'title': "qutebrowser crash", - 'name': "qutebrowser", + 'title': title, + 'name': name, } + if parent is not None: + data['reply'] = parent encoded_data = urllib.parse.urlencode(data).encode('utf-8') create_url = urllib.parse.urljoin(api_url, 'create') headers = { diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index ddc4c96ce..d3f53d398 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -23,6 +23,7 @@ import sys import html +import getpass import traceback import functools @@ -38,6 +39,9 @@ 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 @@ -48,6 +52,8 @@ class _CrashDialog(QDialog): _crash_info: A list of tuples with title and crash information. """ + NAME = None + def __init__(self, debug, parent=None): """Constructor for CrashDialog. @@ -175,7 +181,13 @@ class _CrashDialog(QDialog): lines.append(self._debug_log.toPlainText()) text = '\n\n'.join(lines) try: - utils.pastebin(text) + user = getpass.getuser() + except Exception as e: + log.misc.exception("Error while getting user") + user = 'unknown' + try: + utils.pastebin(user, "qutebrowser {}".format(self.NAME), text, + parent='90286958') # http://p.cmpl.cc/90286958 except Exception as e: log.misc.exception("Error while paste-binning") exc_text = '{}: {}'.format(e.__class__.__name__, e) @@ -214,6 +226,8 @@ class ExceptionCrashDialog(_CrashDialog): _objects: A list of all QObjects as string. """ + NAME = 'exception' + def __init__(self, debug, pages, cmdhist, exc, objects, parent=None): super().__init__(debug, parent) self._pages = pages @@ -294,6 +308,8 @@ class FatalCrashDialog(_CrashDialog): _log: The log text to display. """ + NAME = 'segfault' + def __init__(self, debug, text, parent=None): super().__init__(debug, parent) self._log = text @@ -330,6 +346,8 @@ 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) From adfd1e5b9dad26a0119f6393b8962a1470b762ad Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 2 Nov 2014 19:06:43 +0100 Subject: [PATCH 6/6] Add text about stacktrace.asciidoc in crash dialog. Closes #140. --- qutebrowser/widgets/crash.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/qutebrowser/widgets/crash.py b/qutebrowser/widgets/crash.py index d3f53d398..6bf3af973 100644 --- a/qutebrowser/widgets/crash.py +++ b/qutebrowser/widgets/crash.py @@ -113,8 +113,8 @@ class _CrashDialog(QDialog): """Initialize the main text to be displayed on an exception. Should be extended by superclass to set the actual text.""" - self._lbl = QLabel() - self._lbl.setWordWrap(True) + self._lbl = QLabel(wordWrap=True, openExternalLinks=True, + textInteractionFlags=Qt.LinksAccessibleByMouse) self._vbox.addWidget(self._lbl) def _init_checkboxes(self, debug): @@ -318,7 +318,12 @@ class FatalCrashDialog(_CrashDialog): def _init_text(self): super()._init_text() - text = "qutebrowser was restarted after a fatal crash." + text = ("qutebrowser was restarted after a fatal crash.
" + "
Note: Crash reports for fatal crashes sometimes don't " + "contain the information necessary to fix an issue. Please " + "follow the steps in " + "stacktrace.asciidoc to submit a stacktrace.
") self._lbl.setText(text) def _init_buttons(self):