linux system notification (stick to custom notification for now because gnome does not display notification banner when app is active)

This commit is contained in:
Gaelle Braud 2026-04-02 17:35:16 +02:00 committed by gaelle
parent 724eb72b94
commit 9ef9953238
12 changed files with 421 additions and 154 deletions

View file

@ -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<App>(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) {

View file

@ -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;

View file

@ -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()

View file

@ -1,5 +1,10 @@
#include "AbstractNotificationBackend.hpp"
#include <QFileInfo>
#include <QPainter>
#include <QStandardPaths>
#include <QSvgRenderer>
DEFINE_ABSTRACT_OBJECT(AbstractNotificationBackend)
const QHash<int, AbstractNotificationBackend::Notification> AbstractNotificationBackend::Notifications = {
@ -8,3 +13,26 @@ 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

@ -4,8 +4,8 @@
#include "tool/AbstractObject.hpp"
#include <QHash>
#include <QObject>
#include <QSize>
#include <QString>
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

View file

@ -30,7 +30,11 @@
#include <QTimer>
#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<QScreen *> 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<QScreen *> 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<QQuickWindow *>(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::ConnectionType>(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<QQuickWindow *>(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::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
lDebug() << log().arg("Engine loading notification");
engine->load(url);
}
}
#endif
mMutex->unlock();
return true;

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <QDBusReply>
#include <QDebug>
#include <QQuickItem>
#include <QQuickWindow>
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 &notif : 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<CallGui *>();
// 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<uint> 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<CallGui *>();
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<CallGui *>();
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<quint32> 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);
// }

View file

@ -2,16 +2,21 @@
#define SYSTRAYNOTIFICATIONBACKEND_HPP
#include "AbstractNotificationBackend.hpp"
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDebug>
#include <QLocalServer>
#include <QLocalSocket>
#include <QObject>
#include <QString>
#include <QVariantMap>
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<PendingNotification> mPendingNotifications;
QDBusInterface *mInterface = nullptr;
uint mActiveCallNotifId = 0;
QMap<uint, CurrentNotification> mCurrentNotifications; // IDs des notifs d'appel actives
};
#endif // SYSTRAYNOTIFICATIONBACKEND_HPP

View file

@ -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 <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;
@ -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();

View file

@ -5,6 +5,7 @@
#include <QDebug>
#include <QLocalServer>
#include <QLocalSocket>
#include <QObject>
#include <QString>
class NotificationBackend : public AbstractNotificationBackend {

View file

@ -154,6 +154,7 @@ public:
}
bool tryLock() {
if (!this) return false;
mLocker.lock();
auto coreLocked = mCore.lock();
auto modelLocked = mModel.lock();

View file

@ -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()