diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index c80afd7f0..308759289 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -122,9 +122,9 @@ #include "core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp" #endif // if defined(Q_OS_MACOS) -#if defined(Q_OS_WIN) +#ifdef Q_OS_WIN #include "core/notifier/WindowsNotificationBackend.hpp" -#else +#elif defined(Q_OS_LINUX) #include "core/notifier/SysTrayNotificationBackend.hpp" #endif @@ -569,7 +569,7 @@ void App::setSelf(QSharedPointer(me)) { }); mCoreModelConnection->makeConnectToCore(&App::lForceOidcTimeout, [this] { qDebug() << "App: force oidc timeout"; - mCoreModelConnection->invokeToModel([this] { emit CoreModel::getInstance() -> forceOidcTimeout(); }); + mCoreModelConnection->invokeToModel([this] { emit CoreModel::getInstance()->forceOidcTimeout(); }); }); mCoreModelConnection->makeConnectToModel(&CoreModel::timeoutTimerStarted, [this]() { qDebug() << "App: oidc timer started"; @@ -620,9 +620,11 @@ int App::getEventCount() const { return mEventCountNotifier ? mEventCountNotifier->getEventCount() : 0; } +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) NotificationBackend *App::getNotificationBackend() const { return mNotificationBackend; } +#endif //----------------------------------------------------------- // Initializations @@ -754,7 +756,9 @@ void App::initCore() { mEngine->setObjectOwnership(settings.get(), QQmlEngine::CppOwnership); mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) mNotificationBackend = new NotificationBackend(this); +#endif auto initLists = [this] { if (mCoreStarted) { diff --git a/Linphone/core/App.hpp b/Linphone/core/App.hpp index 771c29627..f440ff650 100644 --- a/Linphone/core/App.hpp +++ b/Linphone/core/App.hpp @@ -43,8 +43,9 @@ class Notifier; class QQuickWindow; class QSystemTrayIcon; class DefaultTranslatorCore; +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) class NotificationBackend; - +#endif class App : public SingleApplication, public AbstractObject { Q_OBJECT Q_PROPERTY(bool coreStarted READ getCoreStarted WRITE setCoreStarted NOTIFY coreStartedChanged) @@ -197,8 +198,9 @@ public: QString getGitBranchName(); QString getSdkVersion(); QString getQtVersion() const; - +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) NotificationBackend *getNotificationBackend() const; +#endif Q_INVOKABLE void checkForUpdate(bool requestedByUser = false); @@ -256,7 +258,9 @@ private: Thread *mLinphoneThread = nullptr; Notifier *mNotifier = nullptr; EventCountNotifier *mEventCountNotifier = nullptr; +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) NotificationBackend *mNotificationBackend = nullptr; +#endif QSystemTrayIcon *mSystemTrayIcon = nullptr; QQuickWindow *mMainWindow = nullptr; QQuickWindow *mCallsWindow = nullptr; diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index be3f844c6..6c9971a38 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -135,7 +135,7 @@ if(WIN32) core/notifier/NotificationActivator.cpp core/notifier/DesktopNotificationManagerCompat.cpp core/notifier/WindowsNotificationBackend.cpp) -else() +elseif (UNIX AND NOT APPLE) list(APPEND _LINPHONEAPP_SOURCES core/notifier/SysTrayNotificationBackend.cpp) endif() diff --git a/Linphone/core/notifier/AbstractNotificationBackend.cpp b/Linphone/core/notifier/AbstractNotificationBackend.cpp index e9e2cf4ec..770059adb 100644 --- a/Linphone/core/notifier/AbstractNotificationBackend.cpp +++ b/Linphone/core/notifier/AbstractNotificationBackend.cpp @@ -1,5 +1,10 @@ #include "AbstractNotificationBackend.hpp" +#include +#include +#include +#include + DEFINE_ABSTRACT_OBJECT(AbstractNotificationBackend) const QHash AbstractNotificationBackend::Notifications = { @@ -8,3 +13,26 @@ const QHash 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; +} \ No newline at end of file diff --git a/Linphone/core/notifier/AbstractNotificationBackend.hpp b/Linphone/core/notifier/AbstractNotificationBackend.hpp index f93e7dc03..033a77662 100644 --- a/Linphone/core/notifier/AbstractNotificationBackend.hpp +++ b/Linphone/core/notifier/AbstractNotificationBackend.hpp @@ -4,8 +4,8 @@ #include "tool/AbstractObject.hpp" #include #include +#include #include - struct ToastButton { QString label; QString argument; @@ -23,6 +23,8 @@ public: AbstractNotificationBackend(QObject *parent = Q_NULLPTR); ~AbstractNotificationBackend() = default; + QString getIconAsPng(const QString &imagePath, const QSize &size = QSize(64, 64)); + enum NotificationType { ReceivedMessage, ReceivedCall diff --git a/Linphone/core/notifier/Notifier.cpp b/Linphone/core/notifier/Notifier.cpp index 9ed0eee00..a06569b04 100644 --- a/Linphone/core/notifier/Notifier.cpp +++ b/Linphone/core/notifier/Notifier.cpp @@ -30,7 +30,11 @@ #include #include "Notifier.hpp" +#ifdef Q_OS_WIN #include "WindowsNotificationBackend.hpp" +#elif defined(Q_OS_LINUX) +#include "SysTrayNotificationBackend.hpp" +#endif #include "core/App.hpp" #include "core/call/CallGui.hpp" @@ -129,8 +133,10 @@ Notifier::~Notifier() { bool Notifier::createNotification(AbstractNotificationBackend::NotificationType type, QVariantMap data) { mMutex->lock(); - // Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber); -#ifdef Q_OS_WIN + mustBeInMainThread(log().arg(Q_FUNC_INFO)); + +// Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber); +#if defined(Q_OS_WIN) auto notifBackend = App::getInstance()->getNotificationBackend(); if (!notifBackend) { @@ -143,98 +149,100 @@ bool Notifier::createNotification(AbstractNotificationBackend::NotificationType if (mInstancesNumber >= MaxNotificationsNumber) { // Check existing instances. qWarning() << QStringLiteral("Unable to create another notification."); - QList allScreens = QGuiApplication::screens(); - if (allScreens.size() > 0) { // Ensure to have a screen to avoid errors - QQuickItem *previousWrapper = nullptr; - bool showAsTool = false; + mMutex->unlock(); + return false; + } + QList allScreens = QGuiApplication::screens(); + if (allScreens.size() > 0) { // Ensure to have a screen to avoid errors + QQuickItem *previousWrapper = nullptr; + bool showAsTool = false; #ifdef Q_OS_MACOS - for (auto w : QGuiApplication::topLevelWindows()) { - if (w->visibility() == QWindow::FullScreen) { - showAsTool = true; - w->raise(); // Used to get focus on Mac (On Mac, A Tool is hidden if the app has not focus and the - // only way to rid it is to use Widget Attributes(Qt::WA_MacAlwaysShowToolWindow) that is not - // available) - } - } -#endif - for (int i = 0; i < allScreens.size(); ++i) { - - ++mInstancesNumber; - // Use QQuickView to create a visual root object that is - // independant from current application Window - QScreen *screen = allScreens[i]; - auto engine = App::getInstance()->mEngine; - const QUrl url(QString(NotificationsPath) + Notifier::Notifications[type].filename); - QObject::connect( - engine, &QQmlApplicationEngine::objectCreated, this, - [this, url, screen, engine, type, data, showAsTool](QObject *obj, const QUrl &objUrl) { - if (!obj && url == objUrl) { - lCritical() << "[App] Notifier.qml couldn't be load."; - engine->deleteLater(); - exit(-1); - } else { - auto window = qobject_cast(obj); - if (window) { - window->setParent(nullptr); - window->setProperty(NotificationPropertyData, data); - window->setScreen(screen); - // Don't use Popup for flags : it could lead to error in geometry. On Mac, Using Tool - // ensure to have the Window on Top and fullscreen independant - window->setFlags((showAsTool ? Qt::Tool : Qt::WindowStaysOnTopHint) | - Qt::FramelessWindowHint); -#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) - window->setFlag(Qt::WindowDoesNotAcceptFocus); -#endif - // for (auto it = data.begin(); it != data.end(); ++it) - // window->setProperty(it.key().toLatin1(), it.value()); - const int timeout = Notifications[type].getTimeout() * 1000; - auto updateNotificationCoordinates = [this, window, screen](int width, int height) { - auto screenHeightOffset = - screen ? mScreenHeightOffset.value(screen->name()) : 0; // Access optimization - QRect availableGeometry = screen->availableGeometry(); - - window->setX(availableGeometry.x() + - (availableGeometry.width() - - width)); //*screen->devicePixelRatio()); when using manual scaler - window->setY(availableGeometry.y() + availableGeometry.height() - - screenHeightOffset - height); - }; - updateNotificationCoordinates(window->width(), window->height()); - auto screenHeightOffset = - screen ? mScreenHeightOffset.take(screen->name()) : 0; // Access optimization - mScreenHeightOffset.insert(screen->name(), screenHeightOffset + window->height()); - QObject::connect(window, &QQuickWindow::closing, window, [this, window] { - qDebug() << "closing notification"; - deleteNotification(QVariant::fromValue(window)); - }); - QObject::connect(window, &QQuickWindow::visibleChanged, window, [this](bool visible) { - lInfo() << log().arg("Notification visible changed") << visible; - }); - QObject::connect(window, &QQuickWindow::activeChanged, window, [this, window] { - lInfo() << log().arg("Notification active changed") << window->isActive(); - }); - QObject::connect(window, &QQuickWindow::visibilityChanged, window, - [this](QWindow::Visibility visibility) { - lInfo() - << log().arg("Notification visibility changed") << visibility; - }); - QObject::connect(window, &QQuickWindow::widthChanged, window, [this](int width) { - lInfo() << log().arg("Notification width changed") << width; - }); - QObject::connect(window, &QQuickWindow::heightChanged, window, [this](int height) { - lInfo() << log().arg("Notification height changed") << height; - }); - lInfo() << QStringLiteral("Create notification:") << window; - showNotification(window, timeout); - } - } - }, - static_cast(Qt::QueuedConnection | Qt::SingleShotConnection)); - lDebug() << log().arg("Engine loading notification"); - engine->load(url); + for (auto w : QGuiApplication::topLevelWindows()) { + if (w->visibility() == QWindow::FullScreen) { + showAsTool = true; + w->raise(); // Used to get focus on Mac (On Mac, A Tool is hidden if the app has not focus and the + // only way to rid it is to use Widget Attributes(Qt::WA_MacAlwaysShowToolWindow) that is not + // available) } } +#endif + for (int i = 0; i < allScreens.size(); ++i) { + + ++mInstancesNumber; + // Use QQuickView to create a visual root object that is + // independant from current application Window + QScreen *screen = allScreens[i]; + auto engine = App::getInstance()->mEngine; + const QUrl url(QString(NotificationsPath) + Notifier::Notifications[type].filename); + QObject::connect( + engine, &QQmlApplicationEngine::objectCreated, this, + [this, url, screen, engine, type, data, showAsTool](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) { + lCritical() << "[App] Notifier.qml couldn't be load."; + engine->deleteLater(); + exit(-1); + } else { + auto window = qobject_cast(obj); + if (window) { + window->setParent(nullptr); + window->setProperty(NotificationPropertyData, data); + window->setScreen(screen); + // Don't use Popup for flags : it could lead to error in geometry. On Mac, Using Tool + // ensure to have the Window on Top and fullscreen independant + window->setFlags((showAsTool ? Qt::Tool : Qt::WindowStaysOnTopHint) | + Qt::FramelessWindowHint); +#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) + window->setFlag(Qt::WindowDoesNotAcceptFocus); +#endif + // for (auto it = data.begin(); it != data.end(); ++it) + // window->setProperty(it.key().toLatin1(), it.value()); + const int timeout = Notifications[type].getTimeout() * 1000; + auto updateNotificationCoordinates = [this, window, screen](int width, int height) { + auto screenHeightOffset = + screen ? mScreenHeightOffset.value(screen->name()) : 0; // Access optimization + QRect availableGeometry = screen->availableGeometry(); + + window->setX(availableGeometry.x() + + (availableGeometry.width() - + width)); //*screen->devicePixelRatio()); when using manual scaler + window->setY(availableGeometry.y() + availableGeometry.height() - screenHeightOffset - + height); + }; + updateNotificationCoordinates(window->width(), window->height()); + auto screenHeightOffset = + screen ? mScreenHeightOffset.take(screen->name()) : 0; // Access optimization + mScreenHeightOffset.insert(screen->name(), screenHeightOffset + window->height()); + QObject::connect(window, &QQuickWindow::closing, window, [this, window] { + qDebug() << "closing notification"; + deleteNotification(QVariant::fromValue(window)); + }); + QObject::connect(window, &QQuickWindow::visibleChanged, window, [this](bool visible) { + lInfo() << log().arg("Notification visible changed") << visible; + }); + QObject::connect(window, &QQuickWindow::activeChanged, window, [this, window] { + lInfo() << log().arg("Notification active changed") << window->isActive(); + }); + QObject::connect(window, &QQuickWindow::visibilityChanged, window, + [this](QWindow::Visibility visibility) { + lInfo() << log().arg("Notification visibility changed") << visibility; + }); + QObject::connect(window, &QQuickWindow::widthChanged, window, [this](int width) { + lInfo() << log().arg("Notification width changed") << width; + }); + QObject::connect(window, &QQuickWindow::heightChanged, window, [this](int height) { + lInfo() << log().arg("Notification height changed") << height; + }); + lInfo() << QStringLiteral("Create notification:") << window; + showNotification(window, timeout); + } + } + }, + static_cast(Qt::QueuedConnection | Qt::SingleShotConnection)); + lDebug() << log().arg("Engine loading notification"); + engine->load(url); + } } + #endif mMutex->unlock(); return true; diff --git a/Linphone/core/notifier/SysTrayNotificationBackend.cpp b/Linphone/core/notifier/SysTrayNotificationBackend.cpp index 944ca8c89..c996c0dfa 100644 --- a/Linphone/core/notifier/SysTrayNotificationBackend.cpp +++ b/Linphone/core/notifier/SysTrayNotificationBackend.cpp @@ -1,41 +1,183 @@ -#include "tool/Utils.hpp" +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * This program 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. + * + * This program 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 this program. If not, see . + */ + +#include "SysTrayNotificationBackend.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/Utils.hpp" +#include "tool/providers/ImageProvider.hpp" + +#include #include +#include +#include + +static constexpr const char *SERVICE = "org.freedesktop.Notifications"; +static constexpr const char *PATH = "/org/freedesktop/Notifications"; +static constexpr const char *INTERFACE = "org.freedesktop.Notifications"; NotificationBackend::NotificationBackend(QObject *parent) : AbstractNotificationBackend(parent) { - // connect(App::getInstance(), &App::sessionLockedChanged, this, [this] { - // if (!App::getInstance()->getSessionLocked()) { - // qDebug() << "Session unlocked, flush pending notifications"; - // flushPendingNotifications(); - // } - // }); -} + mInterface = new QDBusInterface(SERVICE, PATH, INTERFACE, QDBusConnection::sessionBus(), this); -void NotificationBackend::flushPendingNotifications() { - for (const auto ¬if : mPendingNotifications) { - sendNotification(notif.type, notif.data); + if (!mInterface->isValid()) { + qWarning() << "Notification service unavailable:" << mInterface->lastError().message(); + return; } - mPendingNotifications.clear(); + + // Connecte les signaux D-Bus entrants + QDBusConnection::sessionBus().connect(SERVICE, PATH, INTERFACE, "ActionInvoked", this, + SLOT(onActionInvoked(uint, QString))); + + QDBusConnection::sessionBus().connect(SERVICE, PATH, INTERFACE, "NotificationClosed", this, + SLOT(onNotificationClosed(uint, uint))); } void NotificationBackend::sendMessageNotification(QVariantMap data) { } -void NotificationBackend::sendCallNotification(QVariantMap data) { +uint NotificationBackend::sendCallNotification(QVariantMap data) { + if (!mInterface->isValid()) return 0; + + auto displayName = data["displayName"].toString(); + CallGui *call = data["call"].value(); + + // Corps de la notification + QString title = tr("incoming_call"); + + qDebug() << "isValid:" << mInterface->isValid(); + qDebug() << "lastError:" << mInterface->lastError().message(); + + // Hints + QVariantMap hints; + hints["urgency"] = QVariant::fromValue(uchar(2)); // critical + hints["resident"] = true; // reste visible jusqu'à action + hints["x-gnome-priority"] = int(2); // force banner display + hints["x-gnome-stack"] = QString("persistent"); + hints["transient"] = false; + hints["category"] = QString("call.incoming"); + + // Actions : paires (clé, label) + QStringList actions = {"accept", tr("accept_button"), "decline", tr("decline_button")}; + + QString appIcon = getIconAsPng(Utils::getAppIcon("logo").toString()); + // QString appIcon = QString("call-start"); // icône freedesktop standard + + QDBusReply reply = mInterface->call(QString("Notify"), + APPLICATION_NAME, // app_name + uint(mActiveCallNotifId), // replaces_id (0 = nouvelle notif) + appIcon, // app_icon (nom d'icône freedesktop) + title, displayName, actions, hints, + int(-1) // expire_timeout (-1 = jamais) + ); + + if (!reply.isValid()) { + qWarning() << "Notify() failed:" << reply.error().message(); + return 0; + } + + uint id = reply.value(); + mActiveCallNotifId = id; + + mCurrentNotifications.insert({id}, {AbstractNotificationBackend::NotificationType::ReceivedCall, data}); + connect(call->mCore.get(), &CallCore::stateChanged, this, [this, call, id] { + if (call->mCore->getState() == LinphoneEnums::CallState::End || + call->mCore->getState() == LinphoneEnums::CallState::Error) { + qDebug() << "Call ended or error, remove toast"; + auto callId = call->mCore->getCallId(); + call->deleteLater(); + closeNotification(id); + } + }); + qDebug() << "Notification d'appel envoyée, id =" << id; + + // qDebug() << "Reply valid:" << reply.isValid(); + // qDebug() << "Reply value:" << reply.value(); + // qDebug() << "Reply error:" << reply.error().name() << reply.error().message(); + // qDebug() << "Interface valid:" << mInterface->isValid(); + // qDebug() << "Interface service:" << mInterface->service(); + + return id; +} + +void NotificationBackend::closeNotification(uint id) { + if (!mInterface->isValid()) { + qWarning() << "invalid interface, return"; + return; + } + mInterface->call("CloseNotification", id); + mCurrentNotifications.remove(id); + + if (mActiveCallNotifId == id) mActiveCallNotifId = 0; +} + +void NotificationBackend::onActionInvoked(uint id, const QString &actionKey) { + if (!mCurrentNotifications.contains(id)) return; // pas notre notification + + qDebug() << "Action invoquée — id:" << id << "key:" << actionKey; + auto notif = mCurrentNotifications.value(id); + if (notif.type == AbstractNotificationBackend::NotificationType::ReceivedCall) { + auto callGui = notif.data["call"].value(); + if (!callGui) { + qWarning() << "Could not retrieve call associated to notification, return"; + return; + } + if (actionKey == "accept") { + qDebug() << "Accept call"; + Utils::openCallsWindow(callGui); + callGui->mCore->lAccept(false); + } else if (actionKey == "decline") { + qDebug() << "Decline call"; + callGui->mCore->lDecline(); + } + } else if (notif.type == AbstractNotificationBackend::NotificationType::ReceivedMessage) { + } + + qDebug() << "Close notification"; + mCurrentNotifications.remove(id); + closeNotification(id); +} + +void NotificationBackend::onNotificationClosed(uint id, uint reason) { + // Raisons : 1=expired, 2=dismissed, 3=CloseNotification(), 4=undefined + if (mCurrentNotifications.contains(id)) { + qDebug() << "Notification fermée — id:" << id << "raison:" << reason; + mCurrentNotifications.remove(id); + if (mActiveCallNotifId == id) mActiveCallNotifId = 0; + emit notificationClosed(id, reason); + + auto notif = mCurrentNotifications.value(id); + if (notif.type == AbstractNotificationBackend::NotificationType::ReceivedCall) { + auto callGui = notif.data["call"].value(); + if (!callGui) { + qWarning() << "Could not retrieve call associated to notification, return"; + return; + } + callGui->mCore->lDecline(); + } + } } void NotificationBackend::sendNotification(NotificationType type, QVariantMap data) { - // if (App::getInstance()->getSessionLocked()) { - // mPendingNotifications.append({type, data}); - // return; - // } switch (type) { case NotificationType::ReceivedCall: sendCallNotification(data); @@ -45,3 +187,98 @@ void NotificationBackend::sendNotification(NotificationType type, QVariantMap da break; } } + +// ============================================================================= + +namespace { +constexpr char ServiceName[] = "org.freedesktop.Notifications"; +constexpr char ServicePath[] = "/org/freedesktop/Notifications"; +} // namespace + +QDBusArgument &operator<<(QDBusArgument &arg, const QImage &image) { + QImage scaledImage; + if (!image.isNull()) { + scaledImage = image.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (scaledImage.format() != QImage::Format_ARGB32) + scaledImage = scaledImage.convertToFormat(QImage::Format_ARGB32); + scaledImage = scaledImage.rgbSwapped(); + } + + const int channels = 4; // ARGB32 has 4 channels + + arg.beginStructure(); + arg << scaledImage.width(); + arg << scaledImage.height(); + arg << scaledImage.bytesPerLine(); + arg << true; // ARGB32 has alpha + arg << scaledImage.depth() / channels; + arg << channels; + arg << QByteArray::fromRawData((const char *)scaledImage.constBits(), + scaledImage.height() * scaledImage.bytesPerLine()); + arg.endStructure(); + + return arg; +} +const QDBusArgument &operator>>(const QDBusArgument &arg, QImage &image) { + Q_UNUSED(image) + return arg; +} + +// static void openUrl(QFileInfo info) { +// bool showDirectory = showDirectory || !info.exists(); +// if (!QDesktopServices::openUrl( +// QUrl(QStringLiteral("file:///%1").arg(showDirectory ? info.absolutePath() : info.absoluteFilePath()))) && +// !showDirectory) { +// QDesktopServices::openUrl(QUrl(QStringLiteral("file:///%1").arg(info.absolutePath()))); +// } +// } + +// void NotificationsDBus::onNotificationClosed(quint32 id, quint32 reason) { +// if (!mProcessed) { // Is was closed from system. +// if (reason != 2) +// qWarning() << "Notification has been closed by system. If this is an issue, please deactivate native " +// "notifications [" +// << id << reason << "]"; +// // open();// Not a workaround because of infinite openning loop. +// } +// } + +// // QDBusMessage +// NotificationsDBus::createMessage(const QString &title, const QString &message, QVariantMap hints, QStringList +// actions) { + +// QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Notifications", "/org/freedesktop/Notifications", +// "org.freedesktop.Notifications", "Notify"); +// hints["urgency"] = 2; // if not 2, it can be timeout without taking account of custom timeout +// hints["category"] = "im"; +// // hints["resident"] = true; +// hints["transient"] = true; +// // hints["desktop-entry"] = "com.belledonnecommunications.linphone"; +// hints["suppress-sound"] = true; + +// msg << APPLICATION_NAME; // Application name +// msg << quint32(0); // ID +// msg << ""; // Icon to display +// msg << APPLICATION_NAME + QString(": ") + title; // Summary / Header of the message to display +// msg << message; // Body of the message to display +// msg << actions; // Actions from which the user may choose +// msg << hints; // Hints to the server displaying the message +// msg << qint32(0); // Timeout in milliseconds + +// return msg; +// } + +// void NotificationsDBus::open() { +// QDBusPendingReply asyncReply(QDBusConnection::sessionBus().asyncCall( +// mMessage)); // Would return a message containing the id of this notification +// asyncReply.waitForFinished(); +// if (asyncReply.isValid()) mId = asyncReply.argumentAt(0).toInt(); +// else qWarning() << asyncReply.error(); +// } + +// void NotificationsDBus::closeNotification() { +// QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.Notifications", "/org/freedesktop/Notifications", +// "org.freedesktop.Notifications", "CloseNotification"); +// msg << quint32(mId); +// QDBusConnection::sessionBus().call(msg); +// } diff --git a/Linphone/core/notifier/SysTrayNotificationBackend.hpp b/Linphone/core/notifier/SysTrayNotificationBackend.hpp index c4a91be83..d7062432e 100644 --- a/Linphone/core/notifier/SysTrayNotificationBackend.hpp +++ b/Linphone/core/notifier/SysTrayNotificationBackend.hpp @@ -2,16 +2,21 @@ #define SYSTRAYNOTIFICATIONBACKEND_HPP #include "AbstractNotificationBackend.hpp" + +#include +#include #include #include #include +#include #include +#include class NotificationBackend : public AbstractNotificationBackend { Q_OBJECT public: - struct PendingNotification { + struct CurrentNotification { NotificationType type; QVariantMap data; }; @@ -19,19 +24,25 @@ public: NotificationBackend(QObject *parent = nullptr); ~NotificationBackend() = default; - void sendCallNotification(QVariantMap data); + uint sendCallNotification(QVariantMap data); + void closeNotification(uint id); void sendMessageNotification(QVariantMap data); void sendNotification(NotificationType type, QVariantMap data) override; - void flushPendingNotifications(); - signals: void toastButtonTriggered(const QString &arg); void sessionLockedChanged(bool locked); + void notificationClosed(uint id, uint reason); + +private slots: + void onActionInvoked(uint id, const QString &actionKey); + void onNotificationClosed(uint id, uint reason); private: - QList mPendingNotifications; + QDBusInterface *mInterface = nullptr; + uint mActiveCallNotifId = 0; + QMap mCurrentNotifications; // IDs des notifs d'appel actives }; #endif // SYSTRAYNOTIFICATIONBACKEND_HPP diff --git a/Linphone/core/notifier/WindowsNotificationBackend.cpp b/Linphone/core/notifier/WindowsNotificationBackend.cpp index ad8a968a7..2be603804 100644 --- a/Linphone/core/notifier/WindowsNotificationBackend.cpp +++ b/Linphone/core/notifier/WindowsNotificationBackend.cpp @@ -1,6 +1,3 @@ -#include "tool/Utils.hpp" - -// #include "NotificationActivator.hpp" #include "WindowsNotificationBackend.hpp" #include "core/App.hpp" #include "core/call/CallGui.hpp" @@ -9,16 +6,11 @@ #include "tool/Constants.hpp" #include "tool/Utils.hpp" -#ifdef Q_OS_WIN #include "DesktopNotificationManagerCompat.hpp" #include #include -#endif #include -#include -#include -#include using namespace Microsoft::WRL; using namespace ABI::Windows::UI::Notifications; @@ -41,29 +33,6 @@ void NotificationBackend::flushPendingNotifications() { 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; @@ -221,8 +190,9 @@ void NotificationBackend::sendCallNotification(QVariantMap data) { 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"; + if (call->mCore->getState() == LinphoneEnums::CallState::End || + call->mCore->getState() == LinphoneEnums::CallState::Error) { + qDebug() << "Call ended or error, remove toast"; auto callId = call->mCore->getCallId(); call->deleteLater(); diff --git a/Linphone/core/notifier/WindowsNotificationBackend.hpp b/Linphone/core/notifier/WindowsNotificationBackend.hpp index f2f7c12ad..8ec6d006f 100644 --- a/Linphone/core/notifier/WindowsNotificationBackend.hpp +++ b/Linphone/core/notifier/WindowsNotificationBackend.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include class NotificationBackend : public AbstractNotificationBackend { diff --git a/Linphone/tool/thread/SafeConnection.hpp b/Linphone/tool/thread/SafeConnection.hpp index d7055f7a7..86b333575 100644 --- a/Linphone/tool/thread/SafeConnection.hpp +++ b/Linphone/tool/thread/SafeConnection.hpp @@ -154,6 +154,7 @@ public: } bool tryLock() { + if (!this) return false; mLocker.lock(); auto coreLocked = mCore.lock(); auto modelLocked = mModel.lock(); diff --git a/Linphone/view/Page/Layout/Main/MainLayout.qml b/Linphone/view/Page/Layout/Main/MainLayout.qml index 41485ae0e..c81c552b8 100644 --- a/Linphone/view/Page/Layout/Main/MainLayout.qml +++ b/Linphone/view/Page/Layout/Main/MainLayout.qml @@ -529,6 +529,7 @@ Item { text: qsTr("recordings_title") icon.source: AppIcons.recordFill onClicked: { + settingsMenuButton.popup.close() UtilsCpp.openNativeDialog(UtilsCpp.getCaptureDirpaths()) // fileDialog.folder = UtilsCpp.getCaptureDirpaths() // fileDialog.open()