mirror of https://github.com/procxx/kepka.git
				
				
				
			
		
			
				
	
	
		
			482 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
/*
 | 
						|
This file is part of Telegram Desktop,
 | 
						|
the official desktop application for the Telegram messaging service.
 | 
						|
 | 
						|
For license and copyright information please follow this link:
 | 
						|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
						|
*/
 | 
						|
#include "window/notifications_manager.h"
 | 
						|
 | 
						|
#include "platform/platform_notifications_manager.h"
 | 
						|
#include "window/notifications_manager_default.h"
 | 
						|
#include "media/media_audio_track.h"
 | 
						|
#include "media/media_audio.h"
 | 
						|
#include "history/history.h"
 | 
						|
#include "history/history_item_components.h"
 | 
						|
#include "history/feed/history_feed_section.h"
 | 
						|
#include "lang/lang_keys.h"
 | 
						|
#include "window/window_controller.h"
 | 
						|
#include "mainwindow.h"
 | 
						|
#include "mainwidget.h"
 | 
						|
#include "apiwrap.h"
 | 
						|
#include "auth_session.h"
 | 
						|
 | 
						|
namespace Window {
 | 
						|
namespace Notifications {
 | 
						|
namespace {
 | 
						|
 | 
						|
// not more than one sound in 500ms from one peer - grouping
 | 
						|
constexpr auto kMinimalAlertDelay = TimeMs(500);
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
System::System(AuthSession *session) : _authSession(session) {
 | 
						|
	createManager();
 | 
						|
 | 
						|
	_waitTimer.setTimeoutHandler([this] {
 | 
						|
		showNext();
 | 
						|
	});
 | 
						|
 | 
						|
	subscribe(settingsChanged(), [this](ChangeType type) {
 | 
						|
		if (type == ChangeType::DesktopEnabled) {
 | 
						|
			App::wnd()->updateTrayMenu();
 | 
						|
			clearAll();
 | 
						|
		} else if (type == ChangeType::ViewParams) {
 | 
						|
			updateAll();
 | 
						|
		} else if (type == ChangeType::IncludeMuted) {
 | 
						|
			Notify::unreadCounterUpdated();
 | 
						|
		}
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void System::createManager() {
 | 
						|
	_manager = Platform::Notifications::Create(this);
 | 
						|
	if (!_manager) {
 | 
						|
		_manager = std::make_unique<Default::Manager>(this);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void System::schedule(History *history, HistoryItem *item) {
 | 
						|
	if (App::quitting() || !history->currentNotification() || !AuthSession::Exists()) return;
 | 
						|
 | 
						|
	auto notifyByFrom = (!history->peer->isUser() && item->mentionsMe())
 | 
						|
		? item->from().get()
 | 
						|
		: nullptr;
 | 
						|
 | 
						|
	if (item->isSilent()) {
 | 
						|
		history->popNotification(item);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	auto haveSetting = !history->peer->notifySettingsUnknown();
 | 
						|
	if (haveSetting) {
 | 
						|
		if (history->peer->isMuted()) {
 | 
						|
			if (notifyByFrom) {
 | 
						|
				haveSetting = !item->from()->notifySettingsUnknown();
 | 
						|
				if (haveSetting) {
 | 
						|
					if (notifyByFrom->isMuted()) {
 | 
						|
						history->popNotification(item);
 | 
						|
						return;
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					Auth().api().requestNotifySetting(notifyByFrom);
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				history->popNotification(item);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		if (notifyByFrom && notifyByFrom->notifySettingsUnknown()) {
 | 
						|
			Auth().api().requestNotifySetting(notifyByFrom);
 | 
						|
		}
 | 
						|
		Auth().api().requestNotifySetting(history->peer);
 | 
						|
	}
 | 
						|
	if (!item->notificationReady()) {
 | 
						|
		haveSetting = false;
 | 
						|
	}
 | 
						|
 | 
						|
	int delay = item->Has<HistoryMessageForwarded>() ? 500 : 100, t = unixtime();
 | 
						|
	auto ms = getms(true);
 | 
						|
	bool isOnline = App::main()->lastWasOnline(), otherNotOld = ((cOtherOnline() * 1000LL) + Global::OnlineCloudTimeout() > t * 1000LL);
 | 
						|
	bool otherLaterThanMe = (cOtherOnline() * 1000LL + (ms - App::main()->lastSetOnline()) > t * 1000LL);
 | 
						|
	if (!isOnline && otherNotOld && otherLaterThanMe) {
 | 
						|
		delay = Global::NotifyCloudDelay();
 | 
						|
	} else if (cOtherOnline() >= t) {
 | 
						|
		delay = Global::NotifyDefaultDelay();
 | 
						|
	}
 | 
						|
 | 
						|
	auto when = ms + delay;
 | 
						|
	_whenAlerts[history].insert(when, notifyByFrom);
 | 
						|
	if (Global::DesktopNotify() && !Platform::Notifications::SkipToast()) {
 | 
						|
		auto &whenMap = _whenMaps[history];
 | 
						|
		if (whenMap.constFind(item->id) == whenMap.cend()) {
 | 
						|
			whenMap.insert(item->id, when);
 | 
						|
		}
 | 
						|
 | 
						|
		auto &addTo = haveSetting ? _waiters : _settingWaiters;
 | 
						|
		auto it = addTo.constFind(history);
 | 
						|
		if (it == addTo.cend() || it->when > when) {
 | 
						|
			addTo.insert(history, Waiter(item->id, when, notifyByFrom));
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (haveSetting) {
 | 
						|
		if (!_waitTimer.isActive() || _waitTimer.remainingTime() > delay) {
 | 
						|
			_waitTimer.start(delay);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void System::clearAll() {
 | 
						|
	_manager->clearAll();
 | 
						|
 | 
						|
	for (auto i = _whenMaps.cbegin(), e = _whenMaps.cend(); i != e; ++i) {
 | 
						|
		i.key()->clearNotifications();
 | 
						|
	}
 | 
						|
	_whenMaps.clear();
 | 
						|
	_whenAlerts.clear();
 | 
						|
	_waiters.clear();
 | 
						|
	_settingWaiters.clear();
 | 
						|
}
 | 
						|
 | 
						|
void System::clearFromHistory(History *history) {
 | 
						|
	_manager->clearFromHistory(history);
 | 
						|
 | 
						|
	history->clearNotifications();
 | 
						|
	_whenMaps.remove(history);
 | 
						|
	_whenAlerts.remove(history);
 | 
						|
	_waiters.remove(history);
 | 
						|
	_settingWaiters.remove(history);
 | 
						|
 | 
						|
	_waitTimer.stop();
 | 
						|
	showNext();
 | 
						|
}
 | 
						|
 | 
						|
void System::clearFromItem(HistoryItem *item) {
 | 
						|
	_manager->clearFromItem(item);
 | 
						|
}
 | 
						|
 | 
						|
void System::clearAllFast() {
 | 
						|
	_manager->clearAllFast();
 | 
						|
 | 
						|
	_whenMaps.clear();
 | 
						|
	_whenAlerts.clear();
 | 
						|
	_waiters.clear();
 | 
						|
	_settingWaiters.clear();
 | 
						|
}
 | 
						|
 | 
						|
void System::checkDelayed() {
 | 
						|
	for (auto i = _settingWaiters.begin(); i != _settingWaiters.end();) {
 | 
						|
		const auto history = i.key();
 | 
						|
		const auto peer = history->peer;
 | 
						|
		auto loaded = false;
 | 
						|
		auto muted = false;
 | 
						|
		if (!peer->notifySettingsUnknown()) {
 | 
						|
			if (!peer->isMuted()) {
 | 
						|
				loaded = true;
 | 
						|
			} else if (const auto from = i.value().notifyByFrom) {
 | 
						|
				if (!from->notifySettingsUnknown()) {
 | 
						|
					if (!from->isMuted()) {
 | 
						|
						loaded = true;
 | 
						|
					} else {
 | 
						|
						loaded = muted = true;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				loaded = muted = true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (loaded) {
 | 
						|
			const auto fullId = FullMsgId(
 | 
						|
				history->channelId(),
 | 
						|
				i.value().msg);
 | 
						|
			if (const auto item = App::histItemById(fullId)) {
 | 
						|
				if (!item->notificationReady()) {
 | 
						|
					loaded = false;
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				muted = true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (loaded) {
 | 
						|
			if (!muted) {
 | 
						|
				_waiters.insert(i.key(), i.value());
 | 
						|
			}
 | 
						|
			i = _settingWaiters.erase(i);
 | 
						|
		} else {
 | 
						|
			++i;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	_waitTimer.stop();
 | 
						|
	showNext();
 | 
						|
}
 | 
						|
 | 
						|
void System::showNext() {
 | 
						|
	if (App::quitting()) return;
 | 
						|
 | 
						|
	auto ms = getms(true), nextAlert = 0LL;
 | 
						|
	bool alert = false;
 | 
						|
	int32 now = unixtime();
 | 
						|
	for (auto i = _whenAlerts.begin(); i != _whenAlerts.end();) {
 | 
						|
		while (!i.value().isEmpty() && i.value().begin().key() <= ms) {
 | 
						|
			const auto peer = i.key()->peer;
 | 
						|
			const auto peerUnknown = peer->notifySettingsUnknown();
 | 
						|
			const auto peerAlert = peerUnknown ? false : !peer->isMuted();
 | 
						|
			const auto from = i.value().begin().value();
 | 
						|
			const auto fromUnknown = (!from || from->notifySettingsUnknown());
 | 
						|
			const auto fromAlert = fromUnknown ? false : !from->isMuted();
 | 
						|
			if (peerAlert || fromAlert) {
 | 
						|
				alert = true;
 | 
						|
			}
 | 
						|
			while (!i.value().isEmpty()
 | 
						|
				&& i.value().begin().key() <= ms + kMinimalAlertDelay) {
 | 
						|
				i.value().erase(i.value().begin());
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (i.value().isEmpty()) {
 | 
						|
			i = _whenAlerts.erase(i);
 | 
						|
		} else {
 | 
						|
			if (!nextAlert || nextAlert > i.value().begin().key()) {
 | 
						|
				nextAlert = i.value().begin().key();
 | 
						|
			}
 | 
						|
			++i;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (alert) {
 | 
						|
		Platform::Notifications::FlashBounce();
 | 
						|
		if (Global::SoundNotify() && !Platform::Notifications::SkipAudio()) {
 | 
						|
			ensureSoundCreated();
 | 
						|
			_soundTrack->playOnce();
 | 
						|
			emit Media::Player::mixer()->suppressAll(_soundTrack->getLengthMs());
 | 
						|
			emit Media::Player::mixer()->faderOnTimer();
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (_waiters.isEmpty() || !Global::DesktopNotify() || Platform::Notifications::SkipToast()) {
 | 
						|
		if (nextAlert) {
 | 
						|
			_waitTimer.start(nextAlert - ms);
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	while (true) {
 | 
						|
		auto next = 0LL;
 | 
						|
		HistoryItem *notifyItem = nullptr;
 | 
						|
		History *notifyHistory = nullptr;
 | 
						|
		for (auto i = _waiters.begin(); i != _waiters.end();) {
 | 
						|
			History *history = i.key();
 | 
						|
			if (history->currentNotification() && history->currentNotification()->id != i.value().msg) {
 | 
						|
				auto j = _whenMaps.find(history);
 | 
						|
				if (j == _whenMaps.end()) {
 | 
						|
					history->clearNotifications();
 | 
						|
					i = _waiters.erase(i);
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
				do {
 | 
						|
					auto k = j.value().constFind(history->currentNotification()->id);
 | 
						|
					if (k != j.value().cend()) {
 | 
						|
						i.value().msg = k.key();
 | 
						|
						i.value().when = k.value();
 | 
						|
						break;
 | 
						|
					}
 | 
						|
					history->skipNotification();
 | 
						|
				} while (history->currentNotification());
 | 
						|
			}
 | 
						|
			if (!history->currentNotification()) {
 | 
						|
				_whenMaps.remove(history);
 | 
						|
				i = _waiters.erase(i);
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
			auto when = i.value().when;
 | 
						|
			if (!notifyItem || next > when) {
 | 
						|
				next = when;
 | 
						|
				notifyItem = history->currentNotification();
 | 
						|
				notifyHistory = history;
 | 
						|
			}
 | 
						|
			++i;
 | 
						|
		}
 | 
						|
		if (notifyItem) {
 | 
						|
			if (next > ms) {
 | 
						|
				if (nextAlert && nextAlert < next) {
 | 
						|
					next = nextAlert;
 | 
						|
					nextAlert = 0;
 | 
						|
				}
 | 
						|
				_waitTimer.start(next - ms);
 | 
						|
				break;
 | 
						|
			} else {
 | 
						|
				auto forwardedItem = notifyItem->Has<HistoryMessageForwarded>() ? notifyItem : nullptr; // forwarded notify grouping
 | 
						|
				auto forwardedCount = 1;
 | 
						|
 | 
						|
				auto ms = getms(true);
 | 
						|
				auto history = notifyItem->history();
 | 
						|
				auto j = _whenMaps.find(history);
 | 
						|
				if (j == _whenMaps.cend()) {
 | 
						|
					history->clearNotifications();
 | 
						|
				} else {
 | 
						|
					auto nextNotify = (HistoryItem*)nullptr;
 | 
						|
					do {
 | 
						|
						history->skipNotification();
 | 
						|
						if (!history->hasNotification()) {
 | 
						|
							break;
 | 
						|
						}
 | 
						|
 | 
						|
						j.value().remove((forwardedItem ? forwardedItem : notifyItem)->id);
 | 
						|
						do {
 | 
						|
							auto k = j.value().constFind(history->currentNotification()->id);
 | 
						|
							if (k != j.value().cend()) {
 | 
						|
								nextNotify = history->currentNotification();
 | 
						|
								_waiters.insert(notifyHistory, Waiter(k.key(), k.value(), 0));
 | 
						|
								break;
 | 
						|
							}
 | 
						|
							history->skipNotification();
 | 
						|
						} while (history->hasNotification());
 | 
						|
						if (nextNotify) {
 | 
						|
							if (forwardedItem) {
 | 
						|
								auto nextForwarded = nextNotify->Has<HistoryMessageForwarded>() ? nextNotify : nullptr;
 | 
						|
								if (nextForwarded
 | 
						|
									&& forwardedItem->author() == nextForwarded->author()
 | 
						|
									&& qAbs(int64(nextForwarded->date()) - int64(forwardedItem->date())) < 2) {
 | 
						|
									forwardedItem = nextForwarded;
 | 
						|
									++forwardedCount;
 | 
						|
								} else {
 | 
						|
									nextNotify = nullptr;
 | 
						|
								}
 | 
						|
							} else {
 | 
						|
								nextNotify = nullptr;
 | 
						|
							}
 | 
						|
						}
 | 
						|
					} while (nextNotify);
 | 
						|
				}
 | 
						|
 | 
						|
				_manager->showNotification(notifyItem, forwardedCount);
 | 
						|
 | 
						|
				if (!history->hasNotification()) {
 | 
						|
					_waiters.remove(history);
 | 
						|
					_whenMaps.remove(history);
 | 
						|
					continue;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (nextAlert) {
 | 
						|
		_waitTimer.start(nextAlert - ms);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void System::ensureSoundCreated() {
 | 
						|
	if (_soundTrack) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	_soundTrack = Media::Audio::Current().createTrack();
 | 
						|
	_soundTrack->fillFromFile(
 | 
						|
		Auth().settings().getSoundPath(qsl("msg_incoming")));
 | 
						|
}
 | 
						|
 | 
						|
void System::updateAll() {
 | 
						|
	_manager->updateAll();
 | 
						|
}
 | 
						|
 | 
						|
Manager::DisplayOptions Manager::getNotificationOptions(HistoryItem *item) {
 | 
						|
	auto hideEverything = (App::passcoded() || Global::ScreenIsLocked());
 | 
						|
 | 
						|
	DisplayOptions result;
 | 
						|
	result.hideNameAndPhoto = hideEverything || (Global::NotifyView() > dbinvShowName);
 | 
						|
	result.hideMessageText = hideEverything || (Global::NotifyView() > dbinvShowPreview);
 | 
						|
	result.hideReplyButton = result.hideMessageText || !item || !item->history()->peer->canWrite();
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Manager::notificationActivated(PeerId peerId, MsgId msgId) {
 | 
						|
	onBeforeNotificationActivated(peerId, msgId);
 | 
						|
	if (auto window = App::wnd()) {
 | 
						|
		auto history = App::history(peerId);
 | 
						|
		window->showFromTray();
 | 
						|
		window->reActivateWindow();
 | 
						|
		if (App::passcoded()) {
 | 
						|
			window->setInnerFocus();
 | 
						|
			system()->clearAll();
 | 
						|
		} else {
 | 
						|
			openNotificationMessage(history, msgId);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	onAfterNotificationActivated(peerId, msgId);
 | 
						|
}
 | 
						|
 | 
						|
void Manager::openNotificationMessage(
 | 
						|
		not_null<History*> history,
 | 
						|
		MsgId messageId) {
 | 
						|
	const auto openExactlyMessage = [&] {
 | 
						|
		if (history->peer->isUser()
 | 
						|
			|| history->peer->isChannel()
 | 
						|
			|| !IsServerMsgId(messageId)) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		const auto item = App::histItemById(history->channelId(), messageId);
 | 
						|
		if (!item || !item->mentionsMe()) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	}();
 | 
						|
	const auto messageFeed = [&] {
 | 
						|
		if (const auto channel = history->peer->asChannel()) {
 | 
						|
			return channel->feed();
 | 
						|
		}
 | 
						|
		return (Data::Feed*)nullptr;
 | 
						|
	}();
 | 
						|
	if (openExactlyMessage) {
 | 
						|
		Ui::showPeerHistory(history, messageId);
 | 
						|
	} else if (messageFeed) {
 | 
						|
		App::wnd()->controller()->showSection(
 | 
						|
			HistoryFeed::Memento(messageFeed));
 | 
						|
	} else {
 | 
						|
		Ui::showPeerHistory(history, ShowAtUnreadMsgId);
 | 
						|
	}
 | 
						|
	system()->clearFromHistory(history);
 | 
						|
}
 | 
						|
 | 
						|
void Manager::notificationReplied(
 | 
						|
		PeerId peerId,
 | 
						|
		MsgId msgId,
 | 
						|
		const QString &reply) {
 | 
						|
	if (!peerId) return;
 | 
						|
 | 
						|
	auto history = App::history(peerId);
 | 
						|
 | 
						|
	auto message = MainWidget::MessageToSend(history);
 | 
						|
	message.textWithTags = { reply, TextWithTags::Tags() };
 | 
						|
	message.replyTo = (msgId > 0 && !history->peer->isUser()) ? msgId : 0;
 | 
						|
	message.clearDraft = false;
 | 
						|
	if (auto main = App::main()) {
 | 
						|
		main->sendMessage(message);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) {
 | 
						|
	const auto options = getNotificationOptions(item);
 | 
						|
 | 
						|
	const auto title = options.hideNameAndPhoto ? qsl("Telegram Desktop") : item->history()->peer->name;
 | 
						|
	const auto subtitle = options.hideNameAndPhoto ? QString() : item->notificationHeader();
 | 
						|
	const auto text = options.hideMessageText
 | 
						|
		? lang(lng_notification_preview)
 | 
						|
		: (forwardedCount < 2
 | 
						|
			? item->notificationText()
 | 
						|
			: lng_forward_messages(lt_count, forwardedCount));
 | 
						|
 | 
						|
	doShowNativeNotification(
 | 
						|
		item->history()->peer,
 | 
						|
		item->id,
 | 
						|
		title,
 | 
						|
		subtitle,
 | 
						|
		text,
 | 
						|
		options.hideNameAndPhoto,
 | 
						|
		options.hideReplyButton);
 | 
						|
}
 | 
						|
 | 
						|
System::~System() = default;
 | 
						|
 | 
						|
} // namespace Notifications
 | 
						|
} // namespace Window
 |