File indexing completed on 2024-03-24 05:29:43
0001 /* 0002 SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de> 0003 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "statusnotifier.h" 0009 0010 #include <QAction> 0011 #include <QDBusConnectionInterface> 0012 #include <QDBusServiceWatcher> 0013 #include <QMenu> 0014 0015 #include <KLocalizedString> 0016 #include <KNotification> 0017 #include <KStatusNotifierItem> 0018 0019 #include "crashedapplication.h" 0020 #include "drkonqi.h" 0021 #include "statusnotifier_activationclosetimer.h" 0022 0023 namespace 0024 { 0025 QString activationMessage(StatusNotifier::Activation activation) 0026 { 0027 switch (activation) { 0028 case StatusNotifier::Activation::NotAllowed: 0029 return i18nc("Notification text", "The application closed unexpectedly."); 0030 case StatusNotifier::Activation::Allowed: 0031 return i18nc("Notification text", "Please report this error to help improve this software."); 0032 case StatusNotifier::Activation::AlreadySubmitting: 0033 return i18nc("Notification text", "The application closed unexpectedly. A report is being automatically submitted."); 0034 } 0035 Q_ASSERT(false); 0036 return {}; 0037 } 0038 } // namespace 0039 0040 StatusNotifier::StatusNotifier(QObject *parent) 0041 : QObject(parent) 0042 , m_sni(new KStatusNotifierItem(this)) 0043 { 0044 CrashedApplication *crashedApp = DrKonqi::crashedApplication(); 0045 0046 // this is used for both the SNI tooltip as well as the notification 0047 m_title = i18nc("Placeholder is an application name; it crashed", "%1 Closed Unexpectedly", crashedApp->name()); 0048 } 0049 0050 StatusNotifier::~StatusNotifier() = default; 0051 0052 void StatusNotifier::show() 0053 { 0054 CrashedApplication *crashedApp = DrKonqi::crashedApplication(); 0055 0056 connect(this, &StatusNotifier::activated, this, &StatusNotifier::deleteLater); 0057 // The expiring the notifier doesn't necessarily quit immediately, make sure to hide the SNI by deleting it (there 0058 // is no way to hide an SNI). 0059 connect(this, &StatusNotifier::expired, m_sni, &QObject::deleteLater); 0060 0061 m_sni->setTitle(m_title); 0062 m_sni->setIconByName(QStringLiteral("tools-report-bug")); 0063 m_sni->setStatus(KStatusNotifierItem::Active); 0064 m_sni->setCategory(KStatusNotifierItem::SystemServices); 0065 m_sni->setToolTip(m_sni->iconName(), m_sni->title(), i18n("Please report this error to help improve this software.")); 0066 connect(m_sni, &KStatusNotifierItem::activateRequested, this, &StatusNotifier::activated); 0067 0068 // you cannot turn off that "Do you really want to quit?" message, so we'll add our own below 0069 m_sni->setStandardActionsEnabled(false); 0070 0071 auto *sniMenu = new QMenu(); 0072 auto *action = new QAction(QIcon::fromTheme(QStringLiteral("tools-report-bug")), i18n("Report &Bug"), nullptr); 0073 connect(action, &QAction::triggered, this, &StatusNotifier::activated); 0074 sniMenu->addAction(action); 0075 sniMenu->setDefaultAction(action); 0076 0077 if (canBeRestarted(crashedApp)) { 0078 action = new QAction(QIcon::fromTheme(QStringLiteral("system-reboot")), i18n("&Restart Application"), nullptr); 0079 connect(action, &QAction::triggered, crashedApp, &CrashedApplication::restart); 0080 // once restarted successfully, disable restart option 0081 connect(crashedApp, &CrashedApplication::restarted, action, [action](bool success) { 0082 action->setEnabled(!success); 0083 }); 0084 sniMenu->addAction(action); 0085 } 0086 0087 sniMenu->addSeparator(); 0088 0089 action = new QAction(QIcon::fromTheme(QStringLiteral("application-exit")), i18nc("Allows the user to hide this notifier item", "Hide"), nullptr); 0090 connect(action, &QAction::triggered, this, &StatusNotifier::expired); 0091 sniMenu->addAction(action); 0092 0093 m_sni->setContextMenu(sniMenu); 0094 0095 // Depending on the environmental state we may auto-activate or auto-close the SNI. 0096 auto timer = new ActivationCloseTimer(this); 0097 connect(timer, &ActivationCloseTimer::autoActivate, this, &StatusNotifier::activated); 0098 connect(timer, &ActivationCloseTimer::autoClose, this, &StatusNotifier::expired); 0099 auto dbusWatcher = new DBusServiceWatcher(timer); 0100 auto idleWatcher = new IdleWatcher(timer); 0101 timer->start(dbusWatcher, idleWatcher); 0102 } 0103 0104 void StatusNotifier::notify(Activation activation) 0105 { 0106 CrashedApplication *crashedApp = DrKonqi::crashedApplication(); 0107 0108 const auto activationAllowed = activation != Activation::NotAllowed; 0109 const QString title = activationAllowed ? m_title : crashedApp->name(); 0110 const QString message = activationMessage(activation); 0111 0112 KNotification *notification = KNotification::event(QStringLiteral("applicationcrash"), 0113 title, 0114 message, 0115 QStringLiteral("tools-report-bug"), 0116 KNotification::DefaultEvent | KNotification::SkipGrouping); 0117 0118 if (activationAllowed) { 0119 if (activation == StatusNotifier::Activation::AlreadySubmitting) { 0120 auto details = notification->addAction(i18nc("@action:button, keep short", "Add Details")); 0121 connect(details, &KNotificationAction::activated, this, &StatusNotifier::sentryActivated); 0122 } else { 0123 auto action = notification->addAction(i18nc("Notification action button, keep short", "Report Bug")); 0124 connect(action, &KNotificationAction::activated, this, &StatusNotifier::activated); 0125 } 0126 } 0127 if (canBeRestarted(crashedApp)) { 0128 auto action = notification->addAction(i18nc("Notification action button, keep short", "Restart App")); 0129 connect(action, &KNotificationAction::activated, this, [crashedApp]() { 0130 if (canBeRestarted(crashedApp)) { 0131 crashedApp->restart(); 0132 } 0133 }); 0134 } 0135 0136 // when the SNI disappears you won't be able to interact with the notification anymore anyway, so close it 0137 if (activationAllowed) { 0138 connect(this, &StatusNotifier::activated, notification, &KNotification::close); 0139 connect(this, &StatusNotifier::expired, notification, &KNotification::close); 0140 } else { 0141 // No SNI means we should quit when the notification is gone 0142 connect(notification, &KNotification::closed, this, &StatusNotifier::expired); 0143 } 0144 } 0145 0146 bool StatusNotifier::notificationServiceRegistered() 0147 { 0148 return QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.freedesktop.Notifications")); 0149 } 0150 0151 bool StatusNotifier::canBeRestarted(CrashedApplication *app) 0152 { 0153 return !app->hasBeenRestarted() && app->fakeExecutableBaseName() != QLatin1String("drkonqi"); 0154 } 0155 0156 #include "moc_statusnotifier.cpp"