From ac66784b799bbf2fc581b64325087cb8b8878ff9 Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Sat, 2 Sep 2023 03:33:54 +0300 Subject: [PATCH] kcrash: implement dialog to show backtrace also open the program bug report address, unless it is official Katana application in which case the default bug report URL will be opened (defined as KDE_BUG_REPORT_URL) Signed-off-by: Ivailo Monev --- kcrash/kded/kded_kcrash.cpp | 121 ++++++++++++++++++++++++++++++++---- kcrash/kded/kded_kcrash.h | 34 +++++++++- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/kcrash/kded/kded_kcrash.cpp b/kcrash/kded/kded_kcrash.cpp index e877d7f8..0557d904 100644 --- a/kcrash/kded/kded_kcrash.cpp +++ b/kcrash/kded/kded_kcrash.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,64 @@ static const QStringList s_kcrashfilters = QStringList() << QString::fromLatin1("*.kcrash"); +KCrashDialog::KCrashDialog(const KCrashDetails &kcrashdetails, QWidget *parent) + : KDialog(parent), + m_mainvbox(nullptr) +{ + setWindowIcon(KIcon("tools-report-bug")); + // do not include the application name in the title + setWindowTitle( + KDialog::makeStandardCaption( + i18nc( + "@title:window", "Crash Details for %1 (%2)", + kcrashdetails.kcrashappname, kcrashdetails.kcrashapppid + ), + nullptr, KDialog::NoCaptionFlags + ) + ); + setButtons(KDialog::Ok | KDialog::Close); + setDefaultButton(KDialog::Ok); + setButtonText(KDialog::Ok, i18nc("@action:button", "Report")); + KUrl bugreporturl(kcrashdetails.kcrashbugreporturl); + const QString bugreporturlprotocol = bugreporturl.protocol(); + if (bugreporturlprotocol.isEmpty() || bugreporturlprotocol == QLatin1String("mailto")) { + setButtonIcon(KDialog::Ok, KIcon("internet-mail")); + bugreporturl.setScheme("mailto"); + } else { + setButtonIcon(KDialog::Ok, KIcon("internet-web-browser")); + } + m_reporturl = bugreporturl.url(); + + m_mainvbox = new KVBox(this); + setMainWidget(m_mainvbox); + + m_backtrace = new KTextEdit(m_mainvbox); + m_backtrace->setReadOnly(true); + m_backtrace->setLineWrapMode(QTextEdit::NoWrap); + m_backtrace->setText( + QString::fromLatin1( + kcrashdetails.kcrashbacktrace.constData(), + kcrashdetails.kcrashbacktrace.size() + ) + ); + + KConfigGroup kconfiggroup(KGlobal::config(), "KCrashDialog"); + restoreDialogSize(kconfiggroup); +} + +KCrashDialog::~KCrashDialog() +{ + KConfigGroup kconfiggroup(KGlobal::config(), "KCrashDialog"); + saveDialogSize(kconfiggroup); + KGlobal::config()->sync(); +} + +QString KCrashDialog::reportUrl() const +{ + return m_reporturl; +} + + K_PLUGIN_FACTORY(KCrashModuleFactory, registerPlugin();) K_EXPORT_PLUGIN(KCrashModuleFactory("kcrash")) @@ -56,14 +115,20 @@ KCrashModule::KCrashModule(QObject *parent, const QList &args) KCrashModule::~KCrashModule() { delete m_dirwatch; + kDebug() << "Closing notifications" << m_notifications.size(); - QMutableListIterator iter(m_notifications); + QMutableMapIterator iter(m_notifications); while (iter.hasNext()) { - KNotification* knotification = iter.next(); + iter.next(); + KNotification* knotification = iter.key(); disconnect(knotification, 0, this, 0); knotification->close(); iter.remove(); } + + kDebug() << "Closing dialogs" << m_dialogs.size(); + qDeleteAll(m_dialogs); + m_dialogs.clear(); } void KCrashModule::slotDirty(const QString &path) @@ -100,21 +165,33 @@ void KCrashModule::slotDirty(const QString &path) kcrashappname = kcrashdata["appname"]; } if (kcrashflags & KCrash::Notify) { + QString bugreporturl = kcrashdata["bugaddress"]; + // special case for Katana applications + if (bugreporturl == QLatin1String(KDE_BUG_REPORT_EMAIL)) { + bugreporturl = QString::fromLatin1(KDE_BUG_REPORT_URL); + } + + KCrashDetails kcrashdetails; + kcrashdetails.kcrashappname = kcrashappname; + kcrashdetails.kcrashapppid = kcrashdata["pid"]; + kcrashdetails.kcrashbacktrace = kcrashbacktrace; + kcrashdetails.kcrashbugreporturl = bugreporturl; + kDebug() << "Sending notification for" << kcrashfilepath; // NOTE: when the notification is closed/deleted the actions become non-operational KNotification* knotification = new KNotification(this); knotification->setEventID("kcrash/Crash"); knotification->setFlags(KNotification::Persistent); knotification->setTitle(i18n("%1 crashed", kcrashappname)); - knotification->setText(i18n("For details about the crash look into the system log.")); - knotification->setActions(QStringList() << i18n("Report")); - m_notifications.append(knotification); + knotification->setText(i18n("To view details about the crash and report it click on the details button.")); + knotification->setActions(QStringList() << i18n("Details")); + m_notifications.insert(knotification, kcrashdetails); connect(knotification, SIGNAL(closed()), this, SLOT(slotClosed())); - connect(knotification, SIGNAL(action1Activated()), this, SLOT(slotReport())); + connect(knotification, SIGNAL(action1Activated()), this, SLOT(slotDetails())); knotification->send(); } if (kcrashflags & KCrash::Log) { - // NOTE: this goes to the system log by default + // NOTE: this goes to the system log by default which the user may not have access to kError() << kcrashappname << "crashed" << kcrashbacktrace; } if (kcrashflags & KCrash::AutoRestart) { @@ -135,15 +212,37 @@ void KCrashModule::slotClosed() { KNotification* knotification = qobject_cast(sender()); kDebug() << "Notification closed" << knotification; - m_notifications.removeAll(knotification); + m_notifications.remove(knotification); } -void KCrashModule::slotReport() +void KCrashModule::slotDetails() { KNotification* knotification = qobject_cast(sender()); + kDebug() << "Notification details will be shown" << knotification; + const KCrashDetails kcrashdetails = m_notifications.value(knotification); knotification->close(); - const QString kcrashreporturl = KDE_BUG_REPORT_URL; - KToolInvocation::invokeBrowser(kcrashreporturl); + KCrashDialog* kcrashdialog = new KCrashDialog(kcrashdetails); + m_dialogs.append(kcrashdialog); + connect(kcrashdialog, SIGNAL(finished(int)), this, SLOT(slotDialogFinished(int))); + kcrashdialog->show(); +} + +void KCrashModule::slotDialogFinished(const int result) +{ + kDebug() << "Notification details result" << result; + if (result != QDialog::Accepted) { + return; + } + + KCrashDialog* kcrashdetails = qobject_cast(sender()); + const QString kcrashreporturl = kcrashdetails->reportUrl(); + m_dialogs.removeAll(kcrashdetails); + kcrashdetails->deleteLater(); + if (kcrashreporturl.startsWith(QLatin1String("mailto:"))) { + KToolInvocation::invokeMailer(kcrashreporturl, QString::fromLatin1("Crash report")); + } else { + KToolInvocation::invokeBrowser(kcrashreporturl); + } } #include "moc_kded_kcrash.cpp" diff --git a/kcrash/kded/kded_kcrash.h b/kcrash/kded/kded_kcrash.h index 60042e8d..5c21b997 100644 --- a/kcrash/kded/kded_kcrash.h +++ b/kcrash/kded/kded_kcrash.h @@ -23,6 +23,34 @@ #include #include +#include +#include +#include + +struct KCrashDetails +{ + QString kcrashappname; + QString kcrashapppid; + QByteArray kcrashbacktrace; + QString kcrashbugreporturl; +}; + + +class KCrashDialog : public KDialog +{ + Q_OBJECT +public: + KCrashDialog(const KCrashDetails &kcrashdetails, QWidget *parent = nullptr); + ~KCrashDialog(); + + QString reportUrl() const; + +private: + QString m_reporturl; + KVBox* m_mainvbox; + KTextEdit* m_backtrace; +}; + class KCrashModule: public KDEDModule { @@ -36,12 +64,14 @@ public: private Q_SLOTS: void slotDirty(const QString &path); void slotClosed(); - void slotReport(); + void slotDetails(); + void slotDialogFinished(const int result); private: QString m_kcrashpath; KDirWatch *m_dirwatch; - QList m_notifications; + QMap m_notifications; + QList m_dialogs; }; #endif // KCRASH_KDED_H