/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Authors: iaom */ #include "popup-notification.h" #include #include #include namespace UkuiNotification { class PopupNotificationPrivate { public: PopupNotificationPrivate(); ~PopupNotificationPrivate(); QImage parseImageHint(const QDBusArgument &arg); void loadImageFromPath(const QString &path); QSize maximumImageSize(); uint m_id = 0; QString m_applicationName; QString m_applicationIconName; QString m_summary; QString m_body; QList> m_actions; bool m_hasDefaultAction = false; QString m_defaultActionLabel; QVariantMap m_hints = QVariantMap(); QString m_category; QImage m_image; QString m_icon; QDateTime m_createdTime; QString m_desktopEntry; PopupNotification::Urgency m_urgency = PopupNotification::NormalUrgency; int m_timeout = -1; bool m_resident = false; bool m_transient = false; QString m_soundFile; QString m_soundName; bool m_suppressSound = false; QString m_display; QStringList m_actionState; bool m_noFold = false; int m_popupTimeout = 6000; //ms }; } using namespace UkuiNotification; PopupNotificationPrivate::PopupNotificationPrivate() { } PopupNotificationPrivate::~PopupNotificationPrivate() = default; QImage PopupNotificationPrivate::parseImageHint(const QDBusArgument &arg) { //copy from plasma-workspace int width, height, rowStride, hasAlpha, bitsPerSample, channels; QByteArray pixels; char *ptr; char *end; arg.beginStructure(); arg >> width >> height >> rowStride >> hasAlpha >> bitsPerSample >> channels >> pixels; arg.endStructure(); auto copyLineRGB32 = [](QRgb *dst, const char *src, int width) { const char *end = src + width * 3; for (; src != end; ++dst, src += 3) { *dst = qRgb(src[0], src[1], src[2]); } }; auto copyLineARGB32 = [](QRgb *dst, const char *src, int width) { const char *end = src + width * 4; for (; src != end; ++dst, src += 4) { *dst = qRgba(src[0], src[1], src[2], src[3]); } }; QImage::Format format = QImage::Format_Invalid; void (*fcn)(QRgb *, const char *, int) = nullptr; if (bitsPerSample == 8) { if (channels == 4) { format = QImage::Format_ARGB32; fcn = copyLineARGB32; } else if (channels == 3) { format = QImage::Format_RGB32; fcn = copyLineRGB32; } } if (format == QImage::Format_Invalid) { qWarning() << "Unsupported image format (hasAlpha:" << hasAlpha << "bitsPerSample:" << bitsPerSample << "channels:" << channels << ")"; return QImage(); } QImage image(width, height, format); ptr = pixels.data(); end = ptr + pixels.length(); for (int y = 0; y < height; ++y, ptr += rowStride) { if (ptr + channels * width > end) { qWarning() << "Image data is incomplete. y:" << y << "height:" << height; break; } fcn((QRgb *)image.scanLine(y), ptr, width); } return image; } void PopupNotificationPrivate::loadImageFromPath(const QString &path) { //copy from plasma-workspace QUrl imageUrl; if (path.startsWith(QLatin1Char('/'))) { imageUrl = QUrl::fromLocalFile(path); } else if (path.contains(QLatin1Char('/'))) { imageUrl = QUrl(path); if (!imageUrl.isLocalFile()) { qDebug() << "Refused to load image from" << path << "which isn't a valid local location."; return; } } if (!imageUrl.isValid()) { m_icon = path; return; } QImageReader reader(imageUrl.toLocalFile()); reader.setAutoTransform(true); const QSize imageSize = reader.size(); if (imageSize.isValid() && (imageSize.width() > maximumImageSize().width() || imageSize.height() > maximumImageSize().height())) { const QSize thumbnailSize = imageSize.scaled(maximumImageSize(), Qt::KeepAspectRatio); reader.setScaledSize(thumbnailSize); } m_image = reader.read(); } QSize PopupNotificationPrivate::maximumImageSize() { return QSize(256, 256); } PopupNotification::PopupNotification(uint id) : d(new PopupNotificationPrivate()) { d->m_id = id; } PopupNotification::PopupNotification(const PopupNotification &other) : d(new PopupNotificationPrivate(*other.d)) { } PopupNotification &PopupNotification::operator=( const PopupNotification &other) { *d = *other.d; return *this; } PopupNotification &PopupNotification::operator=(PopupNotification &&other) Q_DECL_NOEXCEPT { d = other.d; other.d = nullptr; return *this; } PopupNotification::~PopupNotification() { if(d) { delete d; d = nullptr; } } uint PopupNotification::id() const { return d->m_id; } QString PopupNotification::applicationName() const { return d->m_applicationName; } void PopupNotification::setApplicationName(const QString &applicationName) { d->m_applicationName = applicationName; } QString PopupNotification::applicationIconName() const { return d->m_applicationIconName; } void PopupNotification::setApplicationIconName(const QString &applicationIconName) { d->m_applicationIconName = applicationIconName; } QString PopupNotification::summary() const { return d->m_summary; } void PopupNotification::setSummary(const QString &summary) { d->m_summary = summary; } QString PopupNotification::body() const { return d->m_body; } void PopupNotification::setBody(const QString &body) { d->m_body = body; } bool PopupNotification::hasDefaultAction() const { return d->m_hasDefaultAction; } QString PopupNotification::defaultActionLabel() { return d->m_defaultActionLabel; } void PopupNotification::setActions(const QStringList &actions) { if (actions.count() % 2 != 0) { qWarning() << "List of actions must contain an even number of items, tried to set actions to" << actions; return; } d->m_hasDefaultAction = false; for (int i = 0; i < actions.count(); i += 2) { const QString &key = actions.at(i); const QString &label = actions.at(i + 1); if (!d->m_hasDefaultAction && key == QLatin1String("default")) { d->m_hasDefaultAction = true; d->m_defaultActionLabel = label; } d->m_actions.append({key, label}); } } QList> PopupNotification::actions() const { return d->m_actions; } QVariantMap PopupNotification::hints() const { return d->m_hints; } void PopupNotification::setHints(const QVariantMap &hints) { d->m_hints = hints; d->m_desktopEntry = hints.value(QStringLiteral("desktop-entry")).toString(); bool ok; int urgency = hints.value(QStringLiteral("urgency")).toInt(&ok); if(ok) { switch (urgency) { default: case 0: //低等级,不弹窗 setUrgency(Urgency::LowUrgency); d->m_popupTimeout = 0; break; case 1: //中等级,弹窗时间默认6秒 setUrgency(Urgency::NormalUrgency); d->m_popupTimeout = 6000; break; case 2: //高等级,默认弹窗常驻,不可折叠 setUrgency(Urgency::CriticalUrgency); d->m_popupTimeout = -1; d->m_noFold = true; break; } } d->m_createdTime = QDateTime::fromString(hints.value(QStringLiteral("x-ukui-createdTime")).toString()); d->m_resident = hints.value(QStringLiteral("resident")).toBool(); d->m_transient = hints.value(QStringLiteral("transient")).toBool(); d->m_category = hints.value(QStringLiteral("category")).toString(); auto end = hints.end(); auto it = hints.find(QStringLiteral("image-data")); if (it != end) { d->m_image = d->parseImageHint(it->value()); if (!d->m_image.isNull()) { const QSize max = d->maximumImageSize(); if (d->m_image.size().width() > max.width() || d->m_image.size().height() > max.height()) { d->m_image = d->m_image.scaled(max, Qt::KeepAspectRatio, Qt::SmoothTransformation); } } } if (d->m_image.isNull()) { it = hints.find(QStringLiteral("image-path")); if (it != end) { d->loadImageFromPath(it->toString()); } } d->m_soundFile = hints.value(QStringLiteral("sound-file")).toString(); d->m_soundName = hints.value(QStringLiteral("sound-name")).toString(); d->m_suppressSound = hints.value(QStringLiteral("suppress-sound")).toBool(); d->m_display = hints.value(QStringLiteral("x-ukui-display")).toString(); d->m_actionState = hints.value(QStringLiteral("x-ukui-action-state")).toStringList(); QVariant tmp = hints.value(QStringLiteral("x-ukui-no-fold")); if(!tmp.isNull()) { d->m_noFold = tmp.toBool(); } tmp = hints.value(QStringLiteral("x-ukui-popup-timeout")); if(!tmp.isNull()) { d->m_popupTimeout = tmp.toInt(); } } int PopupNotification::timeout() const { return d->m_timeout; } void PopupNotification::setTimeout(int timeout) { d->m_timeout = timeout; } QDateTime PopupNotification::createdTime() const { return d->m_createdTime; } bool PopupNotification::enableActionIcons() const { //TODO 可以支持action-icon return false; } QString PopupNotification::category() const { return d->m_category; } QString PopupNotification::desktopEntry() const { return d->m_desktopEntry; } QImage PopupNotification::image() const { return d->m_image; } QString PopupNotification::icon() const { return d->m_icon; } bool PopupNotification::resident() const { return d->m_resident; } QString PopupNotification::soundFile() const { return d->m_soundFile; } QString PopupNotification::soundName() const { return d->m_soundName; } bool PopupNotification::suppressSound() const { return d->m_suppressSound; } bool PopupNotification::transient() const { return d->m_transient; } QString PopupNotification::display() const { return d->m_display; } void PopupNotification::setUrgency(PopupNotification::Urgency urgency) { d->m_urgency = urgency; } PopupNotification::Urgency PopupNotification::urgency() const { return d->m_urgency; } QStringList PopupNotification::actionState() const { return d->m_actionState; } bool PopupNotification::noFold() const { return d->m_noFold; } int PopupNotification::popupTimeout() const { return d->m_popupTimeout; }