notifications in navigation bar

fix crash macos noification in task bar
This commit is contained in:
Gaelle Braud 2025-08-20 10:58:26 +02:00
parent e78c7d04f0
commit 480704664e
16 changed files with 475 additions and 10 deletions

View file

@ -93,6 +93,12 @@
#include "tool/request/RequestDialog.hpp"
#include "tool/thread/Thread.hpp"
#if defined(Q_OS_MACOS)
#include "core/event-count-notifier/EventCountNotifierMacOs.hpp"
#else
#include "core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp"
#endif // if defined(Q_OS_MACOS)
DEFINE_ABSTRACT_OBJECT(App)
#ifdef Q_OS_LINUX
@ -263,6 +269,7 @@ App::App(int &argc, char *argv[])
// Do not use APPLICATION_NAME here.
// The EXECUTABLE_NAME will be used in qt standard paths. It's our goal.
QThread::currentThread()->setPriority(QThread::HighPriority);
qDebug() << "app thread is" << QThread::currentThread();
QCoreApplication::setApplicationName(EXECUTABLE_NAME);
QApplication::setOrganizationDomain(EXECUTABLE_NAME);
QCoreApplication::setApplicationVersion(APPLICATION_SEMVER);
@ -291,6 +298,7 @@ App::App(int &argc, char *argv[])
emit currentDateChanged();
}
});
mEventCountNotifier = new EventCountNotifier(this);
mDateUpdateTimer.start();
}
@ -372,6 +380,16 @@ void App::setSelf(QSharedPointer<App>(me)) {
});
}
});
mCoreModelConnection->makeConnectToModel(&CoreModel::unreadNotificationsChanged, [this] {
int n = mEventCountNotifier->getCurrentEventCount();
mCoreModelConnection->invokeToCore([this, n] { mEventCountNotifier->notifyEventCount(n); });
});
mCoreModelConnection->makeConnectToModel(&CoreModel::defaultAccountChanged, [this] {
int n = mEventCountNotifier->getCurrentEventCount();
mCoreModelConnection->invokeToCore([this, n] { mEventCountNotifier->notifyEventCount(n); });
});
//---------------------------------------------------------------------------------------------
mCliModelConnection = SafeConnection<App, CliModel>::create(me, CliModel::getInstance());
mCliModelConnection->makeConnectToCore(&App::receivedMessage, [this](int, const QByteArray &byteArray) {
@ -400,6 +418,15 @@ QThread *App::getLinphoneThread() {
Notifier *App::getNotifier() const {
return mNotifier;
}
EventCountNotifier *App::getEventCountNotifier() {
return mEventCountNotifier;
}
int App::getEventCount() const {
return mEventCountNotifier ? mEventCountNotifier->getEventCount() : 0;
}
//-----------------------------------------------------------
// Initializations
//-----------------------------------------------------------
@ -613,7 +640,6 @@ static inline bool installLocale(App &app, QTranslator &translator, const QLocal
}
void App::initLocale() {
// Try to use preferred locale.
QString locale;
@ -628,9 +654,9 @@ void App::initLocale() {
// Try to use system locale.
// #ifdef Q_OS_MACOS
// Use this workaround if there is still an issue about detecting wrong language from system on Mac. Qt doesn't use
// the current system language on QLocale::system(). So we need to get it from user settings and overwrite its
// Locale.
// Use this workaround if there is still an issue about detecting wrong language from system on Mac. Qt doesn't
// use the current system language on QLocale::system(). So we need to get it from user settings and overwrite
// its Locale.
// QSettings settings;
// QString preferredLanguage = settings.value("AppleLanguages").toStringList().first();
// QStringList qtLocale = QLocale::system().name().split('_');
@ -640,9 +666,9 @@ void App::initLocale() {
// }
// QLocale sysLocale = QLocale(qtLocale.join('_'));
// #else
QLocale sysLocale(QLocale::system().name()); // Use Locale from name because Qt has a bug where it didn't use the
// QLocale::language (aka : translator.language != locale.language) on
// Mac. #endif
QLocale sysLocale(QLocale::system().name()); // Use Locale from name because Qt has a bug where it didn't use
// the QLocale::language (aka : translator.language !=
// locale.language) on Mac. #endif
if (installLocale(*this, *mTranslatorCore, sysLocale)) {
qDebug() << "installed sys locale" << sysLocale.name();
setLocale(sysLocale.name());

View file

@ -54,6 +54,9 @@ public:
static QThread *getLinphoneThread();
Notifier *getNotifier() const;
EventCountNotifier *getEventCountNotifier();
int getEventCount() const;
// App::postModelAsync(<lambda>) => run lambda in model thread and continue.
// App::postModelSync(<lambda>) => run lambda in current thread and block connection.
template <typename Func, typename... Args>
@ -119,6 +122,10 @@ public:
void restart();
bool autoStartEnabled();
void setSysTrayIcon();
QSystemTrayIcon *getSystemTrayIcon() const {
return mSystemTrayIcon;
}
void updateSysTrayCount(int n);
QLocale getLocale();
void onLoggerInitialized();
@ -184,6 +191,7 @@ private:
QCommandLineParser *mParser = nullptr;
Thread *mLinphoneThread = nullptr;
Notifier *mNotifier = nullptr;
EventCountNotifier *mEventCountNotifier = nullptr;
QSystemTrayIcon *mSystemTrayIcon = nullptr;
QQuickWindow *mMainWindow = nullptr;
QQuickWindow *mCallsWindow = nullptr;

View file

@ -19,6 +19,7 @@ list(APPEND _LINPHONEAPP_SOURCES
core/camera/CameraGui.cpp
core/camera/CameraDummy.cpp
core/camera/PreviewManager.cpp
core/event-count-notifier/AbstractEventCountNotifier.cpp
core/fps-counter/FPSCounter.cpp
core/friend/FriendCore.cpp
core/friend/FriendGui.cpp
@ -99,5 +100,9 @@ else() # Use QDBus for Linux
core/singleapplication/SingleApplicationDBusPrivate.hpp
core/singleapplication/SingleApplicationDBus.cpp)
endif()
if(APPLE)
list(APPEND _LINPHONEAPP_SOURCES core/event-count-notifier/EventCountNotifierMacOs.m)
else()
list(APPEND _LINPHONEAPP_SOURCES core/event-count-notifier/EventCountNotifierSystemTrayIcon.cpp)
endif()
set(_LINPHONEAPP_SOURCES ${_LINPHONEAPP_SOURCES} PARENT_SCOPE)

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2010-2024 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 <QtDebug>
#include "core/App.hpp"
#include "model/core/CoreModel.hpp"
#include "model/setting/SettingsModel.hpp"
#include "AbstractEventCountNotifier.hpp"
// =============================================================================
using namespace std;
DEFINE_ABSTRACT_OBJECT(AbstractEventCountNotifier)
AbstractEventCountNotifier::AbstractEventCountNotifier(QObject *parent) : QObject(parent) {
}
int AbstractEventCountNotifier::getEventCount() const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto coreModel = CoreModel::getInstance();
int count = coreModel->getCore()->getMissedCallsCount();
return count;
}
int AbstractEventCountNotifier::getCurrentEventCount() const {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
// auto coreModel = CoreModel::getInstance();
// int count = coreModel->getCore()->getMissedCallsCount();
// bool filtered = SettingsModel::getInstance()->isSystrayNotificationFiltered();
// bool global = SettingsModel::getInstance()->isSystrayNotificationGlobal();
// if (global && !filtered)
return getEventCount();
// else {
// auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
// if (currentAccount) {
// auto linphoneChatRooms = currentAccount->filterChatRooms("");
// for (const auto &chatRoom : linphoneChatRooms) {
// count += chatRoom->getUnreadMessagesCount();
// }
// }
// return count;
// }
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2010-2024 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 ABSTRACT_EVENT_COUNT_NOTIFIER_H_
#define ABSTRACT_EVENT_COUNT_NOTIFIER_H_
#include <QHash>
#include <QObject>
#include <QPair>
#include <QSystemTrayIcon>
#include "tool/AbstractObject.hpp"
#include "tool/thread/SafeConnection.hpp"
// =============================================================================
namespace linphone {
class ChatMessage;
}
class CallModel;
class ChatRoomModel;
class HistoryModel;
class AbstractEventCountNotifier : public QObject, public AbstractObject {
Q_OBJECT
public:
AbstractEventCountNotifier(QObject *parent = Q_NULLPTR);
int getEventCount() const; // global
int getCurrentEventCount() const; // Current account
protected:
virtual void notifyEventCount(int n) = 0;
private:
DECLARE_ABSTRACT_OBJECT
};
#endif // ABSTRACT_EVENT_COUNT_NOTIFIER_H_

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2010-2024 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 EVENT_COUNT_NOTIFIER_MAC_OS_H_
#define EVENT_COUNT_NOTIFIER_MAC_OS_H_
#include "AbstractEventCountNotifier.hpp"
// =============================================================================
extern "C" void notifyEventCountMacOs(int n);
class EventCountNotifier : public AbstractEventCountNotifier {
public:
EventCountNotifier(QObject *parent = Q_NULLPTR) : AbstractEventCountNotifier(parent) {
}
void notifyEventCount(int n) override {
notifyEventCountMacOs(n);
}
};
#endif // EVENT_COUNT_NOTIFIER_MAC_OS_H_

View file

@ -0,0 +1,30 @@
/*
* EventCountNotifierMacOs.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: June 30, 2017
* Author: Ghislain MARY
*/
#import <Cocoa/Cocoa.h>
// =============================================================================
void notifyEventCountMacOs (int n) {
NSString *badgeStr = (n > 0) ? [NSString stringWithFormat:@"%d", n] : @"";
[[NSApp dockTile] setBadgeLabel:badgeStr];
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2010-2024 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 <QIcon>
#include <QPainter>
#include <QSvgRenderer>
#include <QSystemTrayIcon>
#include <QTimer>
#include <QWindow>
#include "core/App.hpp"
#include "model/core/CoreModel.hpp"
#include "model/setting/SettingsModel.hpp"
#include "tool/Constants.hpp"
#include "tool/Utils.hpp"
#include "EventCountNotifierSystemTrayIcon.hpp"
// =============================================================================
namespace {
constexpr int IconWidth = 256;
constexpr int IconHeight = 256;
constexpr int IconCounterBackgroundRadius = 100;
constexpr int IconCounterBlinkInterval = 1000;
constexpr int IconCounterTextPixelSize = 144;
} // namespace
DEFINE_ABSTRACT_OBJECT(EventCountNotifier)
QSharedPointer<EventCountNotifier> EventCountNotifier::create(QObject *parent) {
auto sharedPointer = QSharedPointer<EventCountNotifier>(new EventCountNotifier(parent), &QObject::deleteLater);
sharedPointer->setSelf(sharedPointer);
sharedPointer->moveToThread(App::getInstance()->thread());
return sharedPointer;
}
EventCountNotifier::EventCountNotifier(QObject *parent) : AbstractEventCountNotifier(parent) {
QSvgRenderer renderer((QString(Constants::WindowIconPath)));
if (!renderer.isValid()) qFatal("Invalid SVG Image.");
QPixmap buf(IconWidth, IconHeight);
buf.fill(QColor(Qt::transparent));
QPainter painter(&buf);
renderer.render(&painter);
mBuf = new QPixmap(buf);
mBufWithCounter = new QPixmap();
mBlinkTimer = new QTimer(this);
mBlinkTimer->setInterval(IconCounterBlinkInterval);
connect(mBlinkTimer, &QTimer::timeout, this, &EventCountNotifier::update);
}
void EventCountNotifier::setSelf(QSharedPointer<EventCountNotifier> me) {
}
EventCountNotifier::~EventCountNotifier() {
delete mBuf;
delete mBufWithCounter;
}
// -----------------------------------------------------------------------------
void EventCountNotifier::notifyEventCount(int n) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
n = n > 99 ? 99 : n;
QSystemTrayIcon *sysTrayIcon = App::getInstance()->getSystemTrayIcon();
if (!sysTrayIcon) return;
if (!n) {
mBlinkTimer->stop();
sysTrayIcon->setIcon(QIcon(*mBuf));
return;
}
*mBufWithCounter = *mBuf;
QPainter p(mBufWithCounter);
const int width = mBufWithCounter->width();
const int height = mBufWithCounter->height();
// Draw background.
{
p.setBrush(QColor(Utils::getDefaultStyleColor("main1_100")));
p.drawEllipse(QPointF(width / 2, height / 2), IconCounterBackgroundRadius, IconCounterBackgroundRadius);
}
// Draw text.
{
QFont font = p.font();
font.setPixelSize(IconCounterTextPixelSize);
p.setFont(font);
p.setPen(QPen(QColor(Utils::getDefaultStyleColor("main1_500_main"))));
p.drawText(QRect(0, 0, width, height), Qt::AlignCenter, QString::number(n));
}
// Change counter.
mBlinkTimer->stop();
auto coreModel = CoreModel::getInstance();
if (!coreModel->isInitialized() || SettingsModel::getInstance()->isSystrayNotificationBlinkEnabled())
mBlinkTimer->start();
mDisplayCounter = true;
update();
}
void EventCountNotifier::update() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
QSystemTrayIcon *sysTrayIcon = App::getInstance()->getSystemTrayIcon();
if (sysTrayIcon) {
sysTrayIcon->setIcon(QIcon(mDisplayCounter ? *mBufWithCounter : *mBuf));
}
mDisplayCounter = !mDisplayCounter;
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2010-2024 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 EVENT_COUNT_NOTIFIER_SYSTEM_TRAY_ICON_H_
#define EVENT_COUNT_NOTIFIER_SYSTEM_TRAY_ICON_H_
#include "AbstractEventCountNotifier.hpp"
// =============================================================================
class QTimer;
class EventCountNotifier : public AbstractEventCountNotifier {
public:
static QSharedPointer<EventCountNotifier> create(QObject *parent = Q_NULLPTR);
EventCountNotifier(QObject *parent = Q_NULLPTR);
void setSelf(QSharedPointer<EventCountNotifier> me);
~EventCountNotifier();
void notifyEventCount(int n) override;
private:
const QPixmap *mBuf = nullptr;
QPixmap *mBufWithCounter = nullptr;
QTimer *mBlinkTimer = nullptr;
bool mDisplayCounter = false;
QSharedPointer<SafeConnection<EventCountNotifier, CoreModel>> mCoreModelConnection;
void update();
DECLARE_ABSTRACT_OBJECT
};
#endif // EVENT_COUNT_NOTIFIER_SYSTEM_TRAY_ICON_H_

View file

@ -34,6 +34,12 @@
#include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp"
#if defined(Q_OS_MACOS)
#include "core/event-count-notifier/EventCountNotifierMacOs.hpp"
#else
#include "core/event-count-notifier/EventCountNotifierSystemTrayIcon.hpp"
#endif // if defined(Q_OS_MACOS)
// =============================================================================
DEFINE_ABSTRACT_OBJECT(CoreModel)
@ -129,9 +135,14 @@ void CoreModel::start() {
mMagicSearch->setSelf(mMagicSearch);
connect(mMagicSearch.get(), &MagicSearchModel::searchResultsReceived, this,
[this] { emit magicSearchResultReceived(mMagicSearch->mLastSearch); });
mStarted = true;
}
// -----------------------------------------------------------------------------
bool CoreModel::isInitialized() const {
return mStarted;
}
std::shared_ptr<CoreModel> CoreModel::getInstance() {
return gCoreModel;
}

View file

@ -25,6 +25,7 @@
#include <QObject>
#include <QSharedPointer>
#include <QString>
#include <QSystemTrayIcon>
#include <QThread>
#include <QTimer>
#include <linphone++/linphone.hh>
@ -39,6 +40,9 @@
// =============================================================================
class AbstractEventCountNotifier;
class EventCountNotifier;
class CoreModel : public ::Listener<linphone::Core, linphone::CoreListener>,
public linphone::CoreListener,
public AbstractObject {
@ -52,6 +56,8 @@ public:
std::shared_ptr<linphone::Core> getCore();
std::shared_ptr<LoggerModel> getLogger();
bool isInitialized() const;
void start();
void setConfigPath(QString path);
@ -89,6 +95,7 @@ private:
QTimer *mIterateTimer = nullptr;
QMap<QString, OIDCModel *> mOpenIdConnections;
std::shared_ptr<MagicSearchModel> mMagicSearch;
bool mStarted = false;
void setPathBeforeCreation();
void setPathsAfterCreation();

View file

@ -726,6 +726,20 @@ QString SettingsModel::getDefaultDomain() const {
mConfig->getString(SettingsModel::AppSection, "default_domain", "sip.linphone.org"));
}
bool SettingsModel::isSystrayNotificationBlinkEnabled() const {
return !!mConfig->getInt(UiSection, "systray_notification_blink", 1);
}
bool SettingsModel::isSystrayNotificationGlobal() const {
return !!mConfig->getInt(UiSection, "systray_notification_global", 1);
}
bool SettingsModel::isSystrayNotificationFiltered() const {
return !!mConfig->getInt(UiSection, "systray_notification_filtered", 0);
}
bool SettingsModel::getLimeIsSupported() const {
return CoreModel::getInstance()->getCore()->limeX3DhAvailable();
}
// clang-format off
void SettingsModel::notifyConfigReady(){
DEFINE_NOTIFY_CONFIG_READY(disableChatFeature, DisableChatFeature)

View file

@ -157,6 +157,11 @@ public:
static bool clearLocalLdapFriendsUponStartup(const std::shared_ptr<linphone::Config> &config);
bool isSystrayNotificationBlinkEnabled() const;
bool isSystrayNotificationGlobal() const;
bool isSystrayNotificationFiltered() const;
bool getLimeIsSupported() const;
// UI
DECLARE_GETSET(bool, disableChatFeature, DisableChatFeature)
DECLARE_GETSET(bool, disableMeetingsFeature, DisableMeetingsFeature)

View file

@ -36,10 +36,13 @@
#include <limits.h>
#include <QClipboard>
#include <QColor>
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QHostAddress>
#include <QImageReader>
#include <QQmlComponent>
#include <QQmlProperty>
#include <QQuickWindow>
#include <QRandomGenerator>
#include <QRegularExpression>
@ -551,6 +554,16 @@ QString Utils::getOsProduct() {
return product + "/" + version;
}
QColor Utils::getDefaultStyleColor(const QString &colorName) {
mustBeInMainThread(sLog().arg(Q_FUNC_INFO));
static QObject *defaultStyleSingleton = nullptr;
if (!defaultStyleSingleton) {
QQmlComponent component(App::getInstance()->mEngine, QUrl("qrc:/qt/qml/Linphone/view/Style/DefaultStyle.qml"));
defaultStyleSingleton = component.create();
}
return QQmlProperty::read(defaultStyleSingleton, colorName).value<QColor>();
}
QString Utils::getCountryName(const QLocale::Territory &p_country) {
QString countryName;
switch (p_country) {

View file

@ -146,6 +146,8 @@ public:
static QString getApplicationProduct();
static QString getOsProduct();
static QColor getDefaultStyleColor(const QString &colorName);
static QList<QSharedPointer<DownloadablePayloadTypeCore>> getDownloadableVideoPayloadTypes();
static void checkDownloadedCodecsUpdates();

View file

@ -460,8 +460,7 @@ Item {
icon.height: Math.round(32 * DefaultStyle.dp)
text: qsTr("settings_title")
icon.source: AppIcons.settings
onClicked: openContextualMenuComponent(
settingsPageComponent)
onClicked: openContextualMenuComponent(settingsPageComponent)
KeyNavigation.up: visibleChildren.length
!= 0 ? settingsMenuButton.getPreviousItem(
2) : null