mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-04-17 20:08:28 +00:00
302 lines
11 KiB
C++
302 lines
11 KiB
C++
#include "tool/Utils.hpp"
|
|
|
|
// #include "NotificationActivator.hpp"
|
|
#include "WindowsNotificationBackend.hpp"
|
|
#include "core/App.hpp"
|
|
#include "core/call/CallGui.hpp"
|
|
#include "core/chat/ChatGui.hpp"
|
|
#include "core/event-filter/LockEventFilter.hpp"
|
|
#include "tool/Constants.hpp"
|
|
#include "tool/Utils.hpp"
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include "DesktopNotificationManagerCompat.hpp"
|
|
#include <windows.foundation.h>
|
|
#include <windows.ui.notifications.h>
|
|
#endif
|
|
|
|
#include <QDebug>
|
|
#include <QPainter>
|
|
#include <QStandardPaths>
|
|
#include <QSvgRenderer>
|
|
|
|
using namespace Microsoft::WRL;
|
|
using namespace ABI::Windows::UI::Notifications;
|
|
using namespace ABI::Windows::Foundation;
|
|
using namespace Microsoft::WRL::Wrappers;
|
|
|
|
NotificationBackend::NotificationBackend(QObject *parent) : AbstractNotificationBackend(parent) {
|
|
connect(App::getInstance(), &App::sessionLockedChanged, this, [this] {
|
|
if (!App::getInstance()->getSessionLocked()) {
|
|
qDebug() << "Session unlocked, flush pending notifications";
|
|
flushPendingNotifications();
|
|
}
|
|
});
|
|
}
|
|
|
|
void NotificationBackend::flushPendingNotifications() {
|
|
for (const auto ¬if : mPendingNotifications) {
|
|
sendNotification(notif.type, notif.data);
|
|
}
|
|
mPendingNotifications.clear();
|
|
}
|
|
|
|
QString getIconAsPng(const QString &imagePath, const QSize &size = QSize(64, 64)) {
|
|
// Convertit "image://internal/phone-disconnect.svg" en ":/data/image/phone-disconnect.svg"
|
|
QString resourcePath = imagePath;
|
|
if (imagePath.startsWith("image://internal/"))
|
|
resourcePath = ":/data/image/" + imagePath.mid(QString("image://internal/").length());
|
|
|
|
QSvgRenderer renderer(resourcePath);
|
|
if (!renderer.isValid()) return QString();
|
|
|
|
QImage image(size, QImage::Format_ARGB32_Premultiplied);
|
|
image.fill(Qt::transparent);
|
|
QPainter painter(&image);
|
|
renderer.render(&painter);
|
|
painter.end();
|
|
|
|
QString outPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/linphone_" +
|
|
QFileInfo(resourcePath).baseName() + ".png";
|
|
|
|
if (!QFile::exists(outPath)) image.save(outPath, "PNG");
|
|
|
|
return outPath;
|
|
}
|
|
|
|
void NotificationBackend::sendMessageNotification(QVariantMap data) {
|
|
|
|
IToastNotifier *notifier = nullptr;
|
|
HRESULT hr = DesktopNotificationManagerCompat::CreateToastNotifier(¬ifier);
|
|
if (FAILED(hr) || !notifier) {
|
|
lWarning() << "CreateToastNotifier failed:" << Qt::hex << hr;
|
|
return;
|
|
}
|
|
|
|
auto msgTxt = data["message"].toString().toStdWString();
|
|
auto remoteAddress = data["remoteAddress"].toString().toStdWString();
|
|
auto chatRoomName = data["chatRoomName"].toString().toStdWString();
|
|
auto chatRoomAddress = data["chatRoomAddress"].toString().toStdWString();
|
|
auto avatarUri = data["avatarUri"].toString().toStdWString();
|
|
bool isGroup = data["isGroupChat"].toBool();
|
|
ChatGui *chat = data["chat"].value<ChatGui *>();
|
|
|
|
std::wstring xml = L"<toast>"
|
|
L" <visual>"
|
|
L" <binding template=\"ToastGeneric\">"
|
|
L" <text><![CDATA[" +
|
|
chatRoomName +
|
|
L"]]></text>"
|
|
L" <text><![CDATA[" +
|
|
(isGroup ? remoteAddress : L"") +
|
|
L"]]></text>"
|
|
L" <group>"
|
|
L" <subgroup>"
|
|
L" <text hint-style=\"body\"><![CDATA[" +
|
|
msgTxt +
|
|
L"]]></text>"
|
|
L" </subgroup>"
|
|
L" </group>"
|
|
L" </binding>"
|
|
L" </visual>"
|
|
L" <audio silent=\"true\"/>"
|
|
L"</toast>";
|
|
|
|
ABI::Windows::Data::Xml::Dom::IXmlDocument *doc = nullptr;
|
|
hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(xml.c_str(), &doc);
|
|
if (FAILED(hr) || !doc) {
|
|
lWarning() << "CreateXmlDocumentFromString failed:" << Qt::hex << hr;
|
|
notifier->Release();
|
|
return;
|
|
}
|
|
|
|
IToastNotification *toast = nullptr;
|
|
hr = DesktopNotificationManagerCompat::CreateToastNotification(doc, &toast);
|
|
if (FAILED(hr) || !toast) {
|
|
qWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
|
|
doc->Release();
|
|
notifier->Release();
|
|
Utils::showInformationPopup(tr("info_popup_error_title"), tr("info_popup_error_creating_notification"), false);
|
|
return;
|
|
}
|
|
|
|
EventRegistrationToken token;
|
|
toast->add_Activated(Microsoft::WRL::Callback<ITypedEventHandler<ToastNotification *, IInspectable *>>(
|
|
[this, chat](IToastNotification *sender, IInspectable *args) -> HRESULT {
|
|
qInfo() << "Message toast clicked!";
|
|
|
|
Utils::openChat(chat);
|
|
|
|
return S_OK;
|
|
})
|
|
.Get(),
|
|
&token);
|
|
|
|
hr = notifier->Show(toast);
|
|
if (FAILED(hr)) {
|
|
qWarning() << "Toast Show failed:" << Qt::hex << hr;
|
|
}
|
|
|
|
toast->Release();
|
|
doc->Release();
|
|
notifier->Release();
|
|
}
|
|
|
|
void NotificationBackend::sendCallNotification(QVariantMap data) {
|
|
|
|
IToastNotifier *notifier = nullptr;
|
|
HRESULT hr = DesktopNotificationManagerCompat::CreateToastNotifier(¬ifier);
|
|
if (FAILED(hr) || !notifier) {
|
|
lWarning() << "CreateToastNotifier failed:" << Qt::hex << hr;
|
|
return;
|
|
}
|
|
|
|
auto displayName = data["displayName"].toString().toStdWString();
|
|
CallGui *call = data["call"].value<CallGui *>();
|
|
int timeout = 2;
|
|
// AbstractNotificationBackend::Notifications[(int)NotificationType::ReceivedCall].getTimeout();
|
|
|
|
// Incoming call
|
|
auto callDescription = tr("incoming_call").toStdWString();
|
|
|
|
QList<ToastButton> actions;
|
|
QString declineIcon = getIconAsPng(Utils::getAppIcon("endCall").toString());
|
|
QString acceptIcon = getIconAsPng(Utils::getAppIcon("phone").toString());
|
|
actions.append(ToastButton(tr("accept_button"), "accept", acceptIcon));
|
|
actions.append(ToastButton(tr("decline_button"), "decline", declineIcon));
|
|
std::wstring wActions;
|
|
if (!actions.isEmpty()) {
|
|
wActions += L"<actions>";
|
|
for (const auto &action : actions) {
|
|
std::wstring wLabel = action.label.toStdWString();
|
|
std::wstring wArg = action.argument.toStdWString();
|
|
std::wstring wIcon = action.icon.toStdWString();
|
|
qDebug() << "toast icon action" << wIcon;
|
|
wActions +=
|
|
L"<action content=\"" + wLabel + L"\" arguments=\"" + wArg + L"\" imageUri=\"" + wIcon + L"\"/>";
|
|
}
|
|
wActions += L"</actions>";
|
|
}
|
|
|
|
std::wstring xml = L"<toast scenario=\"reminder\">"
|
|
L" <visual>"
|
|
L" <binding template=\"ToastGeneric\">"
|
|
L" <text hint-style=\"header\">" +
|
|
displayName +
|
|
L"</text>"
|
|
L" <text hint-style=\"body\">" +
|
|
callDescription +
|
|
L"</text>"
|
|
L" </binding>"
|
|
L" </visual>" +
|
|
wActions +
|
|
L" <audio silent=\"true\"/>"
|
|
L"</toast>";
|
|
|
|
ABI::Windows::Data::Xml::Dom::IXmlDocument *doc = nullptr;
|
|
hr = DesktopNotificationManagerCompat::CreateXmlDocumentFromString(xml.c_str(), &doc);
|
|
if (FAILED(hr) || !doc) {
|
|
lWarning() << "CreateXmlDocumentFromString failed:" << Qt::hex << hr;
|
|
notifier->Release();
|
|
return;
|
|
}
|
|
|
|
IToastNotification *toast = nullptr;
|
|
hr = DesktopNotificationManagerCompat::CreateToastNotification(doc, &toast);
|
|
if (FAILED(hr) || !toast) {
|
|
qWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
|
|
doc->Release();
|
|
notifier->Release();
|
|
Utils::showInformationPopup(tr("info_popup_error_title"), tr("info_popup_error_creating_notification"), false);
|
|
return;
|
|
}
|
|
|
|
ComPtr<IToastNotification2> toast2;
|
|
hr = toast->QueryInterface(IID_PPV_ARGS(&toast2));
|
|
if (FAILED(hr)) qWarning() << "QueryInterface failed";
|
|
auto callId = call->mCore->getCallId();
|
|
qDebug() << "put tag to toast" << callId;
|
|
hr = toast2->put_Tag(HStringReference(reinterpret_cast<const wchar_t *>(callId.utf16())).Get());
|
|
toast2->put_Group(HStringReference(L"linphone").Get());
|
|
if (FAILED(hr)) qWarning() << "puting tag on toast failed";
|
|
|
|
connect(call->mCore.get(), &CallCore::stateChanged, this, [this, call, notifier, toast] {
|
|
if (call->mCore->getState() == LinphoneEnums::CallState::End) {
|
|
qDebug() << "Call ended, remove toast";
|
|
auto callId = call->mCore->getCallId();
|
|
call->deleteLater();
|
|
|
|
std::unique_ptr<DesktopNotificationHistoryCompat> history;
|
|
DesktopNotificationManagerCompat::get_History(&history);
|
|
|
|
auto hr = history->RemoveGroupedTag(reinterpret_cast<const wchar_t *>(callId.utf16()), L"linphone");
|
|
if (FAILED(hr)) {
|
|
qWarning() << "removing toast failed";
|
|
}
|
|
}
|
|
});
|
|
|
|
EventRegistrationToken token;
|
|
toast->add_Activated(Microsoft::WRL::Callback<ITypedEventHandler<ToastNotification *, IInspectable *>>(
|
|
[this, call](IToastNotification *sender, IInspectable *args) -> HRESULT {
|
|
qInfo() << "Toast clicked!";
|
|
// Cast args en IToastActivatedEventArgs
|
|
Microsoft::WRL::ComPtr<IToastActivatedEventArgs> activatedArgs;
|
|
HRESULT hr = args->QueryInterface(IID_PPV_ARGS(&activatedArgs));
|
|
if (SUCCEEDED(hr)) {
|
|
HSTRING argumentsHString;
|
|
activatedArgs->get_Arguments(&argumentsHString);
|
|
|
|
// Convertir HSTRING en wstring
|
|
UINT32 length;
|
|
const wchar_t *rawStr = WindowsGetStringRawBuffer(argumentsHString, &length);
|
|
std::wstring arguments(rawStr, length);
|
|
QString arg = QString::fromStdWString(arguments);
|
|
qInfo() << "Toast activated with args:" << arg;
|
|
|
|
if (arg.compare("accept") == 0) {
|
|
if (call) {
|
|
qDebug() << "Accept call";
|
|
Utils::openCallsWindow(call);
|
|
call->mCore->lAccept(false);
|
|
}
|
|
} else if (arg.compare("decline") == 0) {
|
|
if (call) {
|
|
qDebug() << "Decline call";
|
|
call->mCore->lDecline();
|
|
}
|
|
} else if (arg.isEmpty()) {
|
|
if (call) Utils::openCallsWindow(call);
|
|
}
|
|
|
|
WindowsDeleteString(argumentsHString);
|
|
}
|
|
return S_OK;
|
|
})
|
|
.Get(),
|
|
&token);
|
|
|
|
hr = notifier->Show(toast);
|
|
if (FAILED(hr)) {
|
|
qWarning() << "Toast Show failed:" << Qt::hex << hr;
|
|
}
|
|
|
|
toast->Release();
|
|
doc->Release();
|
|
notifier->Release();
|
|
}
|
|
|
|
void NotificationBackend::sendNotification(NotificationType type, QVariantMap data) {
|
|
if (App::getInstance()->getSessionLocked()) {
|
|
mPendingNotifications.append({type, data});
|
|
return;
|
|
}
|
|
switch (type) {
|
|
case NotificationType::ReceivedCall:
|
|
sendCallNotification(data);
|
|
break;
|
|
case NotificationType::ReceivedMessage:
|
|
sendMessageNotification(data);
|
|
break;
|
|
}
|
|
}
|