ukui-notification/libukui-notification/popup-notification.cpp

433 lines
11 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 <https://www.gnu.org/licenses/>.
*
* Authors: iaom <zhangpengfei@kylinos.cn>
*/
#include "popup-notification.h"
#include <QDBusArgument>
#include <QDebug>
#include <QImageReader>
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<QPair<QString, QString>> 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<QPair<QString, QString>> 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<QDBusArgument>());
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;
}