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 <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2023-09-02 03:33:54 +03:00
parent 2bdaf882c2
commit ac66784b79
2 changed files with 142 additions and 13 deletions

View file

@ -23,6 +23,7 @@
#include <klocale.h> #include <klocale.h>
#include <kglobalsettings.h> #include <kglobalsettings.h>
#include <ktoolinvocation.h> #include <ktoolinvocation.h>
#include <kurl.h>
#include <kcrash.h> #include <kcrash.h>
#include <kdebug.h> #include <kdebug.h>
@ -35,6 +36,64 @@
static const QStringList s_kcrashfilters = QStringList() static const QStringList s_kcrashfilters = QStringList()
<< QString::fromLatin1("*.kcrash"); << 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<KCrashModule>();) K_PLUGIN_FACTORY(KCrashModuleFactory, registerPlugin<KCrashModule>();)
K_EXPORT_PLUGIN(KCrashModuleFactory("kcrash")) K_EXPORT_PLUGIN(KCrashModuleFactory("kcrash"))
@ -56,14 +115,20 @@ KCrashModule::KCrashModule(QObject *parent, const QList<QVariant> &args)
KCrashModule::~KCrashModule() KCrashModule::~KCrashModule()
{ {
delete m_dirwatch; delete m_dirwatch;
kDebug() << "Closing notifications" << m_notifications.size(); kDebug() << "Closing notifications" << m_notifications.size();
QMutableListIterator<KNotification*> iter(m_notifications); QMutableMapIterator<KNotification*,KCrashDetails> iter(m_notifications);
while (iter.hasNext()) { while (iter.hasNext()) {
KNotification* knotification = iter.next(); iter.next();
KNotification* knotification = iter.key();
disconnect(knotification, 0, this, 0); disconnect(knotification, 0, this, 0);
knotification->close(); knotification->close();
iter.remove(); iter.remove();
} }
kDebug() << "Closing dialogs" << m_dialogs.size();
qDeleteAll(m_dialogs);
m_dialogs.clear();
} }
void KCrashModule::slotDirty(const QString &path) void KCrashModule::slotDirty(const QString &path)
@ -100,21 +165,33 @@ void KCrashModule::slotDirty(const QString &path)
kcrashappname = kcrashdata["appname"]; kcrashappname = kcrashdata["appname"];
} }
if (kcrashflags & KCrash::Notify) { 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; kDebug() << "Sending notification for" << kcrashfilepath;
// NOTE: when the notification is closed/deleted the actions become non-operational // NOTE: when the notification is closed/deleted the actions become non-operational
KNotification* knotification = new KNotification(this); KNotification* knotification = new KNotification(this);
knotification->setEventID("kcrash/Crash"); knotification->setEventID("kcrash/Crash");
knotification->setFlags(KNotification::Persistent); knotification->setFlags(KNotification::Persistent);
knotification->setTitle(i18n("%1 crashed", kcrashappname)); knotification->setTitle(i18n("%1 crashed", kcrashappname));
knotification->setText(i18n("For details about the crash look into the system log.")); knotification->setText(i18n("To view details about the crash and report it click on the details button."));
knotification->setActions(QStringList() << i18n("Report")); knotification->setActions(QStringList() << i18n("Details"));
m_notifications.append(knotification); m_notifications.insert(knotification, kcrashdetails);
connect(knotification, SIGNAL(closed()), this, SLOT(slotClosed())); connect(knotification, SIGNAL(closed()), this, SLOT(slotClosed()));
connect(knotification, SIGNAL(action1Activated()), this, SLOT(slotReport())); connect(knotification, SIGNAL(action1Activated()), this, SLOT(slotDetails()));
knotification->send(); knotification->send();
} }
if (kcrashflags & KCrash::Log) { 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; kError() << kcrashappname << "crashed" << kcrashbacktrace;
} }
if (kcrashflags & KCrash::AutoRestart) { if (kcrashflags & KCrash::AutoRestart) {
@ -135,15 +212,37 @@ void KCrashModule::slotClosed()
{ {
KNotification* knotification = qobject_cast<KNotification*>(sender()); KNotification* knotification = qobject_cast<KNotification*>(sender());
kDebug() << "Notification closed" << knotification; kDebug() << "Notification closed" << knotification;
m_notifications.removeAll(knotification); m_notifications.remove(knotification);
} }
void KCrashModule::slotReport() void KCrashModule::slotDetails()
{ {
KNotification* knotification = qobject_cast<KNotification*>(sender()); KNotification* knotification = qobject_cast<KNotification*>(sender());
kDebug() << "Notification details will be shown" << knotification;
const KCrashDetails kcrashdetails = m_notifications.value(knotification);
knotification->close(); knotification->close();
const QString kcrashreporturl = KDE_BUG_REPORT_URL; KCrashDialog* kcrashdialog = new KCrashDialog(kcrashdetails);
KToolInvocation::invokeBrowser(kcrashreporturl); 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<KCrashDialog*>(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" #include "moc_kded_kcrash.cpp"

View file

@ -23,6 +23,34 @@
#include <kdirwatch.h> #include <kdirwatch.h>
#include <knotification.h> #include <knotification.h>
#include <kdialog.h>
#include <kvbox.h>
#include <ktextedit.h>
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 class KCrashModule: public KDEDModule
{ {
@ -36,12 +64,14 @@ public:
private Q_SLOTS: private Q_SLOTS:
void slotDirty(const QString &path); void slotDirty(const QString &path);
void slotClosed(); void slotClosed();
void slotReport(); void slotDetails();
void slotDialogFinished(const int result);
private: private:
QString m_kcrashpath; QString m_kcrashpath;
KDirWatch *m_dirwatch; KDirWatch *m_dirwatch;
QList<KNotification*> m_notifications; QMap<KNotification*,KCrashDetails> m_notifications;
QList<KCrashDialog*> m_dialogs;
}; };
#endif // KCRASH_KDED_H #endif // KCRASH_KDED_H