[draft] Freedesktop Notifications implementation (Linux)

This commit is contained in:
Julien Wadel 2022-12-30 16:46:29 +01:00
parent b132e19e2e
commit 9593bc230d
11 changed files with 589 additions and 87 deletions

View file

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

View file

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

View file

@ -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<class T>
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<QScreen *> 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<QQuickWindow *>("__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<QQuickItem *>("__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;
}

View file

@ -39,9 +39,10 @@ class ChatMessage;
}
class Notifier : public QObject {
Q_OBJECT;
Q_OBJECT
public:
friend class NotificationsDefault;
Notifier (QObject *parent = Q_NULLPTR);
~Notifier ();

View file

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

View file

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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <QCoreApplication>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDBusMetaType>
#include <QDBusConnectionInterface>
#include <QDebug>
#include <QTimer>
#include <QImage>
#include <QBuffer>
#include <QDesktopServices>
#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<QImage>();
}
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<CallModel*>()->accept();
}else if(key == "call-stop"){
mData["call"].value<CallModel*>()->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*>();
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<CallModel*>()->getFullPeerAddress())+"\n"+data["call"].value<CallModel*>()->getPeerAddress();
actions << "call-start" << "Accept";
actions << "call-stop" << "Decline";
break;
case Notifier::ReceivedFileMessage :{
auto timelineModel = data["timelineModel"].value<TimelineModel*>();
title = "File received from " + timelineModel->getChatRoomModel()->getUsername();
actions << "document-open" << "View";
//message = timelineModel->getChatRoomModel()->getUsername();
break;
}
case Notifier::ReceivedMessage: {
auto timelineModel = data["timelineModel"].value<TimelineModel*>();
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<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

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef NOTIFICATIONS_DBUS_H_
#define NOTIFICATIONS_DBUS_H_
#include <QDBusInterface>
#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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "NotificationsDefault.hpp"
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QGuiApplication>
#include <QQuickWindow>
#include <QQuickItem>
#include <QQuickView>
#include <QScreen>
#include <QTimer>
#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<QString,int> NotificationsDefault::gScreenHeightOffset;
template<class T>
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<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->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<QQuickWindow *>("__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<QQuickItem *>("__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;
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef NOTIFICATIONS_DEFAULT_H_
#define NOTIFICATIONS_DEFAULT_H_
#include <QObject>
#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<QString,int> gScreenHeightOffset;
};
#endif

View file

@ -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 <IOKit/pwr_mgt/IOPMLib.h>
// =============================================================================
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;
}