Notification improvements.
This commit is contained in:
49
src/notifications/manager.h
Normal file
49
src/notifications/manager.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <QImage>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
#include <QtDBus/QDBusArgument>
|
||||
#include <QtDBus/QDBusInterface>
|
||||
#endif
|
||||
|
||||
struct roomEventId {
|
||||
QString roomId;
|
||||
QString eventId;
|
||||
};
|
||||
|
||||
class NotificationsManager : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
NotificationsManager(QObject *parent = nullptr);
|
||||
|
||||
void postNotification(const QString &roomId, const QString &eventId,
|
||||
const QString &roomName, const QString &senderName,
|
||||
const QString &text, const QImage &icon);
|
||||
|
||||
signals:
|
||||
void notificationClicked(const QString roomId, const QString eventId);
|
||||
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
private:
|
||||
QDBusInterface dbus;
|
||||
uint showNotification(const QString summary, const QString text,
|
||||
const QImage image);
|
||||
|
||||
// notification ID to (room ID, event ID)
|
||||
QMap<uint, roomEventId> notificationIds;
|
||||
#endif
|
||||
|
||||
// these slots are platform specific (D-Bus only)
|
||||
// but Qt slot declarations can not be inside an ifdef!
|
||||
private slots:
|
||||
void actionInvoked(uint id, QString action);
|
||||
void notificationClosed(uint id, uint reason);
|
||||
};
|
||||
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
||||
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image);
|
||||
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &);
|
||||
#endif
|
||||
133
src/notifications/managerlinux.cpp
Normal file
133
src/notifications/managerlinux.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "manager.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
#include <QtDBus/QDBusConnection>
|
||||
#include <QtDBus/QDBusMessage>
|
||||
#include <QtDBus/QDBusMetaType>
|
||||
|
||||
NotificationsManager::NotificationsManager(QObject *parent)
|
||||
: QObject(parent),
|
||||
dbus("org.freedesktop.Notifications", "/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications", QDBusConnection::sessionBus(),
|
||||
this) {
|
||||
qDBusRegisterMetaType<QImage>();
|
||||
|
||||
QDBusConnection::sessionBus().connect(
|
||||
"org.freedesktop.Notifications", "/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications", "ActionInvoked", this,
|
||||
SLOT(actionInvoked(uint, QString)));
|
||||
QDBusConnection::sessionBus().connect(
|
||||
"org.freedesktop.Notifications", "/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications", "NotificationClosed", this,
|
||||
SLOT(notificationClosed(uint, uint)));
|
||||
}
|
||||
|
||||
void NotificationsManager::postNotification(
|
||||
const QString &roomid, const QString &eventid, const QString &roomname,
|
||||
const QString &sender, const QString &text, const QImage &icon) {
|
||||
uint id = showNotification(roomname, sender + ": " + text, icon);
|
||||
notificationIds[id] = roomEventId{roomid, eventid};
|
||||
}
|
||||
/**
|
||||
* This function is based on code from
|
||||
* https://github.com/rohieb/StratumsphereTrayIcon
|
||||
* Copyright (C) 2012 Roland Hieber <rohieb@rohieb.name>
|
||||
* Licensed under the GNU General Public License, version 3
|
||||
*/
|
||||
uint NotificationsManager::showNotification(const QString summary,
|
||||
const QString text,
|
||||
const QImage image) {
|
||||
QVariantMap hints;
|
||||
hints["image-data"] = image;
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << "Spectral"; // app_name
|
||||
argumentList << uint(0); // replace_id
|
||||
argumentList << ""; // app_icon
|
||||
argumentList << summary; // summary
|
||||
argumentList << text; // body
|
||||
argumentList << (QStringList("default") << "reply"); // actions
|
||||
argumentList << hints; // hints
|
||||
argumentList << int(-1); // timeout in ms
|
||||
|
||||
static QDBusInterface notifyApp("org.freedesktop.Notifications",
|
||||
"/org/freedesktop/Notifications",
|
||||
"org.freedesktop.Notifications");
|
||||
QDBusMessage reply =
|
||||
notifyApp.callWithArgumentList(QDBus::AutoDetect, "Notify", argumentList);
|
||||
if (reply.type() == QDBusMessage::ErrorMessage) {
|
||||
qDebug() << "D-Bus Error:" << reply.errorMessage();
|
||||
return 0;
|
||||
} else {
|
||||
return reply.arguments().first().toUInt();
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::actionInvoked(uint id, QString action) {
|
||||
if (action == "default" && notificationIds.contains(id)) {
|
||||
roomEventId idEntry = notificationIds[id];
|
||||
emit notificationClicked(idEntry.roomId, idEntry.eventId);
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationsManager::notificationClosed(uint id, uint reason) {
|
||||
Q_UNUSED(reason);
|
||||
notificationIds.remove(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify
|
||||
*
|
||||
* This function is from the Clementine project (see
|
||||
* http://www.clementine-player.org) and licensed under the GNU General Public
|
||||
* License, version 3 or later.
|
||||
*
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
*/
|
||||
QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image) {
|
||||
if (image.isNull()) {
|
||||
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) {
|
||||
QRgb *p = (QRgb *)scaled.scanLine(y);
|
||||
QRgb *q = (QRgb *)i.scanLine(y);
|
||||
QRgb *end = p + scaled.width();
|
||||
while (p < end) {
|
||||
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
|
||||
p++;
|
||||
q++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
arg.beginStructure();
|
||||
arg << i.width();
|
||||
arg << i.height();
|
||||
arg << i.bytesPerLine();
|
||||
arg << i.hasAlphaChannel();
|
||||
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
|
||||
arg << i.depth() / channels;
|
||||
arg << channels;
|
||||
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
|
||||
arg.endStructure();
|
||||
return arg;
|
||||
}
|
||||
|
||||
const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &) {
|
||||
// This is needed to link but shouldn't be called.
|
||||
Q_ASSERT(0);
|
||||
return arg;
|
||||
}
|
||||
33
src/notifications/managermac.mm
Normal file
33
src/notifications/managermac.mm
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "manager.h"
|
||||
|
||||
#include <Foundation/Foundation.h>
|
||||
#include <QtMac>
|
||||
|
||||
@interface NSUserNotification (CFIPrivate)
|
||||
- (void)set_identityImage:(NSImage *)image;
|
||||
@end
|
||||
|
||||
NotificationsManager::NotificationsManager(QObject *parent) : QObject(parent) {}
|
||||
|
||||
void NotificationsManager::postNotification(const QString &roomId, const QString &eventId,
|
||||
const QString &roomName, const QString &senderName,
|
||||
const QString &text, const QImage &icon) {
|
||||
Q_UNUSED(roomId);
|
||||
Q_UNUSED(eventId);
|
||||
Q_UNUSED(icon);
|
||||
|
||||
NSUserNotification *notif = [[NSUserNotification alloc] init];
|
||||
|
||||
notif.title = roomName.toNSString();
|
||||
notif.subtitle = QString("%1 sent a message").arg(senderName).toNSString();
|
||||
notif.informativeText = text.toNSString();
|
||||
notif.soundName = NSUserNotificationDefaultSoundName;
|
||||
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notif];
|
||||
[notif autorelease];
|
||||
}
|
||||
|
||||
// unused
|
||||
void NotificationsManager::actionInvoked(uint, QString) {}
|
||||
|
||||
void NotificationsManager::notificationClosed(uint, uint) {}
|
||||
59
src/notifications/managerwin.cpp
Normal file
59
src/notifications/managerwin.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "manager.h"
|
||||
#include "wintoastlib.h"
|
||||
|
||||
using namespace WinToastLib;
|
||||
|
||||
class CustomHandler : public IWinToastHandler {
|
||||
public:
|
||||
void toastActivated() const {}
|
||||
void toastActivated(int) const {}
|
||||
void toastFailed() const {
|
||||
std::wcout << L"Error showing current toast" << std::endl;
|
||||
}
|
||||
void toastDismissed(WinToastDismissalReason) const {}
|
||||
};
|
||||
|
||||
namespace {
|
||||
bool isInitialized = false;
|
||||
|
||||
void init() {
|
||||
isInitialized = true;
|
||||
|
||||
WinToast::instance()->setAppName(L"Spectral");
|
||||
WinToast::instance()->setAppUserModelId(
|
||||
WinToast::configureAUMI(L"Spectral", L"Spectral"));
|
||||
if (!WinToast::instance()->initialize())
|
||||
std::wcout << "Your system in not compatible with toast notifications\n";
|
||||
}
|
||||
} // namespace
|
||||
|
||||
NotificationsManager::NotificationsManager(QObject *parent) : QObject(parent) {}
|
||||
|
||||
void NotificationsManager::postNotification(
|
||||
const QString &room_id, const QString &event_id, const QString &room_name,
|
||||
const QString &sender, const QString &text, const QImage &icon) {
|
||||
Q_UNUSED(room_id)
|
||||
Q_UNUSED(event_id)
|
||||
Q_UNUSED(icon)
|
||||
|
||||
if (!isInitialized) init();
|
||||
|
||||
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
|
||||
if (room_name != sender)
|
||||
templ.setTextField(
|
||||
QString("%1 - %2").arg(sender).arg(room_name).toStdWString(),
|
||||
WinToastTemplate::FirstLine);
|
||||
else
|
||||
templ.setTextField(QString("%1").arg(sender).toStdWString(),
|
||||
WinToastTemplate::FirstLine);
|
||||
templ.setTextField(QString("%1").arg(text).toStdWString(),
|
||||
WinToastTemplate::SecondLine);
|
||||
// TODO: implement room or user avatar
|
||||
// templ.setImagePath(L"C:/example.png");
|
||||
|
||||
WinToast::instance()->showToast(templ, new CustomHandler());
|
||||
}
|
||||
|
||||
void NotificationsManager::actionInvoked(uint, QString) {}
|
||||
|
||||
void NotificationsManager::notificationClosed(uint, uint) {}
|
||||
1035
src/notifications/wintoastlib.cpp
Normal file
1035
src/notifications/wintoastlib.cpp
Normal file
File diff suppressed because it is too large
Load Diff
164
src/notifications/wintoastlib.h
Normal file
164
src/notifications/wintoastlib.h
Normal file
@@ -0,0 +1,164 @@
|
||||
#ifndef WINTOASTLIB_H
|
||||
#define WINTOASTLIB_H
|
||||
#include <Windows.h>
|
||||
#include <sdkddkver.h>
|
||||
#include <WinUser.h>
|
||||
#include <ShObjIdl.h>
|
||||
#include <wrl/implements.h>
|
||||
#include <wrl/event.h>
|
||||
#include <windows.ui.notifications.h>
|
||||
#include <strsafe.h>
|
||||
#include <Psapi.h>
|
||||
#include <ShlObj.h>
|
||||
#include <roapi.h>
|
||||
#include <propvarutil.h>
|
||||
#include <functiondiscoverykeys.h>
|
||||
#include <iostream>
|
||||
#include <winstring.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
using namespace Microsoft::WRL;
|
||||
using namespace ABI::Windows::Data::Xml::Dom;
|
||||
using namespace ABI::Windows::Foundation;
|
||||
using namespace ABI::Windows::UI::Notifications;
|
||||
using namespace Windows::Foundation;
|
||||
|
||||
#define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\"
|
||||
#define DEFAULT_LINK_FORMAT L".lnk"
|
||||
namespace WinToastLib {
|
||||
|
||||
class IWinToastHandler {
|
||||
public:
|
||||
enum WinToastDismissalReason {
|
||||
UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled,
|
||||
ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden,
|
||||
TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut
|
||||
};
|
||||
virtual ~IWinToastHandler() {}
|
||||
virtual void toastActivated() const = 0;
|
||||
virtual void toastActivated(int actionIndex) const = 0;
|
||||
virtual void toastDismissed(WinToastDismissalReason state) const = 0;
|
||||
virtual void toastFailed() const = 0;
|
||||
};
|
||||
|
||||
class WinToastTemplate {
|
||||
public:
|
||||
enum Duration { System, Short, Long };
|
||||
enum AudioOption { Default = 0, Silent = 1, Loop = 2 };
|
||||
enum TextField { FirstLine = 0, SecondLine, ThirdLine };
|
||||
enum WinToastTemplateType {
|
||||
ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01,
|
||||
ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02,
|
||||
ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03,
|
||||
ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04,
|
||||
Text01 = ToastTemplateType::ToastTemplateType_ToastText01,
|
||||
Text02 = ToastTemplateType::ToastTemplateType_ToastText02,
|
||||
Text03 = ToastTemplateType::ToastTemplateType_ToastText03,
|
||||
Text04 = ToastTemplateType::ToastTemplateType_ToastText04,
|
||||
WinToastTemplateTypeCount
|
||||
};
|
||||
|
||||
WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02);
|
||||
~WinToastTemplate();
|
||||
|
||||
void setTextField(_In_ const std::wstring& txt, _In_ TextField pos);
|
||||
void setImagePath(_In_ const std::wstring& imgPath);
|
||||
void setAudioPath(_In_ const std::wstring& audioPath);
|
||||
void setAttributionText(_In_ const std::wstring & attributionText);
|
||||
void addAction(_In_ const std::wstring& label);
|
||||
void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption);
|
||||
void setDuration(_In_ Duration duration);
|
||||
void setExpiration(_In_ INT64 millisecondsFromNow);
|
||||
std::size_t textFieldsCount() const;
|
||||
std::size_t actionsCount() const;
|
||||
bool hasImage() const;
|
||||
const std::vector<std::wstring>& textFields() const;
|
||||
const std::wstring& textField(_In_ TextField pos) const;
|
||||
const std::wstring& actionLabel(_In_ int pos) const;
|
||||
const std::wstring& imagePath() const;
|
||||
const std::wstring& audioPath() const;
|
||||
const std::wstring& attributionText() const;
|
||||
INT64 expiration() const;
|
||||
WinToastTemplateType type() const;
|
||||
WinToastTemplate::AudioOption audioOption() const;
|
||||
Duration duration() const;
|
||||
private:
|
||||
std::vector<std::wstring> _textFields;
|
||||
std::vector<std::wstring> _actions;
|
||||
std::wstring _imagePath = L"";
|
||||
std::wstring _audioPath = L"";
|
||||
std::wstring _attributionText = L"";
|
||||
INT64 _expiration = 0;
|
||||
AudioOption _audioOption = WinToastTemplate::AudioOption::Default;
|
||||
WinToastTemplateType _type = WinToastTemplateType::Text01;
|
||||
Duration _duration = Duration::System;
|
||||
};
|
||||
|
||||
class WinToast {
|
||||
public:
|
||||
enum WinToastError {
|
||||
NoError = 0,
|
||||
NotInitialized,
|
||||
SystemNotSupported,
|
||||
ShellLinkNotCreated,
|
||||
InvalidAppUserModelID,
|
||||
InvalidParameters,
|
||||
InvalidHandler,
|
||||
NotDisplayed,
|
||||
UnknownError
|
||||
};
|
||||
|
||||
enum ShortcutResult {
|
||||
SHORTCUT_UNCHANGED = 0,
|
||||
SHORTCUT_WAS_CHANGED = 1,
|
||||
SHORTCUT_WAS_CREATED = 2,
|
||||
|
||||
SHORTCUT_MISSING_PARAMETERS = -1,
|
||||
SHORTCUT_INCOMPATIBLE_OS = -2,
|
||||
SHORTCUT_COM_INIT_FAILURE = -3,
|
||||
SHORTCUT_CREATE_FAILED = -4
|
||||
};
|
||||
|
||||
WinToast(void);
|
||||
virtual ~WinToast();
|
||||
static WinToast* instance();
|
||||
static bool isCompatible();
|
||||
static bool isSupportingModernFeatures();
|
||||
static std::wstring configureAUMI(_In_ const std::wstring& companyName,
|
||||
_In_ const std::wstring& productName,
|
||||
_In_ const std::wstring& subProduct = std::wstring(),
|
||||
_In_ const std::wstring& versionInformation = std::wstring()
|
||||
);
|
||||
virtual bool initialize(_Out_ WinToastError* error = nullptr);
|
||||
virtual bool isInitialized() const;
|
||||
virtual bool hideToast(_In_ INT64 id);
|
||||
virtual INT64 showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error = nullptr);
|
||||
virtual void clear();
|
||||
virtual enum ShortcutResult createShortcut();
|
||||
|
||||
const std::wstring& appName() const;
|
||||
const std::wstring& appUserModelId() const;
|
||||
void setAppUserModelId(_In_ const std::wstring& appName);
|
||||
void setAppName(_In_ const std::wstring& appName);
|
||||
|
||||
protected:
|
||||
bool _isInitialized;
|
||||
bool _hasCoInitialized;
|
||||
std::wstring _appName;
|
||||
std::wstring _aumi;
|
||||
std::map<INT64, ComPtr<IToastNotification>> _buffer;
|
||||
|
||||
HRESULT validateShellLinkHelper(_Out_ bool& wasChanged);
|
||||
HRESULT createShellLinkHelper();
|
||||
HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path);
|
||||
HRESULT setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default);
|
||||
HRESULT setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ int pos);
|
||||
HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text);
|
||||
HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& action, _In_ const std::wstring& arguments);
|
||||
HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration);
|
||||
ComPtr<IToastNotifier> notifier(_In_ bool* succeded) const;
|
||||
void setError(_Out_ WinToastError* error, _In_ WinToastError value);
|
||||
};
|
||||
}
|
||||
#endif // WINTOASTLIB_H
|
||||
Reference in New Issue
Block a user