From 1725927aeaa814f28fa14af239c84d118f399db3 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 12 Apr 2017 15:50:35 +0300 Subject: [PATCH] Clear macOS notifications in a separate thread. Sometimes NSUserNotificationCenter -deliveredNotifications method call freezes for a long time, so now we use it only in a separate thread and we group all the requests for clearing while another clearing is done. --- Telegram/SourceFiles/facades.cpp | 4 - Telegram/SourceFiles/facades.h | 1 - .../platform/mac/notifications_manager_mac.mm | 129 +++++++++++++----- 3 files changed, 96 insertions(+), 38 deletions(-) diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index 7fa0839da8..9d7d6ddbc1 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -587,7 +587,6 @@ namespace Global { namespace internal { struct Data { - uint64 LaunchId = 0; SingleQueuedInvokation HandleHistoryUpdate = { [] { App::app()->call_handleHistoryUpdate(); } }; SingleQueuedInvokation HandleUnreadCounterUpdate = { [] { App::app()->call_handleUnreadCounterUpdate(); } }; SingleQueuedInvokation HandleDelayedPeerUpdates = { [] { App::app()->call_handleDelayedPeerUpdates(); } }; @@ -697,8 +696,6 @@ bool started() { void start() { GlobalData = new internal::Data(); - - memset_rand(&GlobalData->LaunchId, sizeof(GlobalData->LaunchId)); } void finish() { @@ -706,7 +703,6 @@ void finish() { GlobalData = nullptr; } -DefineReadOnlyVar(Global, uint64, LaunchId); DefineRefVar(Global, SingleQueuedInvokation, HandleHistoryUpdate); DefineRefVar(Global, SingleQueuedInvokation, HandleUnreadCounterUpdate); DefineRefVar(Global, SingleQueuedInvokation, HandleDelayedPeerUpdates); diff --git a/Telegram/SourceFiles/facades.h b/Telegram/SourceFiles/facades.h index f1139c27c2..c4735aedf5 100644 --- a/Telegram/SourceFiles/facades.h +++ b/Telegram/SourceFiles/facades.h @@ -291,7 +291,6 @@ bool started(); void start(); void finish(); -DeclareReadOnlyVar(uint64, LaunchId); DeclareRefVar(SingleQueuedInvokation, HandleHistoryUpdate); DeclareRefVar(SingleQueuedInvokation, HandleUnreadCounterUpdate); DeclareRefVar(SingleQueuedInvokation, HandleDelayedPeerUpdates); diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm index eedd6a7fcd..b78ee2d8ef 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm @@ -25,6 +25,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "styles/style_window.h" #include "mainwindow.h" #include "base/task_queue.h" +#include "base/variant.h" #include #include @@ -55,7 +56,7 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm); @interface NotificationDelegate : NSObject { } -- (id) initWithManager:(base::weak_unique_ptr)manager; +- (id) initWithManager:(base::weak_unique_ptr)manager managerId:(uint64)managerId; - (void) userNotificationCenter:(NSUserNotificationCenter*)center didActivateNotification:(NSUserNotification*)notification; - (BOOL) userNotificationCenter:(NSUserNotificationCenter*)center shouldPresentNotification:(NSUserNotification*)notification; @@ -63,22 +64,24 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm); @implementation NotificationDelegate { base::weak_unique_ptr _manager; + uint64 _managerId; } -- (id) initWithManager:(base::weak_unique_ptr)manager { +- (id) initWithManager:(base::weak_unique_ptr)manager managerId:(uint64)managerId { if (self = [super init]) { _manager = manager; + _managerId = managerId; } return self; } - (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification { NSDictionary *notificationUserInfo = [notification userInfo]; - NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"]; - auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL; - DEBUG_LOG(("Received notification with instance %1, mine: %2").arg(notificationLaunchId).arg(Global::LaunchId())); - if (notificationLaunchId != Global::LaunchId()) { // other app instance notification + NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"]; + auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL; + DEBUG_LOG(("Received notification with instance %1, mine: %2").arg(notificationManagerId).arg(_managerId)); + if (notificationManagerId != _managerId) { // other app instance notification base::TaskQueue::Main().Put([] { // Usually we show and activate main window when the application // is activated (receives applicationDidBecomeActive: notification). @@ -182,12 +185,37 @@ public: ~Private(); private: + template + void putClearTask(Task task); + + void clearingThreadLoop(); + + const uint64 _managerId = 0; + QString _managerIdString; + NotificationDelegate *_delegate = nullptr; + std::thread _clearingThread; + std::mutex _clearingMutex; + std::condition_variable _clearingCondition; + + struct ClearFromHistory { + PeerId peerId; + }; + struct ClearAll { + }; + struct ClearFinish { + }; + using ClearTask = base::variant; + std::vector _clearingTasks; + }; Manager::Private::Private(Manager *manager) -: _delegate([[NotificationDelegate alloc] initWithManager:manager]) { +: _managerId(rand_value()) +, _managerIdString(QString::number(_managerId)) +, _delegate([[NotificationDelegate alloc] initWithManager:manager managerId:_managerId]) +, _clearingThread([this] { clearingThreadLoop(); }) { updateDelegate(); subscribe(Global::RefWorkMode(), [this](DBIWorkMode mode) { // We need to update the delegate _after_ the tray icon change was done in Qt. @@ -203,11 +231,11 @@ void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QStri NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease]; if ([notification respondsToSelector:@selector(setIdentifier:)]) { - auto identifier = QString::number(Global::LaunchId()) + '_' + QString::number(peer->id) + '_' + QString::number(msgId); + auto identifier = _managerIdString + '_' + QString::number(peer->id) + '_' + QString::number(msgId); auto identifierValue = Q2NSString(identifier); [notification setIdentifier:identifierValue]; } - [notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer->id],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:Global::LaunchId()],@"launch",nil]]; + [notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer->id],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:_managerId],@"manager",nil]]; [notification setTitle:Q2NSString(title)]; [notification setSubtitle:Q2NSString(subtitle)]; @@ -230,34 +258,68 @@ void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QStri } } -void Manager::Private::clearAll() { - NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; - NSArray *notificationsList = [center deliveredNotifications]; - for (id notification in notificationsList) { - NSDictionary *notificationUserInfo = [notification userInfo]; - NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"]; - auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL; - if (notificationLaunchId == Global::LaunchId()) { - [center removeDeliveredNotification:notification]; +void Manager::Private::clearingThreadLoop() { + auto finished = false; + while (!finished) { + auto clearAll = false; + auto clearFromPeers = std::set(); // Better to use flatmap. + { + std::unique_lock lock(_clearingMutex); + + while (_clearingTasks.empty()) { + _clearingCondition.wait(lock); + } + for (auto &task : _clearingTasks) { + if (base::get_if(&task)) { + finished = true; + clearAll = true; + } else if (base::get_if(&task)) { + clearAll = true; + } else if (auto fromHistory = base::get_if(&task)) { + clearFromPeers.insert(fromHistory->peerId); + } + } + _clearingTasks.clear(); + } + + auto clearByPeer = [&clearFromPeers](NSDictionary *notificationUserInfo) { + if (NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"]) { + auto notificationPeerId = [peerObject unsignedLongLongValue]; + if (notificationPeerId) { + return (clearFromPeers.find(notificationPeerId) != clearFromPeers.cend()); + } + } + return true; + }; + + NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; + NSArray *notificationsList = [center deliveredNotifications]; + for (id notification in notificationsList) { + NSDictionary *notificationUserInfo = [notification userInfo]; + NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"]; + auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL; + if (notificationManagerId == _managerId) { + if (clearAll || clearByPeer(notificationUserInfo)) { + [center removeDeliveredNotification:notification]; + } + } } } } -void Manager::Private::clearFromHistory(History *history) { - unsigned long long peerId = history->peer->id; +template +void Manager::Private::putClearTask(Task task) { + std::unique_lock lock(_clearingMutex); + _clearingTasks.push_back(task); + _clearingCondition.notify_one(); +} - NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; - NSArray *notificationsList = [center deliveredNotifications]; - for (id notification in notificationsList) { - NSDictionary *notificationUserInfo = [notification userInfo]; - NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"]; - NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"]; - auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL; - auto notificationPeerId = peerObject ? [peerObject unsignedLongLongValue] : 0ULL; - if (notificationPeerId == peerId && notificationLaunchId == Global::LaunchId()) { - [center removeDeliveredNotification:notification]; - } - } +void Manager::Private::clearAll() { + putClearTask(ClearAll()); +} + +void Manager::Private::clearFromHistory(History *history) { + putClearTask(ClearFromHistory { history->peer->id }); } void Manager::Private::updateDelegate() { @@ -266,7 +328,8 @@ void Manager::Private::updateDelegate() { } Manager::Private::~Private() { - clearAll(); + putClearTask(ClearFinish()); + _clearingThread.join(); [_delegate release]; }