2018-02-27 18:06:05 +01:00
|
|
|
/*
|
|
|
|
* Strawberry Music Player
|
|
|
|
* This file was part of Clementine.
|
|
|
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
2021-03-20 21:14:47 +01:00
|
|
|
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
2018-02-27 18:06:05 +01:00
|
|
|
*
|
|
|
|
* Strawberry 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.
|
|
|
|
*
|
|
|
|
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
2018-08-09 18:39:44 +02:00
|
|
|
*
|
2018-02-27 18:06:05 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
2020-08-09 01:37:00 +02:00
|
|
|
#include <dbus/notification.h>
|
2018-07-03 21:21:33 +02:00
|
|
|
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QtGlobal>
|
|
|
|
#include <QObject>
|
|
|
|
#include <QByteArray>
|
|
|
|
#include <QDateTime>
|
|
|
|
#include <QMap>
|
|
|
|
#include <QVariant>
|
|
|
|
#include <QString>
|
|
|
|
#include <QStringList>
|
2020-11-08 21:54:16 +01:00
|
|
|
#include <QRegularExpression>
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QImage>
|
2020-08-09 01:37:00 +02:00
|
|
|
#include <QCoreApplication>
|
2020-10-04 04:38:46 +02:00
|
|
|
#include <QVersionNumber>
|
2020-08-09 01:37:00 +02:00
|
|
|
#include <QDBusArgument>
|
|
|
|
#include <QDBusConnection>
|
|
|
|
#include <QDBusError>
|
|
|
|
#include <QDBusPendingCall>
|
|
|
|
#include <QDBusPendingReply>
|
|
|
|
#include <QDBusPendingCallWatcher>
|
2018-05-01 00:41:33 +02:00
|
|
|
#include <QJsonObject>
|
2018-02-27 18:06:05 +01:00
|
|
|
#include <QtDebug>
|
|
|
|
|
|
|
|
#include "core/logging.h"
|
2020-08-09 01:37:00 +02:00
|
|
|
#include "osddbus.h"
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2018-07-04 00:55:09 +02:00
|
|
|
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image) {
|
2018-03-18 18:39:30 +01:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
if (image.isNull()) {
|
|
|
|
// Sometimes this gets called with a null QImage for no obvious reason.
|
|
|
|
arg.beginStructure();
|
|
|
|
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
|
|
|
|
arg.endStructure();
|
|
|
|
return arg;
|
|
|
|
}
|
|
|
|
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
|
|
|
|
|
|
|
|
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
|
|
|
|
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
|
|
|
// ABGR -> ARGB
|
|
|
|
QImage i = scaled.rgbSwapped();
|
|
|
|
#else
|
|
|
|
// ABGR -> GBAR
|
|
|
|
QImage i(scaled.size(), scaled.format());
|
|
|
|
for (int y = 0; y < i.height(); ++y) {
|
2021-10-11 22:28:28 +02:00
|
|
|
QRgb *p = reinterpret_cast<QRgb*>(scaled.scanLine(y));
|
|
|
|
QRgb *q = reinterpret_cast<QRgb*>(i.scanLine(y));
|
2018-07-04 00:55:09 +02:00
|
|
|
QRgb *end = p + scaled.width();
|
2018-02-27 18:06:05 +01:00
|
|
|
while (p < end) {
|
|
|
|
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
|
|
|
|
p++;
|
|
|
|
q++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
arg.beginStructure();
|
2020-08-23 21:55:34 +02:00
|
|
|
arg << static_cast<qint32>(i.width());
|
|
|
|
arg << static_cast<qint32>(i.height());
|
2020-08-10 23:05:07 +02:00
|
|
|
arg << static_cast<qint32>(i.bytesPerLine());
|
2018-02-27 18:06:05 +01:00
|
|
|
arg << i.hasAlphaChannel();
|
2020-08-23 21:55:34 +02:00
|
|
|
qint32 channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
|
|
|
|
qint32 bitspersample = i.depth() / channels;
|
|
|
|
arg << bitspersample;
|
2018-02-27 18:06:05 +01:00
|
|
|
arg << channels;
|
2019-07-08 23:35:43 +02:00
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
2020-08-23 21:55:34 +02:00
|
|
|
arg << QByteArray(reinterpret_cast<const char*>(i.constBits()), static_cast<int>(i.sizeInBytes()));
|
2019-07-08 23:35:43 +02:00
|
|
|
#else
|
2020-08-23 21:55:34 +02:00
|
|
|
arg << QByteArray(reinterpret_cast<const char*>(i.constBits()), i.byteCount());
|
2019-07-08 23:35:43 +02:00
|
|
|
#endif
|
2018-02-27 18:06:05 +01:00
|
|
|
arg.endStructure();
|
2020-08-23 21:55:34 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
return arg;
|
2018-03-18 18:39:30 +01:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
2018-07-04 00:55:09 +02:00
|
|
|
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &image) {
|
2020-08-09 01:37:00 +02:00
|
|
|
|
2019-09-15 20:27:32 +02:00
|
|
|
Q_UNUSED(image);
|
2020-08-09 01:37:00 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
// This is needed to link but shouldn't be called.
|
|
|
|
Q_ASSERT(0);
|
|
|
|
return arg;
|
2020-08-09 01:37:00 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-07-11 07:40:57 +02:00
|
|
|
OSDDBus::OSDDBus(std::shared_ptr<SystemTrayIcon> tray_icon, Application *app, QObject *parent)
|
|
|
|
: OSDBase(tray_icon, app, parent),
|
|
|
|
version_(1, 1),
|
|
|
|
notification_id_(0) {
|
|
|
|
|
2020-08-09 01:37:00 +02:00
|
|
|
Init();
|
2021-07-11 07:40:57 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
2020-08-09 01:37:00 +02:00
|
|
|
OSDDBus::~OSDDBus() = default;
|
|
|
|
|
|
|
|
void OSDDBus::Init() {
|
2018-03-18 18:39:30 +01:00
|
|
|
|
2021-06-21 15:40:25 +02:00
|
|
|
interface_ = std::make_unique<OrgFreedesktopNotificationsInterface>(OrgFreedesktopNotificationsInterface::staticInterfaceName(), "/org/freedesktop/Notifications", QDBusConnection::sessionBus());
|
2018-02-27 18:06:05 +01:00
|
|
|
if (!interface_->isValid()) {
|
|
|
|
qLog(Warning) << "Error connecting to notifications service.";
|
|
|
|
}
|
2018-03-18 18:39:30 +01:00
|
|
|
|
2020-10-04 04:38:46 +02:00
|
|
|
QString vendor, version, spec_version;
|
|
|
|
QDBusReply<QString> reply = interface_->GetServerInformation(vendor, version, spec_version);
|
|
|
|
if (reply.isValid()) {
|
|
|
|
version_ = QVersionNumber::fromString(spec_version);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
qLog(Error) << "Could not retrieve notification server information." << reply.error();
|
|
|
|
}
|
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
2020-08-09 01:37:00 +02:00
|
|
|
bool OSDDBus::SupportsNativeNotifications() { return true; }
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2020-08-09 01:37:00 +02:00
|
|
|
bool OSDDBus::SupportsTrayPopups() { return true; }
|
2018-02-27 18:06:05 +01:00
|
|
|
|
2020-08-09 01:37:00 +02:00
|
|
|
void OSDDBus::ShowMessageNative(const QString &summary, const QString &message, const QString &icon, const QImage &image) {
|
2018-03-18 18:39:30 +01:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
if (!interface_) return;
|
|
|
|
|
|
|
|
QVariantMap hints;
|
2020-11-08 21:54:16 +01:00
|
|
|
QString summary_stripped = summary;
|
|
|
|
summary_stripped = summary_stripped.remove(QRegularExpression("[&\"<>]")).simplified();
|
2020-10-04 04:38:46 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
if (!image.isNull()) {
|
2020-10-04 04:38:46 +02:00
|
|
|
if (version_ >= QVersionNumber(1, 2)) {
|
2021-06-25 18:19:37 +02:00
|
|
|
hints["image-data"] = QVariant(image);
|
2020-10-04 04:38:46 +02:00
|
|
|
}
|
|
|
|
else if (version_ >= QVersionNumber(1, 1)) {
|
2021-06-25 18:19:37 +02:00
|
|
|
hints["image_data"] = QVariant(image);
|
2020-10-04 04:38:46 +02:00
|
|
|
}
|
|
|
|
else {
|
2021-06-25 18:19:37 +02:00
|
|
|
hints["icon_data"] = QVariant(image);
|
2020-10-04 04:38:46 +02:00
|
|
|
}
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
2021-06-25 18:19:37 +02:00
|
|
|
hints["transient"] = QVariant(true);
|
2019-04-19 10:35:15 +02:00
|
|
|
|
2021-09-12 21:24:22 +02:00
|
|
|
quint64 id = 0;
|
2020-08-09 01:37:00 +02:00
|
|
|
if (last_notification_time_.secsTo(QDateTime::currentDateTime()) * 1000 < timeout_msec()) {
|
2018-02-27 18:06:05 +01:00
|
|
|
// Reuse the existing popup if it's still open. The reason we don't always
|
2018-05-01 00:41:33 +02:00
|
|
|
// reuse the popup is because the notification daemon on KDE4 won't re-show the bubble if it's already gone to the tray. See issue #118
|
2018-02-27 18:06:05 +01:00
|
|
|
id = notification_id_;
|
|
|
|
}
|
|
|
|
|
2020-12-11 23:59:38 +01:00
|
|
|
QDBusPendingReply<uint> reply = interface_->Notify(app_name(), id, icon, summary_stripped, message, QStringList(), hints, timeout_msec());
|
2018-07-04 00:55:09 +02:00
|
|
|
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
|
2021-01-26 16:48:04 +01:00
|
|
|
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &OSDDBus::CallFinished);
|
2018-03-18 18:39:30 +01:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
|
|
|
|
2020-08-09 01:37:00 +02:00
|
|
|
void OSDDBus::CallFinished(QDBusPendingCallWatcher *watcher) {
|
2018-03-18 18:39:30 +01:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
std::unique_ptr<QDBusPendingCallWatcher> w(watcher);
|
|
|
|
|
2021-08-25 02:58:20 +02:00
|
|
|
QDBusPendingReply<uint> reply = *w;
|
2018-02-27 18:06:05 +01:00
|
|
|
if (reply.isError()) {
|
|
|
|
qLog(Warning) << "Error sending notification" << reply.error().name();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint id = reply.value();
|
|
|
|
if (id != 0) {
|
|
|
|
notification_id_ = id;
|
|
|
|
last_notification_time_ = QDateTime::currentDateTime();
|
|
|
|
}
|
2020-08-09 01:37:00 +02:00
|
|
|
|
2018-02-27 18:06:05 +01:00
|
|
|
}
|
2020-06-17 22:56:20 +02:00
|
|
|
|