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"