improve windows notification ui

This commit is contained in:
gaelle 2026-04-03 13:14:26 +02:00
parent e08112d8c8
commit 49900c5a91
10 changed files with 66 additions and 54 deletions

View file

@ -1,10 +1,5 @@
#include "AbstractNotificationBackend.hpp"
#include <QFileInfo>
#include <QPainter>
#include <QStandardPaths>
#include <QSvgRenderer>
DEFINE_ABSTRACT_OBJECT(AbstractNotificationBackend)
const QHash<int, AbstractNotificationBackend::Notification> AbstractNotificationBackend::Notifications = {
@ -13,26 +8,3 @@ const QHash<int, AbstractNotificationBackend::Notification> AbstractNotification
AbstractNotificationBackend::AbstractNotificationBackend(QObject *parent) : QObject(parent) {
}
QString AbstractNotificationBackend::getIconAsPng(const QString &imagePath, const QSize &size) {
// 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;
}

View file

@ -23,8 +23,6 @@ public:
AbstractNotificationBackend(QObject *parent = Q_NULLPTR);
~AbstractNotificationBackend() = default;
QString getIconAsPng(const QString &imagePath, const QSize &size = QSize(64, 64));
enum NotificationType {
ReceivedMessage,
ReceivedCall

View file

@ -43,7 +43,7 @@ using namespace Microsoft::WRL::Wrappers;
namespace DesktopNotificationManagerCompat {
HRESULT RegisterComServer(GUID clsid, const wchar_t exePath[]);
HRESULT RegisterAumidInRegistry(const wchar_t *aumid);
HRESULT RegisterAumidInRegistry(const wchar_t *aumid, const wchar_t *iconPath = nullptr);
HRESULT EnsureRegistered();
bool IsRunningAsUwp();
@ -100,7 +100,7 @@ HRESULT CreateStartMenuShortcut(const wchar_t *aumid, GUID clsid) {
return persistFile->Save(shortcutPath, TRUE);
}
HRESULT RegisterAumidInRegistry(const wchar_t *aumid) {
HRESULT RegisterAumidInRegistry(const wchar_t *aumid, const wchar_t *iconPath) {
std::wstring keyPath = std::wstring(L"Software\\Classes\\AppUserModelId\\") + aumid;
HKEY key;
@ -114,11 +114,16 @@ HRESULT RegisterAumidInRegistry(const wchar_t *aumid) {
res = ::RegSetValueExW(key, L"DisplayName", 0, REG_SZ, reinterpret_cast<const BYTE *>(displayName),
static_cast<DWORD>((wcslen(displayName) + 1) * sizeof(wchar_t)));
if (iconPath != nullptr) {
res = ::RegSetValueExW(key, L"IconUri", 0, REG_SZ, reinterpret_cast<const BYTE *>(iconPath),
static_cast<DWORD>((wcslen(iconPath) + 1) * sizeof(wchar_t)));
}
::RegCloseKey(key);
return HRESULT_FROM_WIN32(res);
}
HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid) {
HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid, const wchar_t *iconPath) {
// If running as Desktop Bridge
qDebug() << QString("CLSID : {%1-%2-%3-%4%5-%6%7%8%9%10%11}")
.arg(clsid.Data1, 8, 16, QChar('0'))
@ -168,7 +173,7 @@ HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid) {
RETURN_IF_FAILED(RegisterComServer(clsid, exePath));
qInfo() << "Register aumid in registry";
RETURN_IF_FAILED(RegisterAumidInRegistry(aumid));
RETURN_IF_FAILED(RegisterAumidInRegistry(aumid, iconPath));
s_registeredAumidAndComServer = true;
return S_OK;

View file

@ -34,7 +34,7 @@ HRESULT CreateStartMenuShortcut(const wchar_t *aumid, GUID clsid);
/// </summary>
/// <param name="aumid">An AUMID that uniquely identifies your application.</param>
/// <param name="clsid">The CLSID of your NotificationActivator class.</param>
HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid);
HRESULT RegisterAumidAndComServer(const wchar_t *aumid, GUID clsid, const wchar_t *iconPath = nullptr);
/// <summary>
/// Registers your module to handle COM activations. Call this upon application startup.

View file

@ -349,17 +349,19 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
auto callLog = call->getCallLog();
auto displayName = callLog && callLog->getConferenceInfo()
? Utils::coreStringToAppString(callLog->getConferenceInfo()->getSubject())
: ToolModel::getDisplayName(call->getRemoteAddress());
: ToolModel::getDisplayName(remoteAddress);
auto remoteAddrString = Utils::coreStringToAppString(remoteAddress->asStringUriOnly());
// Accessibility alert
//: New call from %1
AccessibilityHelper::announceMessage(tr("new_call_alert_accessible_name").arg(displayName));
App::postCoreAsync([this, gui, displayName]() {
App::postCoreAsync([this, gui, displayName, remoteAddrString]() {
mustBeInMainThread(getClassName());
QVariantMap map;
map["displayName"].setValue(displayName);
map["remoteAddress"].setValue(remoteAddrString);
map["call"].setValue(gui);
CREATE_NOTIFICATION(AbstractNotificationBackend::ReceivedCall, map)

View file

@ -79,7 +79,7 @@ uint NotificationBackend::sendCallNotification(QVariantMap data) {
// Actions : paires (clé, label)
QStringList actions = {"accept", tr("accept_button"), "decline", tr("decline_button")};
QString appIcon = getIconAsPng(Utils::getAppIcon("logo").toString());
QString appIcon = Utils::getIconAsPng(Utils::getAppIcon("logo").toString());
// QString appIcon = QString("call-start"); // icône freedesktop standard
QDBusReply<uint> reply = mInterface->call(QString("Notify"),

View file

@ -46,6 +46,7 @@ void NotificationBackend::sendMessageNotification(QVariantMap data) {
auto remoteAddress = data["remoteAddress"].toString().toStdWString();
auto chatRoomName = data["chatRoomName"].toString().toStdWString();
auto chatRoomAddress = data["chatRoomAddress"].toString().toStdWString();
auto appIcon = Utils::getIconAsPng(Utils::getAppIcon("logo").toString()).toStdWString();
auto avatarUri = data["avatarUri"].toString().toStdWString();
bool isGroup = data["isGroupChat"].toBool();
ChatGui *chat = data["chat"].value<ChatGui *>();
@ -53,6 +54,9 @@ void NotificationBackend::sendMessageNotification(QVariantMap data) {
std::wstring xml = L"<toast>"
L" <visual>"
L" <binding template=\"ToastGeneric\">"
L" <image src=\"file:///" +
appIcon +
L"\" placement=\"appLogoOverride\"/>"
L" <text><![CDATA[" +
chatRoomName +
L"]]></text>"
@ -82,7 +86,7 @@ void NotificationBackend::sendMessageNotification(QVariantMap data) {
IToastNotification *toast = nullptr;
hr = DesktopNotificationManagerCompat::CreateToastNotification(doc, &toast);
if (FAILED(hr) || !toast) {
qWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
lWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
doc->Release();
notifier->Release();
Utils::showInformationPopup(tr("info_popup_error_title"), tr("info_popup_error_creating_notification"), false);
@ -103,7 +107,7 @@ void NotificationBackend::sendMessageNotification(QVariantMap data) {
hr = notifier->Show(toast);
if (FAILED(hr)) {
qWarning() << "Toast Show failed:" << Qt::hex << hr;
lWarning() << "Toast Show failed:" << Qt::hex << hr;
}
toast->Release();
@ -121,6 +125,7 @@ void NotificationBackend::sendCallNotification(QVariantMap data) {
}
auto displayName = data["displayName"].toString().toStdWString();
auto remoteAddress = data["remoteAddress"].toString().toStdWString();
CallGui *call = data["call"].value<CallGui *>();
int timeout = 2;
// AbstractNotificationBackend::Notifications[(int)NotificationType::ReceivedCall].getTimeout();
@ -129,8 +134,9 @@ void NotificationBackend::sendCallNotification(QVariantMap data) {
auto callDescription = tr("incoming_call").toStdWString();
QList<ToastButton> actions;
QString declineIcon = getIconAsPng(Utils::getAppIcon("endCall").toString());
QString acceptIcon = getIconAsPng(Utils::getAppIcon("phone").toString());
QString declineIcon = Utils::getIconAsPng(Utils::getAppIcon("endCall").toString());
QString acceptIcon = Utils::getIconAsPng(Utils::getAppIcon("phone").toString());
auto appIcon = Utils::getIconAsPng(Utils::getAppIcon("logo").toString()).toStdWString();
actions.append(ToastButton(tr("accept_button"), "accept", acceptIcon));
actions.append(ToastButton(tr("decline_button"), "decline", declineIcon));
std::wstring wActions;
@ -150,9 +156,15 @@ void NotificationBackend::sendCallNotification(QVariantMap data) {
std::wstring xml = L"<toast scenario=\"reminder\">"
L" <visual>"
L" <binding template=\"ToastGeneric\">"
L" <image src=\"file:///" +
appIcon +
L"\" placement=\"appLogoOverride\"/>"
L" <text hint-style=\"header\">" +
displayName +
L"</text>"
L" <text hint-style=\"base\">" +
remoteAddress +
L"</text>"
L" <text hint-style=\"body\">" +
callDescription +
L"</text>"
@ -173,7 +185,7 @@ void NotificationBackend::sendCallNotification(QVariantMap data) {
IToastNotification *toast = nullptr;
hr = DesktopNotificationManagerCompat::CreateToastNotification(doc, &toast);
if (FAILED(hr) || !toast) {
qWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
lWarning() << "CreateToastNotification failed:" << Qt::hex << hr;
doc->Release();
notifier->Release();
Utils::showInformationPopup(tr("info_popup_error_title"), tr("info_popup_error_creating_notification"), false);
@ -182,12 +194,12 @@ void NotificationBackend::sendCallNotification(QVariantMap data) {
ComPtr<IToastNotification2> toast2;
hr = toast->QueryInterface(IID_PPV_ARGS(&toast2));
if (FAILED(hr)) qWarning() << "QueryInterface failed";
if (FAILED(hr)) lWarning() << "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";
if (FAILED(hr)) lWarning() << "puting tag on toast failed";
connect(call->mCore.get(), &CallCore::stateChanged, this, [this, call, notifier, toast] {
if (call->mCore->getState() == LinphoneEnums::CallState::End ||
@ -201,7 +213,7 @@ void NotificationBackend::sendCallNotification(QVariantMap data) {
auto hr = history->RemoveGroupedTag(reinterpret_cast<const wchar_t *>(callId.utf16()), L"linphone");
if (FAILED(hr)) {
qWarning() << "removing toast failed";
lWarning() << "removing toast failed";
}
}
});
@ -248,7 +260,7 @@ void NotificationBackend::sendCallNotification(QVariantMap data) {
hr = notifier->Show(toast);
if (FAILED(hr)) {
qWarning() << "Toast Show failed:" << Qt::hex << hr;
lWarning() << "Toast Show failed:" << Qt::hex << hr;
}
toast->Release();

View file

@ -31,7 +31,7 @@ FILE *gStream = NULL;
#define WIDEN2(x) L##x
#define WIDEN(x) WIDEN2(x)
static const wchar_t *mAumid = WIDEN(APPLICATION_ID);
static const wchar_t *mAumid = WIDEN(APPLICATION_NAME);
void cleanStream() {
#ifdef _WIN32
@ -69,13 +69,8 @@ int main(int argc, char *argv[]) {
HRESULT hrCom = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
qInfo() << "CoInitializeEx STA result:" << Qt::hex << hrCom;
auto hr = DesktopNotificationManagerCompat::CreateStartMenuShortcut(mAumid, __uuidof(NotificationActivator));
if (FAILED(hr)) {
qWarning() << "CreateStartMenuShortcut failed:" << Qt::hex << hr;
}
// Register AUMID and COM server (for a packaged app, this is a no-operation)
hr = DesktopNotificationManagerCompat::RegisterAumidAndComServer(L"Linphone", __uuidof(NotificationActivator));
auto hr = DesktopNotificationManagerCompat::RegisterAumidAndComServer(mAumid, __uuidof(NotificationActivator));
if (FAILED(hr)) {
qWarning() << "RegisterAumidAndComServer failed:" << Qt::hex << hr;
}

View file

@ -46,15 +46,19 @@
#include <QClipboard>
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QFileInfo>
#include <QHostAddress>
#include <QImageReader>
#include <QMimeDatabase>
#include <QPainter>
#include <QProcess>
#include <QQmlComponent>
#include <QQmlProperty>
#include <QQuickWindow>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QSvgRenderer>
#ifdef Q_OS_WIN
#ifndef NOMINMAX
@ -1815,6 +1819,29 @@ QUrl Utils::getAppIcon(const QString &iconName) {
return QQmlProperty::read(appIconsSingleton, iconName).value<QUrl>();
}
QString Utils::getIconAsPng(const QString &imagePath, const QSize &size) {
// 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;
}
QColor Utils::getPresenceColor(LinphoneEnums::Presence presence) {
mustBeInMainThread(sLog().arg(Q_FUNC_INFO));
QColor presenceColor = QColorConstants::Transparent;

View file

@ -254,6 +254,7 @@ public:
// Presence
static QString getIconAsPng(const QString &imagePath, const QSize &size = QSize(64, 64));
static QColor getDefaultStyleColor(const QString &colorName);
static QUrl getAppIcon(const QString &iconName);
static QUrl getRegistrationStateIcon(LinphoneEnums::RegistrationState state);