File indexing completed on 2024-04-14 05:20:29

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