Notification improvements.
This commit is contained in:
parent
a0fcd00a6f
commit
bb069197d6
|
@ -1,5 +1,5 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
|
|
||||||
RoomForm {
|
RoomForm {
|
||||||
roomListModel.onNewMessage: if (!window.visible) spectralController.showMessage(roomName, content, icon)
|
roomListModel.onNewMessage: if (!window.visible) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ Page {
|
||||||
property alias filter: roomListForm.filter
|
property alias filter: roomListForm.filter
|
||||||
|
|
||||||
property alias roomListModel: roomListModel
|
property alias roomListModel: roomListModel
|
||||||
|
property alias enteredRoom: roomListForm.enteredRoom
|
||||||
|
|
||||||
id: page
|
id: page
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,12 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: currentRoom.chooseAndUploadFile()
|
onClicked: currentRoom.chooseAndUploadFile()
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
running: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
|
|
@ -38,6 +38,10 @@ ApplicationWindow {
|
||||||
window.requestActivate()
|
window.requestActivate()
|
||||||
}
|
}
|
||||||
onHideWindow: window.hide()
|
onHideWindow: window.hide()
|
||||||
|
onNotificationClicked: {
|
||||||
|
roomPage.enteredRoom = currentConnection.room(roomId)
|
||||||
|
showWindow()
|
||||||
|
}
|
||||||
onErrorOccured: {
|
onErrorOccured: {
|
||||||
errorDialog.error = error
|
errorDialog.error = error
|
||||||
errorDialog.detail = detail
|
errorDialog.detail = detail
|
||||||
|
|
61
spectral.pro
61
spectral.pro
|
@ -1,4 +1,4 @@
|
||||||
QT += quick widgets multimedia
|
QT += quick widgets multimedia dbus
|
||||||
CONFIG += c++14
|
CONFIG += c++14
|
||||||
CONFIG += object_parallel_to_source
|
CONFIG += object_parallel_to_source
|
||||||
CONFIG += link_pkgconfig
|
CONFIG += link_pkgconfig
|
||||||
|
@ -36,18 +36,6 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
SOURCES += src/main.cpp \
|
|
||||||
src/controller.cpp \
|
|
||||||
src/roomlistmodel.cpp \
|
|
||||||
src/imageprovider.cpp \
|
|
||||||
src/messageeventmodel.cpp \
|
|
||||||
src/emojimodel.cpp \
|
|
||||||
src/spectralroom.cpp \
|
|
||||||
src/userlistmodel.cpp \
|
|
||||||
src/imageitem.cpp \
|
|
||||||
src/accountlistmodel.cpp \
|
|
||||||
src/spectraluser.cpp
|
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
res.qrc
|
res.qrc
|
||||||
|
|
||||||
|
@ -85,13 +73,40 @@ mac {
|
||||||
}
|
}
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
$$PWD/src/controller.h \
|
src/controller.h \
|
||||||
$$PWD/src/roomlistmodel.h \
|
src/roomlistmodel.h \
|
||||||
$$PWD/src/imageprovider.h \
|
src/imageprovider.h \
|
||||||
$$PWD/src/messageeventmodel.h \
|
src/messageeventmodel.h \
|
||||||
$$PWD/src/emojimodel.h \
|
src/emojimodel.h \
|
||||||
$$PWD/src/spectralroom.h \
|
src/spectralroom.h \
|
||||||
$$PWD/src/userlistmodel.h \
|
src/userlistmodel.h \
|
||||||
$$PWD/src/imageitem.h \
|
src/imageitem.h \
|
||||||
$$PWD/src/accountlistmodel.h \
|
src/accountlistmodel.h \
|
||||||
$$PWD/src/spectraluser.h
|
src/spectraluser.h \
|
||||||
|
src/notifications/manager.h
|
||||||
|
|
||||||
|
SOURCES += src/main.cpp \
|
||||||
|
src/controller.cpp \
|
||||||
|
src/roomlistmodel.cpp \
|
||||||
|
src/imageprovider.cpp \
|
||||||
|
src/messageeventmodel.cpp \
|
||||||
|
src/emojimodel.cpp \
|
||||||
|
src/spectralroom.cpp \
|
||||||
|
src/userlistmodel.cpp \
|
||||||
|
src/imageitem.cpp \
|
||||||
|
src/accountlistmodel.cpp \
|
||||||
|
src/spectraluser.cpp
|
||||||
|
|
||||||
|
unix:!mac {
|
||||||
|
SOURCES += src/notifications/managerlinux.cpp
|
||||||
|
}
|
||||||
|
|
||||||
|
win32 {
|
||||||
|
HEADERS += src/notifications/wintoastlib.h
|
||||||
|
SOURCES += src/notifications/managerwin.cpp \
|
||||||
|
src/notifications/wintoastlib.cpp
|
||||||
|
}
|
||||||
|
|
||||||
|
mac {
|
||||||
|
SOURCES += src/notifications/managermac.mm
|
||||||
|
}
|
||||||
|
|
|
@ -27,18 +27,20 @@
|
||||||
#include <QtNetwork/QAuthenticator>
|
#include <QtNetwork/QAuthenticator>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
|
||||||
Controller::Controller(QObject* parent) : QObject(parent) {
|
Controller::Controller(QObject* parent)
|
||||||
tray->setIcon(QIcon(":/assets/img/icon.png"));
|
: QObject(parent), tray(this), notificationsManager(this) {
|
||||||
tray->setToolTip("Spectral");
|
connect(¬ificationsManager, &NotificationsManager::notificationClicked,
|
||||||
connect(tray, &QSystemTrayIcon::activated,
|
this, &Controller::notificationClicked);
|
||||||
|
tray.setIcon(QIcon(":/assets/img/icon.png"));
|
||||||
|
tray.setToolTip("Spectral");
|
||||||
|
connect(&tray, &QSystemTrayIcon::activated,
|
||||||
[this](QSystemTrayIcon::ActivationReason r) {
|
[this](QSystemTrayIcon::ActivationReason r) {
|
||||||
if (r != QSystemTrayIcon::Context) emit showWindow();
|
if (r != QSystemTrayIcon::Context) emit showWindow();
|
||||||
});
|
});
|
||||||
connect(tray, &QSystemTrayIcon::messageClicked, [=] { emit showWindow(); });
|
trayMenu.addAction("Hide Window", [=] { emit hideWindow(); });
|
||||||
trayMenu->addAction("Hide Window", [=] { emit hideWindow(); });
|
trayMenu.addAction("Quit", [=] { QApplication::quit(); });
|
||||||
trayMenu->addAction("Quit", [=] { QApplication::quit(); });
|
tray.setContextMenu(&trayMenu);
|
||||||
tray->setContextMenu(trayMenu);
|
tray.show();
|
||||||
tray->show();
|
|
||||||
|
|
||||||
Connection::setRoomType<SpectralRoom>();
|
Connection::setRoomType<SpectralRoom>();
|
||||||
Connection::setUserType<SpectralUser>();
|
Connection::setUserType<SpectralUser>();
|
||||||
|
@ -226,11 +228,6 @@ void Controller::playAudio(QUrl localFile) {
|
||||||
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
|
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::showMessage(const QString& title, const QString& msg,
|
|
||||||
const QIcon& icon) {
|
|
||||||
tray->showMessage(title, msg, icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage Controller::safeImage(QImage image) {
|
QImage Controller::safeImage(QImage image) {
|
||||||
if (image.isNull()) return QImage();
|
if (image.isNull()) return QImage();
|
||||||
return image;
|
return image;
|
||||||
|
@ -243,3 +240,11 @@ QColor Controller::color(QString userId) {
|
||||||
void Controller::setColor(QString userId, QColor newColor) {
|
void Controller::setColor(QString userId, QColor newColor) {
|
||||||
SettingsGroup("UI/Color").setValue(userId, newColor.name());
|
SettingsGroup("UI/Color").setValue(userId, newColor.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Controller::postNotification(const QString& roomId, const QString& eventId,
|
||||||
|
const QString& roomName,
|
||||||
|
const QString& senderName,
|
||||||
|
const QString& text, const QImage& icon) {
|
||||||
|
notificationsManager.postNotification(roomId, eventId, roomName, senderName,
|
||||||
|
text, icon);
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define CONTROLLER_H
|
#define CONTROLLER_H
|
||||||
|
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
|
#include "notifications/manager.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
||||||
|
@ -39,8 +40,9 @@ class Controller : public QObject {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QClipboard* m_clipboard = QApplication::clipboard();
|
QClipboard* m_clipboard = QApplication::clipboard();
|
||||||
QSystemTrayIcon* tray = new QSystemTrayIcon();
|
QSystemTrayIcon tray;
|
||||||
QMenu* trayMenu = new QMenu();
|
QMenu trayMenu;
|
||||||
|
NotificationsManager notificationsManager;
|
||||||
QVector<Connection*> m_connections;
|
QVector<Connection*> m_connections;
|
||||||
|
|
||||||
QByteArray loadAccessToken(const AccountSettings& account);
|
QByteArray loadAccessToken(const AccountSettings& account);
|
||||||
|
@ -60,6 +62,7 @@ class Controller : public QObject {
|
||||||
void connectionAdded(Connection* conn);
|
void connectionAdded(Connection* conn);
|
||||||
void connectionDropped(Connection* conn);
|
void connectionDropped(Connection* conn);
|
||||||
void initiated();
|
void initiated();
|
||||||
|
void notificationClicked(const QString roomId, const QString eventId);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void logout(Connection* conn);
|
void logout(Connection* conn);
|
||||||
|
@ -68,7 +71,9 @@ class Controller : public QObject {
|
||||||
void createDirectChat(Connection* c, const QString& userID);
|
void createDirectChat(Connection* c, const QString& userID);
|
||||||
void copyToClipboard(const QString& text);
|
void copyToClipboard(const QString& text);
|
||||||
void playAudio(QUrl localFile);
|
void playAudio(QUrl localFile);
|
||||||
void showMessage(const QString& title, const QString& msg, const QIcon& icon);
|
void postNotification(const QString& roomId, const QString& eventId,
|
||||||
|
const QString& roomName, const QString& senderName,
|
||||||
|
const QString& text, const QImage& icon);
|
||||||
|
|
||||||
static QImage safeImage(QImage image);
|
static QImage safeImage(QImage image);
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,8 +49,9 @@ int main(int argc, char *argv[]) {
|
||||||
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
|
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
|
||||||
|
|
||||||
qRegisterMetaType<User *>("User*");
|
qRegisterMetaType<User *>("User*");
|
||||||
|
qRegisterMetaType<Room *>("Room*");
|
||||||
qRegisterMetaType<MessageEventType>("MessageEventType");
|
qRegisterMetaType<MessageEventType>("MessageEventType");
|
||||||
qRegisterMetaType<SpectralRoom *>("SpectralRoom");
|
qRegisterMetaType<SpectralRoom *>("SpectralRoom*");
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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) {}
|
|
@ -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) {}
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -80,10 +80,10 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
|
||||||
if (event->isStateEvent()) return;
|
if (event->isStateEvent()) return;
|
||||||
User* sender = room->user(event->senderId());
|
User* sender = room->user(event->senderId());
|
||||||
if (sender == room->localUser()) return;
|
if (sender == room->localUser()) return;
|
||||||
emit newMessage(room->displayName(),
|
emit newMessage(room->id(), event->id(), room->displayName(),
|
||||||
sender->displayname() + ": " +
|
sender->displayname(),
|
||||||
event->contentJson().value("body").toString(),
|
event->contentJson().value("body").toString(),
|
||||||
QPixmap::fromImage(room->avatar(64)));
|
room->avatar(64));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,8 +74,9 @@ class RoomListModel : public QAbstractListModel {
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
void roomAdded(SpectralRoom* room);
|
void roomAdded(SpectralRoom* room);
|
||||||
void newMessage(const QString& roomName, const QString& content,
|
void newMessage(const QString& roomId, const QString& eventId,
|
||||||
const QIcon& icon);
|
const QString& roomName, const QString& senderName,
|
||||||
|
const QString& text, const QImage& icon);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ROOMLISTMODEL_H
|
#endif // ROOMLISTMODEL_H
|
||||||
|
|
Loading…
Reference in New Issue