File indexing completed on 2024-04-14 15:32:47

0001 /*******************************************************************
0002  * drkonqidialog.cpp
0003  * SPDX-FileCopyrightText: 2009 Dario Andres Rodriguez <andresbajotierra@gmail.com>
0004  * SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  *
0008  ******************************************************************/
0009 
0010 #include "drkonqidialog.h"
0011 
0012 #include <KLocalizedString>
0013 #include <KWindowConfig>
0014 
0015 #include "drkonqi_debug.h"
0016 #include <QDesktopServices>
0017 #include <QDialogButtonBox>
0018 #include <QLocale>
0019 #include <QMenu>
0020 #include <QQmlApplicationEngine>
0021 #include <QQmlContext>
0022 #include <QStandardPaths>
0023 #include <QTabBar>
0024 #include <QTabWidget>
0025 #include <QtQml>
0026 
0027 #include "backtracegenerator.h"
0028 #include "backtraceparser.h"
0029 #include "backtracewidget.h"
0030 #include "config-drkonqi.h"
0031 #include "crashedapplication.h"
0032 #include "debuggerlaunchers.h"
0033 #include "debuggermanager.h"
0034 #include "drkonqi.h"
0035 #include "drkonqi_globals.h"
0036 #include "qmlextensions/commentmodel.h"
0037 #include "qmlextensions/credentialstore.h"
0038 #include "qmlextensions/doctore.h"
0039 #include "qmlextensions/duplicatemodel.h"
0040 #include "qmlextensions/platformmodel.h"
0041 #include "qmlextensions/reproducibilitymodel.h"
0042 #include "systeminformation.h"
0043 
0044 static const QString ABOUT_BUG_REPORTING_URL = QStringLiteral("https://community.kde.org/Get_Involved/Issue_Reporting");
0045 static const QString DRKONQI_REPORT_BUG_URL = KDE_BUGZILLA_URL + QStringLiteral("enter_bug.cgi?product=drkonqi&format=guided");
0046 
0047 void DrKonqiDialog::show()
0048 {
0049     if (DrKonqi::isSafer()) {
0050         QDialog::show();
0051         return;
0052     }
0053 
0054     auto engine = new QQmlApplicationEngine(this);
0055 
0056     static auto l10nContext = new KLocalizedContext(engine);
0057     l10nContext->setTranslationDomain(QStringLiteral(TRANSLATION_DOMAIN));
0058     engine->rootContext()->setContextObject(l10nContext);
0059 
0060     qmlRegisterType<BugzillaManager>("org.kde.drkonqi", 1, 0, "Bugzilla");
0061     qmlRegisterType<DuplicateModel>("org.kde.drkonqi", 1, 0, "DuplicateModel");
0062     qmlRegisterType<PlatformModel>("org.kde.drkonqi", 1, 0, "PlatformModel");
0063     qmlRegisterType<ReproducibilityModel>("org.kde.drkonqi", 1, 0, "ReproducibilityModel");
0064     qmlRegisterType<ReportInterface>("org.kde.drkonqi", 1, 0, "ReportInterface");
0065     qmlRegisterType<CredentialStore>("org.kde.drkonqi", 1, 0, "CredentialStore");
0066     qmlRegisterType<DebugPackageInstaller>("org.kde.drkonqi", 1, 0, "DebugPackageInstaller");
0067 
0068     qmlRegisterSingletonInstance("org.kde.drkonqi", 1, 0, "CrashedApplication", DrKonqi::crashedApplication());
0069     qmlRegisterSingletonInstance("org.kde.drkonqi", 1, 0, "BacktraceGenerator", DrKonqi::debuggerManager()->backtraceGenerator());
0070 
0071     static Doctore doctore;
0072     qmlRegisterSingletonInstance("org.kde.drkonqi", 1, 0, "DrKonqi", &doctore);
0073 
0074     // TODO do we need this second BG?
0075     qmlRegisterUncreatableType<BacktraceGenerator>("org.kde.drkonqi", 1, 0, "BacktraceGenerator1", QStringLiteral("Cannot create WarningLevel in QML"));
0076     qmlRegisterUncreatableType<BacktraceParser>("org.kde.drkonqi", 1, 0, "BacktraceParser", QStringLiteral("Cannot create WarningLevel in QML"));
0077 
0078     const QUrl mainUrl(QStringLiteral("qrc:/ui/main.qml"));
0079     QObject::connect(
0080         engine,
0081         &QQmlApplicationEngine::objectCreated,
0082         this,
0083         [mainUrl, this](QObject *obj, const QUrl &objUrl) {
0084             if (!obj && mainUrl == objUrl) {
0085                 qWarning() << "Failed to load QML dialog, falling back to QWidget.";
0086                 QDialog::show();
0087             }
0088         },
0089         Qt::QueuedConnection);
0090     engine->load(mainUrl);
0091 }
0092 
0093 DrKonqiDialog::DrKonqiDialog(QWidget *parent)
0094     : QDialog(parent)
0095 {
0096     setAttribute(Qt::WA_DeleteOnClose, true);
0097     QGuiApplication::setQuitOnLastWindowClosed(true);
0098 
0099     // Setting dialog title and icon
0100     setWindowTitle(DrKonqi::crashedApplication()->name());
0101     setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug")));
0102 
0103     auto *l = new QVBoxLayout(this);
0104     m_tabWidget = new QTabWidget(this);
0105     l->addWidget(m_tabWidget);
0106     m_buttonBox = new QDialogButtonBox(this);
0107     connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accepted);
0108     connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::rejected);
0109     l->addWidget(m_buttonBox);
0110 
0111     connect(m_tabWidget, &QTabWidget::currentChanged, this, &DrKonqiDialog::tabIndexChanged);
0112 
0113     KConfigGroup config(KSharedConfig::openConfig(), "General");
0114 
0115     if (!config.readEntry(QStringLiteral("ShowOnlyBacktrace"), false)) {
0116         buildIntroWidget();
0117         m_tabWidget->addTab(m_introWidget, i18nc("@title:tab general information", "&General"));
0118     }
0119 
0120     m_backtraceWidget = new BacktraceWidget(DrKonqi::debuggerManager()->backtraceGenerator(), this);
0121     m_tabWidget->addTab(m_backtraceWidget, i18nc("@title:tab", "&Developer Information"));
0122 
0123     m_tabWidget->tabBar()->setVisible(m_tabWidget->count() > 1);
0124 
0125     buildDialogButtons();
0126 
0127     KWindowConfig::restoreWindowSize(windowHandle(), config);
0128 
0129     // Force a 16:9 ratio for nice appearance by default.
0130     QSize aspect(16, 9);
0131     aspect.scale(sizeHint(), Qt::KeepAspectRatioByExpanding);
0132     resize(aspect);
0133 }
0134 
0135 DrKonqiDialog::~DrKonqiDialog()
0136 {
0137     KConfigGroup config(KSharedConfig::openConfig(), "General");
0138     KWindowConfig::saveWindowSize(windowHandle(), config);
0139 }
0140 
0141 void DrKonqiDialog::tabIndexChanged(int index)
0142 {
0143     if (index == m_tabWidget->indexOf(m_backtraceWidget)) {
0144         m_backtraceWidget->generateBacktrace();
0145     }
0146 }
0147 
0148 void DrKonqiDialog::buildIntroWidget()
0149 {
0150     const CrashedApplication *crashedApp = DrKonqi::crashedApplication();
0151 
0152     m_introWidget = new QWidget(this);
0153     ui.setupUi(m_introWidget);
0154 
0155     ui.titleLabel->setText(xi18nc("@info",
0156                                   "<para>We are sorry, <application>%1</application> "
0157                                   "closed unexpectedly.</para>",
0158                                   crashedApp->name()));
0159 
0160     QString reportMessage;
0161     if (!crashedApp->bugReportAddress().isEmpty()) {
0162         if (crashedApp->fakeExecutableBaseName() == QLatin1String("drkonqi")) { // Handle own crashes
0163             reportMessage = xi18nc("@info",
0164                                    "<para>As the Crash Handler itself has failed, the "
0165                                    "automatic reporting process is disabled to reduce the "
0166                                    "risks of failing again.<nl /><nl />"
0167                                    "Please, <link url='%1'>manually report</link> this error "
0168                                    "to the KDE bug tracking system. Do not forget to include "
0169                                    "the backtrace from the <interface>Developer Information</interface> "
0170                                    "tab.</para>",
0171                                    DRKONQI_REPORT_BUG_URL);
0172         } else if (DrKonqi::isSafer()) {
0173             reportMessage = xi18nc("@info",
0174                                    "<para>The reporting assistant is disabled because "
0175                                    "the crash handler dialog was started in safe mode."
0176                                    "<nl />You can manually report this bug to %1 "
0177                                    "(including the backtrace from the "
0178                                    "<interface>Developer Information</interface> "
0179                                    "tab.)</para>",
0180                                    crashedApp->bugReportAddress());
0181         } else if (crashedApp->hasDeletedFiles()) {
0182             reportMessage = xi18nc("@info",
0183                                    "<para>The reporting assistant is disabled because "
0184                                    "the crashed application appears to have been updated or "
0185                                    "uninstalled since it had been started. This prevents accurate "
0186                                    "crash reporting and can also be the cause of this crash.</para>"
0187                                    "<para>After updating it is always a good idea to log out and back "
0188                                    "in to make sure the update is fully applied and will not cause "
0189                                    "any side effects.</para>");
0190         } else {
0191             reportMessage = xi18nc("@info",
0192                                    "<para>You can help us improve KDE Software by reporting "
0193                                    "this error.<nl /><link url='%1'>Learn "
0194                                    "more about bug reporting.</link></para>",
0195                                    ABOUT_BUG_REPORTING_URL);
0196         }
0197     } else {
0198         reportMessage = xi18nc("@info",
0199                                "<para>You cannot report this error, because "
0200                                "<application>%1</application> does not provide a bug reporting "
0201                                "address.</para>",
0202                                crashedApp->name());
0203     }
0204     ui.infoLabel->setText(reportMessage);
0205     connect(ui.infoLabel, &QLabel::linkActivated, this, &DrKonqiDialog::linkActivated);
0206 
0207     ui.detailsTitleLabel->setText(QStringLiteral("<strong>%1</strong>").arg(i18nc("@label", "Details:")));
0208 
0209     QLocale locale;
0210     ui.detailsLabel->setText(xi18nc("@info Note the time information is divided into date and time parts",
0211                                     "<para>Executable: <application>%1"
0212                                     "</application> PID: %2 Signal: %3 (%4) "
0213                                     "Time: %5 %6</para>",
0214                                     crashedApp->fakeExecutableBaseName(),
0215                                     // prevent number localization by ki18n
0216                                     QString::number(crashedApp->pid()),
0217                                     crashedApp->signalName(),
0218 #if defined(Q_OS_UNIX)
0219                                     // prevent number localization by ki18n
0220                                     QString::number(crashedApp->signalNumber()),
0221 #else
0222                                     // windows uses weird big numbers for exception codes,
0223                                     // so it doesn't make sense to display them in decimal
0224                                     QString().asprintf("0x%8x", crashedApp->signalNumber()),
0225 #endif
0226                                     locale.toString(crashedApp->datetime().date(), QLocale::ShortFormat),
0227 
0228                                     locale.toString(crashedApp->datetime().time())));
0229 }
0230 
0231 void DrKonqiDialog::buildDialogButtons()
0232 {
0233     const CrashedApplication *crashedApp = DrKonqi::crashedApplication();
0234 
0235     // Set dialog buttons
0236     m_buttonBox->setStandardButtons(QDialogButtonBox::Close);
0237 
0238     // Default debugger button and menu (only for developer mode): User2
0239     DebuggerManager *debuggerManager = DrKonqi::debuggerManager();
0240     m_debugButton = new QPushButton(this);
0241     KGuiItem2 debugItem(i18nc("@action:button this is the debug menu button label which contains the debugging applications", "&Debug"),
0242                         QIcon::fromTheme(QStringLiteral("applications-development")),
0243                         i18nc("@info:tooltip", "Starts a program to debug the crashed application."));
0244     KGuiItem::assign(m_debugButton, debugItem);
0245     m_debugButton->setVisible(false);
0246 
0247     m_debugMenu = new QMenu(this);
0248     m_debugButton->setMenu(m_debugMenu);
0249 
0250     // Only add the debugger if requested by the config or if a KDevelop session is running.
0251     const bool showExternal = debuggerManager->showExternalDebuggers();
0252     QList<AbstractDebuggerLauncher *> debuggers = debuggerManager->availableExternalDebuggers();
0253     for (AbstractDebuggerLauncher *launcher : std::as_const(debuggers)) {
0254         if (showExternal || qobject_cast<DBusInterfaceLauncher *>(launcher)) {
0255             addDebugger(launcher);
0256         }
0257     }
0258 
0259     connect(debuggerManager, &DebuggerManager::externalDebuggerAdded, this, &DrKonqiDialog::addDebugger);
0260     connect(debuggerManager, &DebuggerManager::externalDebuggerRemoved, this, &DrKonqiDialog::removeDebugger);
0261     connect(debuggerManager, &DebuggerManager::debuggerRunning, this, &DrKonqiDialog::enableDebugMenu);
0262 
0263     // Restart application button
0264     m_restartButton = new QPushButton(m_buttonBox);
0265     KGuiItem::assign(m_restartButton, DrStandardGuiItem::appRestart());
0266     m_restartButton->setEnabled(!crashedApp->hasBeenRestarted() && crashedApp->fakeExecutableBaseName() != QLatin1String("drkonqi"));
0267     m_buttonBox->addButton(m_restartButton, QDialogButtonBox::ActionRole);
0268     connect(m_restartButton, &QAbstractButton::clicked, crashedApp, &CrashedApplication::restart);
0269     connect(crashedApp, &CrashedApplication::restarted, this, &DrKonqiDialog::applicationRestarted);
0270 
0271     // Close button
0272     QString tooltipText = i18nc("@info:tooltip", "Close this dialog (you will lose the crash information.)");
0273     m_buttonBox->button(QDialogButtonBox::Close)->setToolTip(tooltipText);
0274     m_buttonBox->button(QDialogButtonBox::Close)->setWhatsThis(tooltipText);
0275     m_buttonBox->button(QDialogButtonBox::Close)->setFocus();
0276 }
0277 
0278 void DrKonqiDialog::addDebugger(AbstractDebuggerLauncher *launcher)
0279 {
0280     auto *action = new QAction(QIcon::fromTheme(QStringLiteral("applications-development")),
0281                                i18nc("@action:inmenu 1 is the debugger name", "Debug in %1", launcher->name()),
0282                                m_debugMenu);
0283     m_debugMenu->addAction(action);
0284     connect(action, &QAction::triggered, launcher, &AbstractDebuggerLauncher::start);
0285     m_debugMenuActions.insert(launcher, action);
0286 
0287     // Make sure that the debug button is the first button with action role to be
0288     // inserted, then add the other buttons. See removeDebugger below for more information.
0289     if (!m_debugButtonInBox) {
0290         auto buttons = m_buttonBox->buttons();
0291         m_buttonBox->addButton(m_debugButton, QDialogButtonBox::ActionRole);
0292         m_debugButton->setVisible(true);
0293         for (QAbstractButton *button : buttons) {
0294             if (m_buttonBox->buttonRole(button) == QDialogButtonBox::ActionRole) {
0295                 m_buttonBox->addButton(button, QDialogButtonBox::ActionRole);
0296             }
0297         }
0298         m_debugButtonInBox = true;
0299     }
0300 }
0301 
0302 void DrKonqiDialog::removeDebugger(AbstractDebuggerLauncher *launcher)
0303 {
0304     QAction *action = m_debugMenuActions.take(launcher);
0305     if (action) {
0306         m_debugMenu->removeAction(action);
0307         action->deleteLater();
0308         // Remove the button from the box, otherwise the box will force
0309         // it visible as it calls show() explicitly. (QTBUG-3651)
0310         if (m_debugMenu->isEmpty()) {
0311             m_buttonBox->removeButton(m_debugButton);
0312             m_debugButton->setVisible(false);
0313             m_debugButton->setParent(this);
0314             m_debugButtonInBox = false;
0315         }
0316     } else {
0317         qCWarning(DRKONQI_LOG) << "Invalid launcher";
0318     }
0319 }
0320 
0321 void DrKonqiDialog::enableDebugMenu(bool debuggerRunning)
0322 {
0323     m_debugButton->setEnabled(!debuggerRunning);
0324 }
0325 
0326 void DrKonqiDialog::linkActivated(const QString &link)
0327 {
0328     if (link == DRKONQI_REPORT_BUG_URL || link == ABOUT_BUG_REPORTING_URL) {
0329         QDesktopServices::openUrl(QUrl(link));
0330     } else if (link.startsWith(QLatin1String("http"))) {
0331         qCWarning(DRKONQI_LOG) << "unexpected link";
0332         QDesktopServices::openUrl(QUrl(link));
0333     }
0334 }
0335 
0336 void DrKonqiDialog::applicationRestarted(bool success)
0337 {
0338     m_restartButton->setEnabled(!success);
0339 }
0340 
0341 #include "drkonqidialog.moc"