From ca500317cab924ea881d7ecbed9f2ca8e10ca4b1 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Fri, 8 May 2020 23:34:42 +0200 Subject: [PATCH] Show notifications on all screens - Fix windows flags for DesktopPopup - Merge notification creation and show macros - Store height offset for each screens (key: screen name) - Create a Visual Root Window for each notifications - Put each Visual Root Windows behind its notification to get visually only one entity - Bind each Visual Root Window to a Screen - Propagate events between visual objects in order to use only one - Add open/close events - Add missing anchors - Avoid using window as a keyword --- .../src/components/notifier/Notifier.cpp | 131 +++++++++--------- .../src/components/notifier/Notifier.hpp | 5 +- .../ui/modules/Common/Popup/DesktopPopup.qml | 62 ++++----- .../Linphone/Notifications/Notification.qml | 5 - .../NotificationReceivedCall.qml | 3 +- 5 files changed, 97 insertions(+), 109 deletions(-) diff --git a/linphone-app/src/components/notifier/Notifier.cpp b/linphone-app/src/components/notifier/Notifier.cpp index baaf29c3d..ee7d8524f 100644 --- a/linphone-app/src/components/notifier/Notifier.cpp +++ b/linphone-app/src/components/notifier/Notifier.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include @@ -115,49 +117,61 @@ Notifier::~Notifier () { // ----------------------------------------------------------------------------- -QObject *Notifier::createNotification (Notifier::NotificationType type) { - mMutex->lock(); +QObject *Notifier::createNotification (Notifier::NotificationType type, QVariantMap data) { + QQuickItem *wrapperItem = nullptr; + mMutex->lock(); + Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber); + if (mInstancesNumber == MaxNotificationsNumber) { // Check existing instances. + qWarning() << QStringLiteral("Unable to create another notification."); + mMutex->unlock(); + return nullptr; + } + QList allScreens = QGuiApplication::screens(); + if(allScreens.size() > 0){ // Ensure to have a screen to avoid errors - Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber); + QQuickItem * previousWrapper = nullptr; + ++mInstancesNumber; + 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]; - // Check existing instances. - if (mInstancesNumber == MaxNotificationsNumber) { - qWarning() << QStringLiteral("Unable to create another notification."); - mMutex->unlock(); - return nullptr; - } + 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 - // Create instance and set attributes. - QObject *instance = mComponents[type]->create(); - qInfo() << QStringLiteral("Create notification:") << instance; + int * screenHeightOffset = &mScreenHeightOffset[screen->name()]; // Access optimization + QRect availableGeometry = screen->availableGeometry(); + int heightOffset = availableGeometry.y() + screen->devicePixelRatio()*(availableGeometry.height() - subWindow->height()); - mInstancesNumber++; + subWindow->setX(availableGeometry.x()+availableGeometry.width()*screen->devicePixelRatio()-subWindow->property("width").toInt()*screen->devicePixelRatio()); + subWindow->setY(heightOffset-(*screenHeightOffset % heightOffset)); - { - QQuickWindow *window = instance->findChild(NotificationPropertyWindow); - Q_CHECK_PTR(window); + *screenHeightOffset = (subWindow->height() + *screenHeightOffset) + NotificationSpacing; + if (*screenHeightOffset - heightOffset + availableGeometry.y() >= 0) + *screenHeightOffset = 0; - QScreen *screen = window->screen(); - Q_CHECK_PTR(screen); +// 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 - QRect geometry = screen->availableGeometry(); - - // Set X/Y. (Not Pokémon games.) - int windowHeight = window->height(); - int offset = geometry.y() + geometry.height() - windowHeight; - - ::setProperty(*instance, NotificationPropertyX, geometry.x() + geometry.width() - window->width()); - ::setProperty(*instance, NotificationPropertyY, offset - (mOffset % offset)); - - // Update offset. - mOffset = (windowHeight + mOffset) + NotificationSpacing; - if (mOffset - offset + geometry.y() >= 0) - mOffset = 0; - } - - mMutex->unlock(); - - return instance; + if(previousWrapper!=nullptr){ // Link objects in order to propagate events without having to store them + 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; + } + mMutex->unlock(); + return wrapperItem; } // ----------------------------------------------------------------------------- @@ -204,7 +218,7 @@ void Notifier::deleteNotification (QVariant notification) { Q_ASSERT(mInstancesNumber >= 0); if (mInstancesNumber == 0) - mOffset = 0; + mScreenHeightOffset.clear(); mMutex->unlock(); @@ -213,14 +227,11 @@ void Notifier::deleteNotification (QVariant notification) { // ============================================================================= -#define CREATE_NOTIFICATION(TYPE) \ - QObject * notification = createNotification(TYPE); \ +#define CREATE_NOTIFICATION(TYPE, DATA) \ + QObject * notification = createNotification(TYPE, DATA); \ if (!notification) \ return; \ - const int timeout = Notifications[TYPE].timeout * 1000; - -#define SHOW_NOTIFICATION(DATA) \ - ::setProperty(*notification, NotificationPropertyData, DATA); \ + const int timeout = Notifications[TYPE].timeout * 1000; \ showNotification(notification, timeout); // ----------------------------------------------------------------------------- @@ -228,8 +239,6 @@ void Notifier::deleteNotification (QVariant notification) { // ----------------------------------------------------------------------------- void Notifier::notifyReceivedMessage (const shared_ptr &message) { - CREATE_NOTIFICATION(Notifier::ReceivedMessage) - QVariantMap map; map["message"] = message->getFileTransferInformation() ? tr("newFileMessage") @@ -239,62 +248,46 @@ void Notifier::notifyReceivedMessage (const shared_ptr &m map["peerAddress"] = Utils::coreStringToAppString(chatRoom->getPeerAddress()->asStringUriOnly()); map["localAddress"] = Utils::coreStringToAppString(chatRoom->getLocalAddress()->asStringUriOnly()); map["window"].setValue(App::getInstance()->getMainWindow()); - - SHOW_NOTIFICATION(map) + CREATE_NOTIFICATION(Notifier::ReceivedMessage, map) } void Notifier::notifyReceivedFileMessage (const shared_ptr &message) { - CREATE_NOTIFICATION(Notifier::ReceivedFileMessage) - QVariantMap map; map["fileUri"] = Utils::coreStringToAppString(message->getFileTransferInformation()->getFilePath()); map["fileSize"] = quint64(message->getFileTransferInformation()->getSize() +message->getFileTransferInformation()->getFileSize()); - - SHOW_NOTIFICATION(map) + CREATE_NOTIFICATION(Notifier::ReceivedFileMessage, map) } void Notifier::notifyReceivedCall (const shared_ptr &call) { - CREATE_NOTIFICATION(Notifier::ReceivedCall) - CallModel *callModel = &call->getData("call-model"); + QVariantMap map; + map["call"].setValue(callModel); + CREATE_NOTIFICATION(Notifier::ReceivedCall, map) QObject::connect(callModel, &CallModel::statusChanged, notification, [this, notification](CallModel::CallStatus status) { if (status == CallModel::CallStatusEnded || status == CallModel::CallStatusConnected) deleteNotification(QVariant::fromValue(notification)); }); - QVariantMap map; - map["call"].setValue(callModel); - - SHOW_NOTIFICATION(map) } void Notifier::notifyNewVersionAvailable (const QString &version, const QString &url) { - CREATE_NOTIFICATION(Notifier::NewVersionAvailable) - QVariantMap map; map["message"] = tr("newVersionAvailable").arg(version); map["url"] = url; - - SHOW_NOTIFICATION(map) + CREATE_NOTIFICATION(Notifier::NewVersionAvailable, map) } void Notifier::notifySnapshotWasTaken (const QString &filePath) { - CREATE_NOTIFICATION(Notifier::SnapshotWasTaken) - QVariantMap map; map["filePath"] = filePath; - - SHOW_NOTIFICATION(map) + CREATE_NOTIFICATION(Notifier::SnapshotWasTaken, map) } void Notifier::notifyRecordingCompleted (const QString &filePath) { - CREATE_NOTIFICATION(Notifier::RecordingCompleted) - QVariantMap map; map["filePath"] = filePath; - - SHOW_NOTIFICATION(map) + CREATE_NOTIFICATION(Notifier::RecordingCompleted, map) } #undef SHOW_NOTIFICATION diff --git a/linphone-app/src/components/notifier/Notifier.hpp b/linphone-app/src/components/notifier/Notifier.hpp index eacc8123a..79b88209f 100644 --- a/linphone-app/src/components/notifier/Notifier.hpp +++ b/linphone-app/src/components/notifier/Notifier.hpp @@ -24,6 +24,7 @@ #include #include +#include // ============================================================================= @@ -72,10 +73,10 @@ private: int timeout; }; - QObject *createNotification (NotificationType type); + QObject *createNotification (NotificationType type, QVariantMap data); void showNotification (QObject *notification, int timeout); - int mOffset = 0; + QHash mScreenHeightOffset; int mInstancesNumber = 0; QMutex *mMutex = nullptr; diff --git a/linphone-app/ui/modules/Common/Popup/DesktopPopup.qml b/linphone-app/ui/modules/Common/Popup/DesktopPopup.qml index d641d0002..fa3793941 100644 --- a/linphone-app/ui/modules/Common/Popup/DesktopPopup.qml +++ b/linphone-app/ui/modules/Common/Popup/DesktopPopup.qml @@ -1,5 +1,9 @@ import QtQuick 2.7 -import QtQuick.Window 2.2 +import QtQuick.Window 2.12 + +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import Qt.labs.platform 1.0 import Common.Styles 1.0 @@ -7,56 +11,57 @@ import Common.Styles 1.0 Item { id: wrapper + objectName: '__internalWrapper' // --------------------------------------------------------------------------- - property alias popupX: window.x - property alias popupY: window.y + property alias popupX: windowId.x + property alias popupY: windowId.y property bool requestActivate: false property int flags: Qt.SplashScreen - readonly property alias popupWidth: window.width - readonly property alias popupHeight: window.height + readonly property alias popupWidth: windowId.width + readonly property alias popupHeight: windowId.height default property alias _content: content.data property bool _isOpen: false + signal isOpened() + signal isClosed() + signal dataChanged() + on_ContentChanged: dataChanged(_content) // --------------------------------------------------------------------------- function open () { - _isOpen = true + _isOpen = true; + isOpened(); } function close () { _isOpen = false + isClosed() } // --------------------------------------------------------------------------- - // DO NOT TOUCH THESE PROPERTIES. - - // No visible. - visible: false - // No size, no position. height: 0 width: 0 - x: 0 - y: 0 + visible:true Window { - id: window - - // Used for internal purposes only. Like Notifications. + id: windowId objectName: '__internalWindow' - - flags: wrapper.flags - opacity: 0 + property bool isFrameLess : false; + // Don't use Popup for flags : it could lead to error in geometry + flags: Qt.BypassWindowManagerHint | Qt.WindowStaysOnTopHint | Qt.Window | Qt.FramelessWindowHint; + opacity: 1.0 height: _content[0] != null ? _content[0].height : 0 width: _content[0] != null ? _content[0].width : 0 - + visible:true Item { id: content + anchors.fill:parent property var $parent: wrapper } @@ -65,37 +70,32 @@ Item { // --------------------------------------------------------------------------- states: State { - name: 'opened' + name: 'opening' when: _isOpen PropertyChanges { opacity: 1.0 - target: window + target: windowId } } transitions: [ Transition { from: '' - to: 'opened' - + to: 'opening' ScriptAction { script: { - window.showNormal() - if (wrapper.requestActivate) { - window.requestActivate() + windowId.requestActivate() } } } }, - Transition { - from: 'opened' + from: '*' to: '' - ScriptAction { - script: window.hide() + script: windowId.hide() } } ] diff --git a/linphone-app/ui/modules/Linphone/Notifications/Notification.qml b/linphone-app/ui/modules/Linphone/Notifications/Notification.qml index a6ff2a022..3630037a1 100644 --- a/linphone-app/ui/modules/Linphone/Notifications/Notification.qml +++ b/linphone-app/ui/modules/Linphone/Notifications/Notification.qml @@ -23,11 +23,6 @@ DesktopPopup { deleteNotification(notification) } - flags: { - return (Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) | - (Qt.platform.os === 'osx' ? Qt.Window : Qt.Popup) - } - Rectangle { color: NotificationStyle.color height: overrodeHeight || NotificationStyle.height diff --git a/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedCall.qml b/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedCall.qml index 0633c8eeb..ffbf5ad76 100644 --- a/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedCall.qml +++ b/linphone-app/ui/modules/Linphone/Notifications/NotificationReceivedCall.qml @@ -29,7 +29,7 @@ Notification { } sourceComponent: ColumnLayout { - spacing: NotificationReceivedCallStyle.spacing + spacing: NotificationReceivedCallStyle.spacing Contact { Layout.fillWidth: true @@ -77,7 +77,6 @@ Notification { ActionButton { icon: 'hangup' - onClicked: notification._close(notification.call.terminate) } }