File indexing completed on 2024-04-21 04:56:51
0001 /** 0002 * SPDX-FileCopyrightText: 2013 Albert Vaca <albertvaka@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "notification.h" 0008 #include "plugin_notifications_debug.h" 0009 0010 #include <KLocalizedString> 0011 #include <KNotification> 0012 #include <KNotificationReplyAction> 0013 #include <QFile> 0014 #include <QIcon> 0015 #include <QJsonArray> 0016 #include <QPixmap> 0017 #include <QString> 0018 #include <QUrl> 0019 #include <QtGlobal> 0020 #include <knotifications_version.h> 0021 0022 #include <core/filetransferjob.h> 0023 #include <core/notificationserverinfo.h> 0024 0025 QMap<QString, FileTransferJob *> Notification::s_downloadsInProgress; 0026 0027 Notification::Notification(const NetworkPacket &np, const Device *device, QObject *parent) 0028 : QObject(parent) 0029 , m_imagesDir() 0030 , m_device(device) 0031 { 0032 // Make a own directory for each user so no one can see each others icons 0033 QString username; 0034 #ifdef Q_OS_WIN 0035 username = QString::fromLatin1(qgetenv("USERNAME")); 0036 #else 0037 username = QString::fromLatin1(qgetenv("USER")); 0038 #endif 0039 0040 m_imagesDir.setPath(QDir::temp().absoluteFilePath(QStringLiteral("kdeconnect_") + username)); 0041 m_imagesDir.mkpath(m_imagesDir.absolutePath()); 0042 QFile(m_imagesDir.absolutePath()).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner); 0043 m_ready = false; 0044 0045 parseNetworkPacket(np); 0046 createKNotification(np); 0047 0048 #if QT_VERSION_MAJOR == 5 0049 connect(m_notification, &KNotification::activated, this, [this](unsigned int actionIndex) { 0050 // Notification action indices start at 1 0051 Q_EMIT actionTriggered(m_internalId, m_actions[actionIndex - 1]); 0052 }); 0053 #endif 0054 } 0055 0056 void Notification::dismiss() 0057 { 0058 if (m_dismissable) { 0059 Q_EMIT dismissRequested(m_internalId); 0060 } 0061 } 0062 0063 void Notification::show() 0064 { 0065 m_ready = true; 0066 Q_EMIT ready(); 0067 if (!m_silent) { 0068 m_notification->sendEvent(); 0069 } 0070 } 0071 0072 void Notification::update(const NetworkPacket &np) 0073 { 0074 parseNetworkPacket(np); 0075 createKNotification(np); 0076 } 0077 0078 void Notification::createKNotification(const NetworkPacket &np) 0079 { 0080 if (!m_notification) { 0081 m_notification = new KNotification(QStringLiteral("notification"), KNotification::CloseOnTimeout, this); 0082 m_notification->setComponentName(QStringLiteral("kdeconnect")); 0083 m_notification->setHint(QStringLiteral("resident"), 0084 true); // This means the notification won't be deleted automatically, but only with KNotifications 5.81 0085 } 0086 0087 QString escapedTitle = m_title.toHtmlEscaped(); 0088 // notification title text does not have markup, but in some cases below it is used in body text so we escape it 0089 QString escapedText = m_text.toHtmlEscaped(); 0090 QString escapedTicker = m_ticker.toHtmlEscaped(); 0091 0092 if (NotificationServerInfo::instance().supportedHints().testFlag(NotificationServerInfo::X_KDE_DISPLAY_APPNAME)) { 0093 m_notification->setTitle(m_title); 0094 m_notification->setText(escapedText); 0095 m_notification->setHint(QStringLiteral("x-kde-display-appname"), m_appName.toHtmlEscaped()); 0096 } else { 0097 m_notification->setTitle(m_appName); 0098 0099 if (m_title.isEmpty() && m_text.isEmpty()) { 0100 m_notification->setText(escapedTicker); 0101 } else if (m_appName == m_title) { 0102 m_notification->setText(escapedText); 0103 } else if (m_title.isEmpty()) { 0104 m_notification->setText(escapedText); 0105 } else if (m_text.isEmpty()) { 0106 m_notification->setText(escapedTitle); 0107 } else { 0108 m_notification->setText(escapedTitle + QStringLiteral(": ") + escapedText); 0109 } 0110 } 0111 0112 m_notification->setHint(QStringLiteral("x-kde-origin-name"), m_device->name()); 0113 0114 if (!m_requestReplyId.isEmpty()) { 0115 auto replyAction = std::make_unique<KNotificationReplyAction>(i18nc("@action:button", "Reply")); 0116 replyAction->setPlaceholderText(i18nc("@info:placeholder", "Reply to %1...", m_appName)); 0117 replyAction->setFallbackBehavior(KNotificationReplyAction::FallbackBehavior::UseRegularAction); 0118 QObject::connect(replyAction.get(), &KNotificationReplyAction::replied, this, &Notification::replied); 0119 QObject::connect(replyAction.get(), &KNotificationReplyAction::activated, this, &Notification::reply); 0120 m_notification->setReplyAction(std::move(replyAction)); 0121 } 0122 0123 #if QT_VERSION_MAJOR == 6 0124 m_notification->clearActions(); 0125 for (const QString &actionId : std::as_const(m_actions)) { 0126 KNotificationAction *action = m_notification->addAction(actionId); 0127 0128 connect(action, &KNotificationAction::activated, this, [this, actionId] { 0129 Q_EMIT actionTriggered(m_internalId, actionId); 0130 }); 0131 } 0132 #else 0133 m_notification->setActions(m_actions); 0134 #endif 0135 0136 m_hasIcon = m_hasIcon && !m_payloadHash.isEmpty(); 0137 0138 if (!m_hasIcon) { 0139 show(); 0140 } else { 0141 m_iconPath = m_imagesDir.absoluteFilePath(m_payloadHash); 0142 loadIcon(np); 0143 } 0144 } 0145 0146 void Notification::loadIcon(const NetworkPacket &np) 0147 { 0148 m_ready = false; 0149 0150 if (QFileInfo::exists(m_iconPath)) { 0151 applyIcon(); 0152 show(); 0153 } else { 0154 FileTransferJob *fileTransferJob = s_downloadsInProgress.value(m_iconPath); 0155 if (!fileTransferJob) { 0156 fileTransferJob = np.createPayloadTransferJob(QUrl::fromLocalFile(m_iconPath)); 0157 fileTransferJob->start(); 0158 s_downloadsInProgress[m_iconPath] = fileTransferJob; 0159 } 0160 0161 connect(fileTransferJob, &FileTransferJob::result, this, [this, fileTransferJob] { 0162 s_downloadsInProgress.remove(m_iconPath); 0163 if (fileTransferJob->error()) { 0164 qCDebug(KDECONNECT_PLUGIN_NOTIFICATIONS) << "Error in FileTransferJob: " << fileTransferJob->errorString(); 0165 } else { 0166 applyIcon(); 0167 } 0168 show(); 0169 }); 0170 } 0171 } 0172 0173 void Notification::applyIcon() 0174 { 0175 QPixmap icon(m_iconPath, "PNG"); 0176 m_notification->setPixmap(icon); 0177 } 0178 0179 void Notification::reply() 0180 { 0181 Q_EMIT replyRequested(); 0182 } 0183 0184 void Notification::sendReply(const QString &message) 0185 { 0186 Q_EMIT replied(message); 0187 } 0188 0189 void Notification::parseNetworkPacket(const NetworkPacket &np) 0190 { 0191 m_internalId = np.get<QString>(QStringLiteral("id")); 0192 m_appName = np.get<QString>(QStringLiteral("appName")); 0193 m_ticker = np.get<QString>(QStringLiteral("ticker")); 0194 m_title = np.get<QString>(QStringLiteral("title")); 0195 m_text = np.get<QString>(QStringLiteral("text")); 0196 m_dismissable = np.get<bool>(QStringLiteral("isClearable")); 0197 m_hasIcon = np.hasPayload(); 0198 m_silent = np.get<bool>(QStringLiteral("silent")); 0199 m_payloadHash = np.get<QString>(QStringLiteral("payloadHash")); 0200 m_requestReplyId = np.get<QString>(QStringLiteral("requestReplyId"), QString()); 0201 0202 m_actions.clear(); 0203 0204 const auto actions = np.get<QJsonArray>(QStringLiteral("actions")); 0205 for (const QJsonValue &value : actions) { 0206 m_actions.append(value.toString()); 0207 } 0208 } 0209 0210 #include "moc_notification.cpp"