File indexing completed on 2024-04-21 16:12:22

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