File indexing completed on 2022-11-29 20:01:36

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