diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt index 2edfbae96..9440436ff 100644 --- a/linphone-app/CMakeLists.txt +++ b/linphone-app/CMakeLists.txt @@ -235,6 +235,7 @@ set(SOURCES src/components/other/colors/ColorListModel.cpp src/components/other/colors/ColorProxyModel.cpp src/components/other/colors/ImageColorsProxyModel.cpp + src/components/other/desktop-tools/notifications/NotificationsDefault.cpp src/components/other/images/ImageModel.cpp src/components/other/images/ImageListModel.cpp src/components/other/images/ImageProxyModel.cpp @@ -376,6 +377,7 @@ set(HEADERS src/components/other/images/ImageListModel.hpp src/components/other/images/ImageProxyModel.hpp src/components/other/desktop-tools/DesktopTools.hpp + src/components/other/desktop-tools/notifications/NotificationsDefault.hpp src/components/other/text-to-speech/TextToSpeech.hpp src/components/other/timeZone/TimeZoneModel.hpp src/components/other/timeZone/TimeZoneListModel.hpp @@ -460,6 +462,7 @@ else () src/app/single-application/SingleApplicationDBus.cpp src/components/core/event-count-notifier/EventCountNotifierSystemTrayIcon.cpp src/components/other/desktop-tools/DesktopToolsLinux.cpp + src/components/other/desktop-tools/notifications/NotificationsDBus.cpp src/components/other/desktop-tools/screen-saver/ScreenSaverDBus.cpp src/components/other/desktop-tools/screen-saver/ScreenSaverXdg.cpp ) @@ -467,6 +470,7 @@ else () src/app/single-application/SingleApplicationDBusPrivate.hpp src/components/core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp src/components/other/desktop-tools/DesktopToolsLinux.hpp + src/components/other/desktop-tools/notifications/NotificationsDBus.hpp src/components/other/desktop-tools/screen-saver/ScreenSaverDBus.hpp src/components/other/desktop-tools/screen-saver/ScreenSaverXdg.hpp ) diff --git a/linphone-app/src/app/App.cpp b/linphone-app/src/app/App.cpp index 13b1ec968..d3a27daec 100644 --- a/linphone-app/src/app/App.cpp +++ b/linphone-app/src/app/App.cpp @@ -1110,6 +1110,10 @@ void App::checkForUpdate() { checkForUpdates(false); } void App::checkForUpdates(bool force) { +#ifdef DEBUG + if(force) + App::getInstance()->getNotifier()->notifyNewVersionAvailable("5.42.5","https://linphone.org"); +#endif if(force || CoreManager::getInstance()->getSettingsModel()->isCheckForUpdateEnabled()) CoreManager::getInstance()->getCore()->checkForUpdate( Utils::appStringToCoreString(applicationVersion()) diff --git a/linphone-app/src/components/notifier/Notifier.cpp b/linphone-app/src/components/notifier/Notifier.cpp index 13f8bab07..bd45f7962 100644 --- a/linphone-app/src/components/notifier/Notifier.cpp +++ b/linphone-app/src/components/notifier/Notifier.cpp @@ -31,6 +31,8 @@ #include "components/core/CoreManager.hpp" #include "components/timeline/TimelineModel.hpp" #include "components/timeline/TimelineListModel.hpp" +#include "components//other/desktop-tools/notifications/NotificationsDefault.hpp" +#include "components//other/desktop-tools/notifications/NotificationsDBus.hpp" #include "utils/Utils.hpp" #include "Notifier.hpp" @@ -48,33 +50,15 @@ namespace { constexpr char NotificationShowMethodName[] = "open"; - constexpr char NotificationPropertyData[] = "notificationData"; - - constexpr char NotificationPropertyX[] = "popupX"; - constexpr char NotificationPropertyY[] = "popupY"; - - constexpr char NotificationPropertyWindow[] = "__internalWindow"; - constexpr char NotificationPropertyTimer[] = "__timer"; // --------------------------------------------------------------------------- // Arbitrary hardcoded values. // --------------------------------------------------------------------------- - constexpr int NotificationSpacing = 10; constexpr int MaxNotificationsNumber = 5; } -// ============================================================================= - -template -void setProperty (QObject &object, const char *property, const T &value) { - if (!object.setProperty(property, QVariant(value))) { - qWarning() << QStringLiteral("Unable to set property: `%1`.").arg(property); - abort(); - } -} - // ============================================================================= // Available notifications. // ============================================================================= @@ -121,7 +105,7 @@ Notifier::~Notifier () { // ----------------------------------------------------------------------------- QObject *Notifier::createNotification (Notifier::NotificationType type, QVariantMap data) { - QQuickItem *wrapperItem = nullptr; + QObject *wrapperItem = nullptr; mMutex->lock(); Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber); if (mInstancesNumber == MaxNotificationsNumber) { // Check existing instances. @@ -129,74 +113,17 @@ QObject *Notifier::createNotification (Notifier::NotificationType type, QVariant mMutex->unlock(); return nullptr; } - QList allScreens = QGuiApplication::screens(); - if(allScreens.size() > 0){ // Ensure to have a screen to avoid errors - QQuickItem * previousWrapper = nullptr; - ++mInstancesNumber; - bool showAsTool = false; -#ifdef Q_OS_MACOS - for(auto w : QGuiApplication::topLevelWindows()){ - if( (w->windowState()&Qt::WindowFullScreen)==Qt::WindowFullScreen){ - 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) - } - } +#ifdef _WIN32 +#elif defined(__APPLE__) +#else + wrapperItem = NotificationsDBus::create(type, data); #endif - for(int i = 0 ; i < allScreens.size() ; ++i){ - QQuickView *view = new QQuickView(App::getInstance()->getEngine(), nullptr); // Use QQuickView to create a visual root object that is independant from current application Window - QScreen *screen = allScreens[i]; - QObject::connect(view, &QQuickView::statusChanged, [allScreens](QQuickView::Status status){ // Debug handler : show screens descriptions on Error - if( status == QQuickView::Error){ - QScreen * primaryScreen = QGuiApplication::primaryScreen(); - qInfo() << "Primary screen : " << primaryScreen->geometry() << primaryScreen->availableGeometry() << primaryScreen->virtualGeometry() << primaryScreen->availableVirtualGeometry(); - for(int i = 0 ; i < allScreens.size() ; ++i){ - QScreen *screen = allScreens[i]; - qInfo() << QString("Screen [")+QString::number(i)+"] (hdpi, Geometry, Available, Virtual, AvailableGeometry) :" - << screen->devicePixelRatio() << screen->geometry() << screen->availableGeometry() << screen->virtualGeometry() << screen->availableVirtualGeometry(); - } - } - }); - view->setScreen(screen); // Bind the visual root object to the screen - view->setProperty("flags", QVariant(Qt::BypassWindowManagerHint | Qt::WindowStaysOnBottomHint | Qt::CustomizeWindowHint | Qt::X11BypassWindowManagerHint)); // Set the visual ghost window - view->setSource(QString(NotificationsPath)+Notifier::Notifications[type].filename); - - QQuickWindow *subWindow = view->findChild("__internalWindow"); - QObject::connect(subWindow, &QObject::destroyed, view, &QObject::deleteLater); // When destroying window, detroy visual root object too - - int * screenHeightOffset = &mScreenHeightOffset[screen->name()]; // Access optimization - QRect availableGeometry = screen->availableGeometry(); - int heightOffset = availableGeometry.y() + (availableGeometry.height() - subWindow->height());//*screen->devicePixelRatio(); when using manual scaler - if(showAsTool) - subWindow->setProperty("showAsTool",true); - subWindow->setX(availableGeometry.x()+ (availableGeometry.width()-subWindow->property("width").toInt()));//*screen->devicePixelRatio()); when using manual scaler - subWindow->setY(heightOffset-(*screenHeightOffset % heightOffset)); - - *screenHeightOffset = (subWindow->height() + *screenHeightOffset) + NotificationSpacing; - if (*screenHeightOffset - heightOffset + availableGeometry.y() >= 0) - *screenHeightOffset = 0; - - // if(primaryScreen != screen){ //Useful when doing manual scaling jobs. Need to implement scaler in GUI objects - // //subwindow->setProperty("xScale", (double)screen->availableVirtualGeometry().width()/availableGeometry.width() ); - // //subwindow->setProperty("yScale", (double)screen->availableVirtualGeometry().height()/availableGeometry.height()); - // } - wrapperItem = view->findChild("__internalWrapper"); - ::setProperty(*wrapperItem, NotificationPropertyData,data); - view->setGeometry(subWindow->geometry()); // Ensure to have sufficient space to both let painter do job without error, and stay behind popup - - if(previousWrapper!=nullptr){ // Link objects in order to propagate events without having to store them - QObject::connect(previousWrapper, SIGNAL(deleteNotification(QVariant)), wrapperItem,SLOT(deleteNotificationSlot())); - QObject::connect(wrapperItem, SIGNAL(isOpened()), previousWrapper,SLOT(open())); - QObject::connect(wrapperItem, SIGNAL(isClosed()), previousWrapper,SLOT(close())); - QObject::connect(wrapperItem, &QObject::destroyed, previousWrapper, &QObject::deleteLater); - } - previousWrapper = wrapperItem; // The last one is used as a point of start when deleting and openning - - view->show(); - } - qInfo() << QStringLiteral("Create notifications:") << wrapperItem; - } + if(!wrapperItem) + wrapperItem = NotificationsDefault::create(type, data); mMutex->unlock(); + if(wrapperItem) + ++mInstancesNumber; return wrapperItem; } diff --git a/linphone-app/src/components/notifier/Notifier.hpp b/linphone-app/src/components/notifier/Notifier.hpp index 3748accd7..a08cbeadd 100644 --- a/linphone-app/src/components/notifier/Notifier.hpp +++ b/linphone-app/src/components/notifier/Notifier.hpp @@ -39,9 +39,10 @@ class ChatMessage; } class Notifier : public QObject { - Q_OBJECT; + Q_OBJECT public: + friend class NotificationsDefault; Notifier (QObject *parent = Q_NULLPTR); ~Notifier (); diff --git a/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.cpp b/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.cpp index 98fb0799e..c8304e751 100644 --- a/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.cpp +++ b/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.cpp @@ -19,13 +19,19 @@ */ #include "DesktopToolsLinux.hpp" +#include "notifications/NotificationsDBus.hpp" // ============================================================================= +DesktopTools gDesktopTools; DesktopTools::~DesktopTools () { setScreenSaverStatus(true); } +void DesktopTools::init(){ + NotificationsDBus::init(); +} + bool DesktopTools::getScreenSaverStatus () const { return mScreenSaverStatus; } diff --git a/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.hpp b/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.hpp index d3cf5f96b..d3ac87e33 100644 --- a/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.hpp +++ b/linphone-app/src/components/other/desktop-tools/DesktopToolsLinux.hpp @@ -32,13 +32,15 @@ class DesktopTools : public QObject { Q_PROPERTY(bool screenSaverStatus READ getScreenSaverStatus WRITE setScreenSaverStatus NOTIFY screenSaverStatusChanged); public: - DesktopTools (QObject *parent = Q_NULLPTR) : QObject(parent) {} + DesktopTools (QObject *parent = Q_NULLPTR) : QObject(parent) { + + } ~DesktopTools (); bool getScreenSaverStatus () const; void setScreenSaverStatus (bool status); - static void init(){} + static void init(); static void applicationStateChanged(Qt::ApplicationState){}; signals: diff --git a/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDBus.cpp b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDBus.cpp new file mode 100644 index 000000000..31cfda4f0 --- /dev/null +++ b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDBus.cpp @@ -0,0 +1,258 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NotificationsDBus.hpp" + +#include "app/App.hpp" +#include "app/providers/ImageProvider.hpp" +#include "components/call/CallModel.hpp" +#include "components/chat-room/ChatRoomModel.hpp" +#include "components/settings/AccountSettingsModel.hpp" +#include "components/timeline/TimelineModel.hpp" +#include "qquickwindow.h" +#include "utils/Utils.hpp" + +// ============================================================================= + +namespace { + constexpr char ServiceName[] = "org.freedesktop.Notifications"; + constexpr char ServicePath[] = "/org/freedesktop/Notifications"; +} + +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; +} + +NotificationsDBus::NotificationsDBus (Notifier::NotificationType type, QVariantMap data, QDBusMessage message, QObject *parent) : QObject(parent), mType(type), mData(data), mMessage(message) { + QDBusConnection::sessionBus().connect(QString(), QString(), "org.freedesktop.Notifications", "ActionInvoked", this, SLOT(onActionInvoked(quint32,QString))); + QDBusConnection::sessionBus().connect(QString(), QString(), "org.freedesktop.Notifications", "NotificationClosed", this, SLOT(onNotificationClosed(quint32,quint32))); +} + +NotificationsDBus::~NotificationsDBus () { + closeNotification(); +} + +void NotificationsDBus::init(){ + qDBusRegisterMetaType(); +} + +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::onActionInvoked(quint32 code ,QString key){ + qWarning() << code << key; + if(code == mId && !mProcessed){ + switch(mType){ + case Notifier::ReceivedCall :{ + if(key == "call-start"){ + mData["call"].value()->accept(); + }else if(key == "call-stop"){ + mData["call"].value()->terminate(); + }else + emit deleteNotification(QVariant::fromValue(this)); + } + break; + case Notifier::ReceivedFileMessage:{ + if(key == "document-open"){ + openUrl(QFileInfo( mData["fileUri"].toString())); + } + } + break; + case Notifier::ReceivedMessage: { + if(key == "document-open"){ + CoreManager::getInstance()->getAccountSettingsModel()->setDefaultAccountFromSipAddress(mData["localAddress"].toString()); + auto timelineModel = mData["timelineModel"].value(); + timelineModel->setSelected(true); + App::getInstance()->smartShowWindow(App::getInstance()->getMainWindow()); + } + /* + notification.notificationData.window.setView('Conversation', { + chatRoomModel:notification.timelineModel.getChatRoomModel() + }) + */ + + }break; + case Notifier::NewVersionAvailable:{ + if(key == "software-update-available") + QDesktopServices::openUrl(QUrl(mData["url"].toString())); + } + break; + case Notifier::SnapshotWasTaken:{ + if(key == "document-open"){ + openUrl(QFileInfo( mData["filePath"].toString())); + } + } + break; + case Notifier::RecordingCompleted:{ + if(key == "document-open"){ + openUrl(QFileInfo( mData["filePath"].toString())); + } + } + break; + default:{ + } + } + mProcessed = true; + } +} + +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. + } +} + +QObject *NotificationsDBus::create(Notifier::NotificationType type, QVariantMap data) { + QString title; + QString message; + QSize size, requestedSize(200,200); + ImageProvider imageProvider; + QVariantMap hints; + QStringList actions; + QString iconName = "linphone_logo"; + actions << "default" << "Close"; + //Check https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + switch(type){ + case Notifier::ReceivedCall : + //iconName = "call_sign_incoming"; + title = "Incoming call"; + message = Utils::getDisplayName(data["call"].value()->getFullPeerAddress())+"\n"+data["call"].value()->getPeerAddress(); + actions << "call-start" << "Accept"; + actions << "call-stop" << "Decline"; + break; + case Notifier::ReceivedFileMessage :{ + auto timelineModel = data["timelineModel"].value(); + title = "File received from " + timelineModel->getChatRoomModel()->getUsername(); + actions << "document-open" << "View"; + //message = timelineModel->getChatRoomModel()->getUsername(); + break; + } + case Notifier::ReceivedMessage: { + auto timelineModel = data["timelineModel"].value(); + title = "Message received from "+ timelineModel->getChatRoomModel()->getUsername(); + message = data["message"].toString(); + actions << "document-open" << "View"; + } + break; + case Notifier::NewVersionAvailable:{ + title = data["message"].toString(); + message = data["url"].toString(); + actions << "software-update-available" << "Download"; + } + break; + case Notifier::SnapshotWasTaken:{ + //iconName = "snapshot_sign"; + title = "Snapshot taken"; + actions << "document-open" << "View"; + } + break; + case Notifier::RecordingCompleted:{ + title = "Recording completed"; + actions << "document-open" << "View"; + }break; + default:{ + return nullptr; + } + } + + hints["image_data"] = imageProvider.requestImage(iconName, &size, requestedSize); + + return new NotificationsDBus(type, data, createMessage(title, message, hints, actions)); +} + +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); +} \ No newline at end of file diff --git a/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDBus.hpp b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDBus.hpp new file mode 100644 index 000000000..cd4144b54 --- /dev/null +++ b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDBus.hpp @@ -0,0 +1,62 @@ +/* + * 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 . + */ + +#ifndef NOTIFICATIONS_DBUS_H_ +#define NOTIFICATIONS_DBUS_H_ + +#include +#include "components/notifier/Notifier.hpp" + +// ============================================================================= + +class QDBusPendingCallWatcher; + + +class NotificationsDBus : public QObject { + Q_OBJECT + +public: + NotificationsDBus (Notifier::NotificationType type, QVariantMap data, QDBusMessage message, QObject *parent = Q_NULLPTR); + ~NotificationsDBus (); + static void init(); + + static QObject *create(Notifier::NotificationType type, QVariantMap data); + + //QObject *createNotification (NotificationType type, QVariantMap data); + + + void closeNotification(); + static QDBusMessage createMessage(const QString& title, const QString& message, QVariantMap hints, QStringList actions); +public slots: + void open(); + void onActionInvoked(quint32 code ,QString key); + void onNotificationClosed(quint32 id,quint32 reason); +signals: + void deleteNotification(QVariant notification); +private: + //bool mScreenSaverStatus = true; + Notifier::NotificationType mType; + QVariantMap mData; + QDBusMessage mMessage; + int mId = -1; + bool mProcessed = false; +}; + +#endif diff --git a/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDefault.cpp b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDefault.cpp new file mode 100644 index 000000000..d0eb4cfdd --- /dev/null +++ b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDefault.cpp @@ -0,0 +1,150 @@ +/* + * 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 "NotificationsDefault.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "app/App.hpp" +#include "components/call/CallModel.hpp" +#include "components/core/CoreManager.hpp" +#include "components/timeline/TimelineModel.hpp" +#include "components/timeline/TimelineListModel.hpp" +#include "utils/Utils.hpp" + + +namespace { + constexpr char NotificationsPath[] = "qrc:/ui/modules/Linphone/Notifications/"; + + // --------------------------------------------------------------------------- + // Notifications QML properties/methods. + // --------------------------------------------------------------------------- + + constexpr char NotificationShowMethodName[] = "open"; + + constexpr char NotificationPropertyData[] = "notificationData"; + + constexpr char NotificationPropertyX[] = "popupX"; + constexpr char NotificationPropertyY[] = "popupY"; + + constexpr char NotificationPropertyWindow[] = "__internalWindow"; + + constexpr char NotificationPropertyTimer[] = "__timer"; + + // --------------------------------------------------------------------------- + // Arbitrary hardcoded values. + // --------------------------------------------------------------------------- + + constexpr int NotificationSpacing = 10; + constexpr int MaxNotificationsNumber = 5; +} + +QHash NotificationsDefault::gScreenHeightOffset; + +template +void setProperty (QObject &object, const char *property, const T &value) { + if (!object.setProperty(property, QVariant(value))) { + qWarning() << QStringLiteral("Unable to set property: `%1`.").arg(property); + abort(); + } +} + +NotificationsDefault::NotificationsDefault (QObject *parent){ + +} + +QObject *NotificationsDefault::create(Notifier::NotificationType type, QVariantMap data){ + QQuickItem *wrapperItem = nullptr; + + 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->windowState()&Qt::WindowFullScreen)==Qt::WindowFullScreen){ + 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){ + QQuickView *view = new QQuickView(App::getInstance()->getEngine(), nullptr); // Use QQuickView to create a visual root object that is independant from current application Window + QScreen *screen = allScreens[i]; + QObject::connect(view, &QQuickView::statusChanged, [allScreens](QQuickView::Status status){ // Debug handler : show screens descriptions on Error + if( status == QQuickView::Error){ + QScreen * primaryScreen = QGuiApplication::primaryScreen(); + qInfo() << "Primary screen : " << primaryScreen->geometry() << primaryScreen->availableGeometry() << primaryScreen->virtualGeometry() << primaryScreen->availableVirtualGeometry(); + for(int i = 0 ; i < allScreens.size() ; ++i){ + QScreen *screen = allScreens[i]; + qInfo() << QString("Screen [")+QString::number(i)+"] (hdpi, Geometry, Available, Virtual, AvailableGeometry) :" + << screen->devicePixelRatio() << screen->geometry() << screen->availableGeometry() << screen->virtualGeometry() << screen->availableVirtualGeometry(); + } + } + }); + view->setScreen(screen); // Bind the visual root object to the screen + view->setProperty("flags", QVariant(Qt::BypassWindowManagerHint | Qt::WindowStaysOnBottomHint | Qt::CustomizeWindowHint | Qt::X11BypassWindowManagerHint)); // Set the visual ghost window + view->setSource(QString(NotificationsPath)+Notifier::Notifications[type].filename); + + QQuickWindow *subWindow = view->findChild("__internalWindow"); + QObject::connect(subWindow, &QObject::destroyed, view, &QObject::deleteLater); // When destroying window, detroy visual root object too + + int * screenHeightOffset = &gScreenHeightOffset[screen->name()]; // Access optimization + QRect availableGeometry = screen->availableGeometry(); + int heightOffset = availableGeometry.y() + (availableGeometry.height() - subWindow->height());//*screen->devicePixelRatio(); when using manual scaler + if(showAsTool) + subWindow->setProperty("showAsTool",true); + subWindow->setX(availableGeometry.x()+ (availableGeometry.width()-subWindow->property("width").toInt()));//*screen->devicePixelRatio()); when using manual scaler + subWindow->setY(heightOffset-(*screenHeightOffset % heightOffset)); + + *screenHeightOffset = (subWindow->height() + *screenHeightOffset) + NotificationSpacing; + if (*screenHeightOffset - heightOffset + availableGeometry.y() >= 0) + *screenHeightOffset = 0; + + // if(primaryScreen != screen){ //Useful when doing manual scaling jobs. Need to implement scaler in GUI objects + // //subwindow->setProperty("xScale", (double)screen->availableVirtualGeometry().width()/availableGeometry.width() ); + // //subwindow->setProperty("yScale", (double)screen->availableVirtualGeometry().height()/availableGeometry.height()); + // } + wrapperItem = view->findChild("__internalWrapper"); + ::setProperty(*wrapperItem, NotificationPropertyData,data); + view->setGeometry(subWindow->geometry()); // Ensure to have sufficient space to both let painter do job without error, and stay behind popup + + if(previousWrapper!=nullptr){ // Link objects in order to propagate events without having to store them + QObject::connect(previousWrapper, SIGNAL(deleteNotification(QVariant)), wrapperItem,SLOT(deleteNotificationSlot())); + QObject::connect(wrapperItem, SIGNAL(isOpened()), previousWrapper,SLOT(open())); + QObject::connect(wrapperItem, SIGNAL(isClosed()), previousWrapper,SLOT(close())); + QObject::connect(wrapperItem, &QObject::destroyed, previousWrapper, &QObject::deleteLater); + } + previousWrapper = wrapperItem; // The last one is used as a point of start when deleting and openning + + view->show(); + } + qInfo() << QStringLiteral("Create notifications:") << wrapperItem; + } + return wrapperItem; +} \ No newline at end of file diff --git a/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDefault.hpp b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDefault.hpp new file mode 100644 index 000000000..9d06a53d8 --- /dev/null +++ b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsDefault.hpp @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +#ifndef NOTIFICATIONS_DEFAULT_H_ +#define NOTIFICATIONS_DEFAULT_H_ + +#include + +#include "components/notifier/Notifier.hpp" +// ============================================================================= + +class NotificationsDefault : public QObject { +Q_OBJECT +public: + NotificationsDefault (QObject *parent = Q_NULLPTR); + + static QObject *create(Notifier::NotificationType type, QVariantMap data); + + static QHash gScreenHeightOffset; +}; + +#endif diff --git a/linphone-app/src/components/other/desktop-tools/notifications/NotificationsMacOs.m b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsMacOs.m new file mode 100644 index 000000000..5704fe6bc --- /dev/null +++ b/linphone-app/src/components/other/desktop-tools/notifications/NotificationsMacOs.m @@ -0,0 +1,49 @@ +/* + * ScreenSaverMacOS.m + * Copyright (C) 2017-2018 Belledonne Communications, Grenoble, France + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Created on: August 3, 2018 + * Author: Ronan Abhamon + */ + +#import + +// ============================================================================= + +static bool ScreenSaverEnabled = true; +static IOPMAssertionID AssertionID; + +bool enableScreenSaverMacOs () { + if (ScreenSaverEnabled) + return true; + + ScreenSaverEnabled = IOPMAssertionRelease(AssertionID) == kIOReturnSuccess; + return ScreenSaverEnabled; +} + +bool disableScreenSaverMacOs () { + if (!ScreenSaverEnabled) + return true; + + ScreenSaverEnabled = IOPMAssertionCreateWithName( + kIOPMAssertionTypeNoDisplaySleep, + kIOPMAssertionLevelOn, + CFSTR("Inhibit asked for video stream"), + &AssertionID + ) != kIOReturnSuccess; + return !ScreenSaverEnabled; +}