From 7f950d2de2ea62b156201b9106652a626d701691 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 2 Oct 2016 19:32:46 +0300 Subject: [PATCH] Unified code for notification activation events in Windows version. --- .../platform/mac/notifications_manager_mac.h | 3 +- .../platform/win/main_window_win.cpp | 5 +- .../win/notifications_manager_win.cpp | 594 ++++++++++++++++- .../platform/win/notifications_manager_win.h | 32 +- .../platform/win/windows_toasts.cpp | 618 ------------------ .../SourceFiles/platform/win/windows_toasts.h | 61 -- Telegram/SourceFiles/pspecific_win.cpp | 2 +- .../window/notifications_manager.cpp | 25 + .../window/notifications_manager.h | 6 + .../window/notifications_manager_default.cpp | 15 +- Telegram/gyp/Telegram.gyp | 4 - 11 files changed, 660 insertions(+), 705 deletions(-) delete mode 100644 Telegram/SourceFiles/platform/win/windows_toasts.cpp delete mode 100644 Telegram/SourceFiles/platform/win/windows_toasts.h diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h index 3e4c67d94..2ea20da9e 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h @@ -36,11 +36,12 @@ public: Manager(); ~Manager(); -private: +protected: void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) override; void doClearAllFast() override; void doClearFromHistory(History *history) override; +private: class Impl; std_::unique_ptr _impl; diff --git a/Telegram/SourceFiles/platform/win/main_window_win.cpp b/Telegram/SourceFiles/platform/win/main_window_win.cpp index 2003b23a9..407c49881 100644 --- a/Telegram/SourceFiles/platform/win/main_window_win.cpp +++ b/Telegram/SourceFiles/platform/win/main_window_win.cpp @@ -21,7 +21,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "platform/win/main_window_win.h" -#include "platform/win/windows_toasts.h" +#include "platform/win/notifications_manager_win.h" #include "platform/win/windows_dlls.h" #include "window/notifications_manager.h" #include "mainwindow.h" @@ -814,7 +814,6 @@ void MainWindow::psInitFrameless() { } // RegisterApplicationRestart(NULL, 0); - Toasts::start(); psInitSysMenu(); } @@ -862,7 +861,7 @@ void MainWindow::psUpdatedPosition() { } bool MainWindow::psHasNativeNotifications() { - return (Toasts::manager() != nullptr); + return Notifications::supported(); } Q_DECLARE_METATYPE(QMargins); diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 7f1066ac3..2cf32473b 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -21,24 +21,606 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "stdafx.h" #include "platform/win/notifications_manager_win.h" -#include "platform/win/windows_toasts.h" +#include "platform/win/windows_app_user_model_id.h" +#include "platform/win/windows_dlls.h" +#include "mainwindow.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include + +HICON qt_pixmapToWinHICON(const QPixmap &); + +using namespace Microsoft::WRL; +using namespace ABI::Windows::UI::Notifications; +using namespace ABI::Windows::Data::Xml::Dom; +using namespace Windows::Foundation; namespace Platform { namespace Notifications { +namespace { -void start() { - Toasts::start(); +// Delete notify photo file after 1 minute of not using. +constexpr int kNotifyDeletePhotoAfterMs = 60000; + +NeverFreedPointer ManagerInstance; + +ComPtr _notificationManager; +ComPtr _notifier; +ComPtr _notificationFactory; + +struct NotificationPtr { + NotificationPtr() { + } + NotificationPtr(const ComPtr &ptr) : p(ptr) { + } + ComPtr p; +}; +using Notifications = QMap>; +Notifications _notifications; +struct Image { + uint64 until; + QString path; +}; +using Images = QMap; +Images _images; +bool _imageSavedFlag = false; + +class StringReferenceWrapper { +public: + StringReferenceWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() { + HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + if (!SUCCEEDED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } + + ~StringReferenceWrapper() { + Dlls::WindowsDeleteString(_hstring); + } + + template + StringReferenceWrapper(_In_reads_(N) wchar_t const (&stringRef)[N]) throw() { + UINT32 length = N - 1; + HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + if (!SUCCEEDED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } + + template + StringReferenceWrapper(_In_reads_(_) wchar_t(&stringRef)[_]) throw() { + UINT32 length; + HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length); + if (!SUCCEEDED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + + Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); + } + + HSTRING Get() const throw() { + return _hstring; + } + +private: + HSTRING _hstring; + HSTRING_HEADER _header; + +}; + +template +_Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) { + return Dlls::RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory)); } -Window::Notifications::Manager *manager() { +template +inline HRESULT wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef factory) throw() { + return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf()); +} + +bool init() { + if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS8) { + return false; + } + if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr) + || (Dlls::PropVariantToString == nullptr) + || (Dlls::RoGetActivationFactory == nullptr) + || (Dlls::WindowsCreateStringReference == nullptr) + || (Dlls::WindowsDeleteString == nullptr)) { + return false; + } + + if (!AppUserModelId::validateShortcut()) { + return false; + } + + auto appUserModelId = AppUserModelId::getId(); + if (!SUCCEEDED(Dlls::SetCurrentProcessExplicitAppUserModelID(appUserModelId))) { + return false; + } + if (!SUCCEEDED(wrap_GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &_notificationManager))) { + return false; + } + if (!SUCCEEDED(_notificationManager->CreateToastNotifierWithId(StringReferenceWrapper(appUserModelId, wcslen(appUserModelId)).Get(), &_notifier))) { + return false; + } + if (!SUCCEEDED(wrap_GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &_notificationFactory))) { + return false; + } + QDir().mkpath(cWorkingDir() + qsl("tdata/temp")); + return true; +} + +HRESULT SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml) { + ComPtr inputText; + + HRESULT hr = xml->CreateTextNode(inputString, &inputText); + if (!SUCCEEDED(hr)) return hr; + ComPtr inputTextNode; + + hr = inputText.As(&inputTextNode); + if (!SUCCEEDED(hr)) return hr; + + ComPtr pAppendedChild; + return node->AppendChild(inputTextNode.Get(), &pAppendedChild); +} + +HRESULT SetAudioSilent(_In_ IXmlDocument *toastXml) { + ComPtr nodeList; + HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList); + if (!SUCCEEDED(hr)) return hr; + + ComPtr audioNode; + hr = nodeList->Item(0, &audioNode); + if (!SUCCEEDED(hr)) return hr; + + if (audioNode) { + ComPtr audioElement; + hr = audioNode.As(&audioElement); + if (!SUCCEEDED(hr)) return hr; + + hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get()); + if (!SUCCEEDED(hr)) return hr; + } else { + ComPtr audioElement; + hr = toastXml->CreateElement(StringReferenceWrapper(L"audio").Get(), &audioElement); + if (!SUCCEEDED(hr)) return hr; + + hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get()); + if (!SUCCEEDED(hr)) return hr; + + ComPtr audioNode; + hr = audioElement.As(&audioNode); + if (!SUCCEEDED(hr)) return hr; + + ComPtr nodeList; + hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &nodeList); + if (!SUCCEEDED(hr)) return hr; + + ComPtr toastNode; + hr = nodeList->Item(0, &toastNode); + if (!SUCCEEDED(hr)) return hr; + + ComPtr appendedNode; + hr = toastNode->AppendChild(audioNode.Get(), &appendedNode); + } + return hr; +} + +HRESULT SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml) { + wchar_t imageSrc[MAX_PATH] = L"file:///"; + HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath); + if (!SUCCEEDED(hr)) return hr; + + ComPtr nodeList; + hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); + if (!SUCCEEDED(hr)) return hr; + + ComPtr imageNode; + hr = nodeList->Item(0, &imageNode); + if (!SUCCEEDED(hr)) return hr; + + ComPtr attributes; + hr = imageNode->get_Attributes(&attributes); + if (!SUCCEEDED(hr)) return hr; + + ComPtr srcAttribute; + hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute); + if (!SUCCEEDED(hr)) return hr; + + return SetNodeValueString(StringReferenceWrapper(imageSrc).Get(), srcAttribute.Get(), toastXml); +} + +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastActivatedEventHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastDismissedEventHandler; +typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastFailedEventHandler; + +class ToastEventHandler : public Implements { +public: + ToastEventHandler::ToastEventHandler(const PeerId &peer, MsgId msg) : _ref(1), _peerId(peer), _msgId(msg) { + } + ~ToastEventHandler() { + } + + // DesktopToastActivatedEventHandler + IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IInspectable* args) { + if (auto manager = ManagerInstance.data()) { + manager->notificationActivated(_peerId, _msgId); + } + return S_OK; + } + + // DesktopToastDismissedEventHandler + IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastDismissedEventArgs *e) { + ToastDismissalReason tdr; + if (SUCCEEDED(e->get_Reason(&tdr))) { + switch (tdr) { + case ToastDismissalReason_ApplicationHidden: + break; + case ToastDismissalReason_UserCanceled: + case ToastDismissalReason_TimedOut: + default: + if (auto manager = ManagerInstance.data()) { + manager->clearNotification(_peerId, _msgId); + } + break; + } + } + return S_OK; + } + + // DesktopToastFailedEventHandler + IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastFailedEventArgs *e) { + if (auto manager = ManagerInstance.data()) { + manager->clearNotification(_peerId, _msgId); + } + return S_OK; + } + + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef() { + return InterlockedIncrement(&_ref); + } + + IFACEMETHODIMP_(ULONG) Release() { + ULONG l = InterlockedDecrement(&_ref); + if (l == 0) delete this; + return l; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) { + if (IsEqualIID(riid, IID_IUnknown)) + *ppv = static_cast(static_cast(this)); + else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) + *ppv = static_cast(this); + else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) + *ppv = static_cast(this); + else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler))) + *ppv = static_cast(this); + else *ppv = nullptr; + + if (*ppv) { + reinterpret_cast(*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + +private: + ULONG _ref; + PeerId _peerId; + MsgId _msgId; + +}; + +QString getImage(const StorageKey &key, PeerData *peer) { + uint64 ms = getms(true); + auto i = _images.find(key); + if (i != _images.cend()) { + if (i->until) { + i->until = ms + kNotifyDeletePhotoAfterMs; + if (auto manager = ManagerInstance.data()) { + manager->clearPhotosInMs(-kNotifyDeletePhotoAfterMs); + } + } + } else { + Image v; + if (key.first) { + v.until = ms + kNotifyDeletePhotoAfterMs; + if (auto manager = ManagerInstance.data()) { + manager->clearPhotosInMs(-kNotifyDeletePhotoAfterMs); + } + } else { + v.until = 0; + } + v.path = cWorkingDir() + qsl("tdata/temp/") + QString::number(rand_value(), 16) + qsl(".png"); + if (key.first || key.second) { + peer->saveUserpic(v.path, st::notifyMacPhotoSize); + } else { + App::wnd()->iconLarge().save(v.path, "PNG"); + } + i = _images.insert(key, v); + _imageSavedFlag = true; + } + return i->path; +} + +} // namespace + +void start() { + if (init()) { + ManagerInstance.makeIfNull(); + } +} + +Manager *manager() { if (Global::WindowsNotifications()) { - return Toasts::manager(); + return ManagerInstance.data(); } return nullptr; } +bool supported() { + return ManagerInstance.data() != nullptr; +} + void finish() { - Toasts::finish(); + ManagerInstance.clear(); +} + +uint64 clearImages(uint64 ms) { + uint64 result = 0; + for (auto i = _images.begin(); i != _images.end();) { + if (!i->until) { + ++i; + continue; + } + if (i->until <= ms) { + QFile(i->path).remove(); + i = _images.erase(i); + } else { + if (!result) { + result = i->until; + } else { + accumulate_min(result, i->until); + } + ++i; + } + } + return result; +} + +class Manager::Impl { +public: + bool showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton); + void clearAll(); + void clearFromHistory(History *history); + void beforeNotificationActivated(PeerId peerId, MsgId msgId); + void afterNotificationActivated(PeerId peerId, MsgId msgId); + void clearNotification(PeerId peerId, MsgId msgId); + + ~Impl(); + +private: + QTimer _clearPhotosTimer; + friend class Manager; + +}; + +Manager::Impl::~Impl() { + _notifications.clear(); + if (_notificationManager) _notificationManager.Reset(); + if (_notifier) _notifier.Reset(); + if (_notificationFactory) _notificationFactory.Reset(); + + if (_imageSavedFlag) { + psDeleteDir(cWorkingDir() + qsl("tdata/temp")); + } +} + +void Manager::Impl::clearAll() { + if (!_notifier) return; + + auto temp = createAndSwap(_notifications); + for_const (auto ¬ifications, temp) { + for_const (auto ¬ification, notifications) { + _notifier->Hide(notification.p.Get()); + } + } +} + +void Manager::Impl::clearFromHistory(History *history) { + if (!_notifier) return; + + auto i = _notifications.find(history->peer->id); + if (i != _notifications.cend()) { + auto temp = createAndSwap(i.value()); + _notifications.erase(i); + + for (auto j = temp.cbegin(), e = temp.cend(); j != e; ++j) { + _notifier->Hide(j->p.Get()); + } + } +} + +void Manager::Impl::beforeNotificationActivated(PeerId peerId, MsgId msgId) { + clearNotification(peerId, msgId); +} + +void Manager::Impl::afterNotificationActivated(PeerId peerId, MsgId msgId) { + if (auto window = App::wnd()) { + SetForegroundWindow(window->psHwnd()); + } +} + +void Manager::Impl::clearNotification(PeerId peerId, MsgId msgId) { + auto i = _notifications.find(peerId); + if (i != _notifications.cend()) { + i.value().remove(msgId); + if (i.value().isEmpty()) { + _notifications.erase(i); + } + } +} + +bool Manager::Impl::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) { + if (!_notificationManager || !_notifier || !_notificationFactory) return false; + + ComPtr toastXml; + bool withSubtitle = !subtitle.isEmpty(); + + HRESULT hr = _notificationManager->GetTemplateContent(withSubtitle ? ToastTemplateType_ToastImageAndText04 : ToastTemplateType_ToastImageAndText02, &toastXml); + if (!SUCCEEDED(hr)) return false; + + hr = SetAudioSilent(toastXml.Get()); + if (!SUCCEEDED(hr)) return false; + + StorageKey key; + QString imagePath; + if (showUserpic) { + key = peer->userpicUniqueKey(); + } else { + key = StorageKey(0, 0); + } + QString image = getImage(key, peer); + std::wstring wimage = QDir::toNativeSeparators(image).toStdWString(); + + hr = SetImageSrc(wimage.c_str(), toastXml.Get()); + if (!SUCCEEDED(hr)) return false; + + ComPtr nodeList; + hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); + if (!SUCCEEDED(hr)) return false; + + UINT32 nodeListLength; + hr = nodeList->get_Length(&nodeListLength); + if (!SUCCEEDED(hr)) return false; + + if (nodeListLength < (withSubtitle ? 3U : 2U)) return false; + + { + ComPtr textNode; + hr = nodeList->Item(0, &textNode); + if (!SUCCEEDED(hr)) return false; + + std::wstring wtitle = title.toStdWString(); + hr = SetNodeValueString(StringReferenceWrapper(wtitle.data(), wtitle.size()).Get(), textNode.Get(), toastXml.Get()); + if (!SUCCEEDED(hr)) return false; + } + if (withSubtitle) { + ComPtr textNode; + hr = nodeList->Item(1, &textNode); + if (!SUCCEEDED(hr)) return false; + + std::wstring wsubtitle = subtitle.toStdWString(); + hr = SetNodeValueString(StringReferenceWrapper(wsubtitle.data(), wsubtitle.size()).Get(), textNode.Get(), toastXml.Get()); + if (!SUCCEEDED(hr)) return false; + } + { + ComPtr textNode; + hr = nodeList->Item(withSubtitle ? 2 : 1, &textNode); + if (!SUCCEEDED(hr)) return false; + + std::wstring wmsg = msg.toStdWString(); + hr = SetNodeValueString(StringReferenceWrapper(wmsg.data(), wmsg.size()).Get(), textNode.Get(), toastXml.Get()); + if (!SUCCEEDED(hr)) return false; + } + + ComPtr toast; + hr = _notificationFactory->CreateToastNotification(toastXml.Get(), &toast); + if (!SUCCEEDED(hr)) return false; + + EventRegistrationToken activatedToken, dismissedToken, failedToken; + ComPtr eventHandler(new ToastEventHandler(peer->id, msgId)); + + hr = toast->add_Activated(eventHandler.Get(), &activatedToken); + if (!SUCCEEDED(hr)) return false; + + hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); + if (!SUCCEEDED(hr)) return false; + + hr = toast->add_Failed(eventHandler.Get(), &failedToken); + if (!SUCCEEDED(hr)) return false; + + auto i = _notifications.find(peer->id); + if (i != _notifications.cend()) { + auto j = i->find(msgId); + if (j != i->cend()) { + ComPtr notify = j->p; + i->erase(j); + _notifier->Hide(notify.Get()); + i = _notifications.find(peer->id); + } + } + if (i == _notifications.cend()) { + i = _notifications.insert(peer->id, QMap()); + } + hr = _notifier->Show(toast.Get()); + if (!SUCCEEDED(hr)) { + i = _notifications.find(peer->id); + if (i != _notifications.cend() && i->isEmpty()) _notifications.erase(i); + return false; + } + _notifications[peer->id].insert(msgId, toast); + + return true; +} + +Manager::Manager() : _impl(std_::make_unique()) { + connect(&_impl->_clearPhotosTimer, SIGNAL(timeout()), this, SLOT(onClearPhotos())); +} + +void Manager::clearPhotosInMs(int ms) { + if (ms < 0) { + ms = -ms; + if (_impl->_clearPhotosTimer.isActive() && _impl->_clearPhotosTimer.remainingTime() <= ms) { + return; + } + } + _impl->_clearPhotosTimer.start(ms); +} + +void Manager::clearNotification(PeerId peerId, MsgId msgId) { + _impl->clearNotification(peerId, msgId); +} + +void Manager::onClearPhotos() { + auto ms = getms(true); + auto minuntil = clearImages(ms); + if (minuntil) { + clearPhotosInMs(int32(minuntil - ms)); + } +} + +Manager::~Manager() = default; + +void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) { + _impl->showNotification(peer, msgId, title, subtitle, showUserpic, msg, showReplyButton); +} + +void Manager::doClearAllFast() { + _impl->clearAll(); +} + +void Manager::doClearFromHistory(History *history) { + _impl->clearFromHistory(history); +} + +void Manager::onBeforeNotificationActivated(PeerId peerId, MsgId msgId) { + _impl->beforeNotificationActivated(peerId, msgId); +} + +void Manager::onAfterNotificationActivated(PeerId peerId, MsgId msgId) { + _impl->afterNotificationActivated(peerId, msgId); } } // namespace Notifications diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.h b/Telegram/SourceFiles/platform/win/notifications_manager_win.h index 4478c9bad..f1e24d057 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.h +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.h @@ -25,12 +25,42 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org namespace Platform { namespace Notifications { +class Manager; + void start(); -Window::Notifications::Manager *manager(); +Manager *manager(); +bool supported(); void finish(); inline void defaultNotificationShown(QWidget *widget) { } +class Manager : public QObject, public Window::Notifications::NativeManager { + Q_OBJECT + +public: + Manager(); + + void clearPhotosInMs(int ms); + void clearNotification(PeerId peerId, MsgId msgId); + + ~Manager(); + +private slots: + void onClearPhotos(); + +protected: + void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) override; + void doClearAllFast() override; + void doClearFromHistory(History *history) override; + void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) override; + void onAfterNotificationActivated(PeerId peerId, MsgId msgId) override; + +private: + class Impl; + std_::unique_ptr _impl; + +}; + } // namespace Notifications } // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/windows_toasts.cpp b/Telegram/SourceFiles/platform/win/windows_toasts.cpp deleted file mode 100644 index 5f4c967c0..000000000 --- a/Telegram/SourceFiles/platform/win/windows_toasts.cpp +++ /dev/null @@ -1,618 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop 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. - -It 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. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#include "stdafx.h" -#include "platform/win/windows_toasts.h" - -#include "platform/win/windows_app_user_model_id.h" -#include "platform/win/windows_dlls.h" -#include "mainwindow.h" - -#include -#include - -#include -#include -#include -#include - -#include -#include - -HICON qt_pixmapToWinHICON(const QPixmap &); - -using namespace Microsoft::WRL; -using namespace ABI::Windows::UI::Notifications; -using namespace ABI::Windows::Data::Xml::Dom; -using namespace Windows::Foundation; - -namespace Platform { -namespace Toasts { -namespace { - -// Delete notify photo file after 1 minute of not using. -constexpr int kNotifyDeletePhotoAfterMs = 60000; - -NeverFreedPointer ManagerInstance; - -ComPtr _notificationManager; -ComPtr _notifier; -ComPtr _notificationFactory; - -struct NotificationPtr { - NotificationPtr() { - } - NotificationPtr(const ComPtr &ptr) : p(ptr) { - } - ComPtr p; -}; -using Notifications = QMap>; -Notifications _notifications; -struct Image { - uint64 until; - QString path; -}; -using Images = QMap; -Images _images; -bool _imageSavedFlag = false; - -class StringReferenceWrapper { -public: - StringReferenceWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) throw() { - HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); - if (!SUCCEEDED(hr)) { - RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); - } - } - - ~StringReferenceWrapper() { - Dlls::WindowsDeleteString(_hstring); - } - - template - StringReferenceWrapper(_In_reads_(N) wchar_t const (&stringRef)[N]) throw() { - UINT32 length = N - 1; - HRESULT hr = Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); - if (!SUCCEEDED(hr)) { - RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); - } - } - - template - StringReferenceWrapper(_In_reads_(_) wchar_t(&stringRef)[_]) throw() { - UINT32 length; - HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length); - if (!SUCCEEDED(hr)) { - RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); - } - - Dlls::WindowsCreateStringReference(stringRef, length, &_header, &_hstring); - } - - HSTRING Get() const throw() { - return _hstring; - } - -private: - HSTRING _hstring; - HSTRING_HEADER _header; - -}; - -template -_Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) { - return Dlls::RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory)); -} - -template -inline HRESULT wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef factory) throw() { - return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf()); -} - -bool init() { - if (QSysInfo::windowsVersion() < QSysInfo::WV_WINDOWS8) { - return false; - } - if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr) - || (Dlls::PropVariantToString == nullptr) - || (Dlls::RoGetActivationFactory == nullptr) - || (Dlls::WindowsCreateStringReference == nullptr) - || (Dlls::WindowsDeleteString == nullptr)) { - return false; - } - - if (!AppUserModelId::validateShortcut()) { - return false; - } - - auto appUserModelId = AppUserModelId::getId(); - if (!SUCCEEDED(Dlls::SetCurrentProcessExplicitAppUserModelID(appUserModelId))) { - return false; - } - if (!SUCCEEDED(wrap_GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &_notificationManager))) { - return false; - } - if (!SUCCEEDED(_notificationManager->CreateToastNotifierWithId(StringReferenceWrapper(appUserModelId, wcslen(appUserModelId)).Get(), &_notifier))) { - return false; - } - if (!SUCCEEDED(wrap_GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &_notificationFactory))) { - return false; - } - QDir().mkpath(cWorkingDir() + qsl("tdata/temp")); - return true; -} - -HRESULT SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml) { - ComPtr inputText; - - HRESULT hr = xml->CreateTextNode(inputString, &inputText); - if (!SUCCEEDED(hr)) return hr; - ComPtr inputTextNode; - - hr = inputText.As(&inputTextNode); - if (!SUCCEEDED(hr)) return hr; - - ComPtr pAppendedChild; - return node->AppendChild(inputTextNode.Get(), &pAppendedChild); -} - -HRESULT SetAudioSilent(_In_ IXmlDocument *toastXml) { - ComPtr nodeList; - HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList); - if (!SUCCEEDED(hr)) return hr; - - ComPtr audioNode; - hr = nodeList->Item(0, &audioNode); - if (!SUCCEEDED(hr)) return hr; - - if (audioNode) { - ComPtr audioElement; - hr = audioNode.As(&audioElement); - if (!SUCCEEDED(hr)) return hr; - - hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get()); - if (!SUCCEEDED(hr)) return hr; - } else { - ComPtr audioElement; - hr = toastXml->CreateElement(StringReferenceWrapper(L"audio").Get(), &audioElement); - if (!SUCCEEDED(hr)) return hr; - - hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get()); - if (!SUCCEEDED(hr)) return hr; - - ComPtr audioNode; - hr = audioElement.As(&audioNode); - if (!SUCCEEDED(hr)) return hr; - - ComPtr nodeList; - hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &nodeList); - if (!SUCCEEDED(hr)) return hr; - - ComPtr toastNode; - hr = nodeList->Item(0, &toastNode); - if (!SUCCEEDED(hr)) return hr; - - ComPtr appendedNode; - hr = toastNode->AppendChild(audioNode.Get(), &appendedNode); - } - return hr; -} - -HRESULT SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml) { - wchar_t imageSrc[MAX_PATH] = L"file:///"; - HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath); - if (!SUCCEEDED(hr)) return hr; - - ComPtr nodeList; - hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); - if (!SUCCEEDED(hr)) return hr; - - ComPtr imageNode; - hr = nodeList->Item(0, &imageNode); - if (!SUCCEEDED(hr)) return hr; - - ComPtr attributes; - hr = imageNode->get_Attributes(&attributes); - if (!SUCCEEDED(hr)) return hr; - - ComPtr srcAttribute; - hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute); - if (!SUCCEEDED(hr)) return hr; - - return SetNodeValueString(StringReferenceWrapper(imageSrc).Get(), srcAttribute.Get(), toastXml); -} - -typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastActivatedEventHandler; -typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastDismissedEventHandler; -typedef ABI::Windows::Foundation::ITypedEventHandler DesktopToastFailedEventHandler; - -class ToastEventHandler : public Implements { -public: - - ToastEventHandler::ToastEventHandler(const PeerId &peer, MsgId msg) : _ref(1), _peerId(peer), _msgId(msg) { - } - ~ToastEventHandler() { - } - - // DesktopToastActivatedEventHandler - IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IInspectable* args) { - auto i = _notifications.find(_peerId); - if (i != _notifications.cend()) { - i.value().remove(_msgId); - if (i.value().isEmpty()) { - _notifications.erase(i); - } - } - if (App::wnd()) { - History *history = App::history(_peerId); - - App::wnd()->showFromTray(); - if (App::passcoded()) { - App::wnd()->setInnerFocus(); - App::wnd()->notifyClear(); - } else { - bool tomsg = !history->peer->isUser() && (_msgId > 0); - if (tomsg) { - HistoryItem *item = App::histItemById(peerToChannel(_peerId), _msgId); - if (!item || !item->mentionsMe()) { - tomsg = false; - } - } - Ui::showPeerHistory(history, tomsg ? _msgId : ShowAtUnreadMsgId); - App::wnd()->notifyClear(history); - } - SetForegroundWindow(App::wnd()->psHwnd()); - } - return S_OK; - } - - // DesktopToastDismissedEventHandler - IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastDismissedEventArgs *e) { - ToastDismissalReason tdr; - if (SUCCEEDED(e->get_Reason(&tdr))) { - switch (tdr) { - case ToastDismissalReason_ApplicationHidden: - break; - case ToastDismissalReason_UserCanceled: - case ToastDismissalReason_TimedOut: - default: - auto i = _notifications.find(_peerId); - if (i != _notifications.cend()) { - i.value().remove(_msgId); - if (i.value().isEmpty()) { - _notifications.erase(i); - } - } - break; - } - } - return S_OK; - } - - // DesktopToastFailedEventHandler - IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastFailedEventArgs *e) { - auto i = _notifications.find(_peerId); - if (i != _notifications.cend()) { - i.value().remove(_msgId); - if (i.value().isEmpty()) { - _notifications.erase(i); - } - } - return S_OK; - } - - // IUnknown - IFACEMETHODIMP_(ULONG) AddRef() { - return InterlockedIncrement(&_ref); - } - - IFACEMETHODIMP_(ULONG) Release() { - ULONG l = InterlockedDecrement(&_ref); - if (l == 0) delete this; - return l; - } - - IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) { - if (IsEqualIID(riid, IID_IUnknown)) - *ppv = static_cast(static_cast(this)); - else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) - *ppv = static_cast(this); - else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) - *ppv = static_cast(this); - else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler))) - *ppv = static_cast(this); - else *ppv = nullptr; - - if (*ppv) { - reinterpret_cast(*ppv)->AddRef(); - return S_OK; - } - - return E_NOINTERFACE; - } - -private: - - ULONG _ref; - PeerId _peerId; - MsgId _msgId; -}; - -QString getImage(const StorageKey &key, PeerData *peer) { - uint64 ms = getms(true); - auto i = _images.find(key); - if (i != _images.cend()) { - if (i->until) { - i->until = ms + kNotifyDeletePhotoAfterMs; - if (auto manager = ManagerInstance.data()) { - manager->clearNotifyPhotosInMs(-kNotifyDeletePhotoAfterMs); - } - } - } else { - Image v; - if (key.first) { - v.until = ms + kNotifyDeletePhotoAfterMs; - if (auto manager = ManagerInstance.data()) { - manager->clearNotifyPhotosInMs(-kNotifyDeletePhotoAfterMs); - } - } else { - v.until = 0; - } - v.path = cWorkingDir() + qsl("tdata/temp/") + QString::number(rand_value(), 16) + qsl(".png"); - if (key.first || key.second) { - peer->saveUserpic(v.path, st::notifyMacPhotoSize); - } else { - App::wnd()->iconLarge().save(v.path, "PNG"); - } - i = _images.insert(key, v); - _imageSavedFlag = true; - } - return i->path; -} - -} // namespace - -void start() { - if (init()) { - ManagerInstance.makeIfNull(); - } -} - -Manager *manager() { - return ManagerInstance.data(); -} - -void finish() { - ManagerInstance.clear(); -} - -uint64 clearImages(uint64 ms) { - uint64 result = 0; - for (auto i = _images.begin(); i != _images.end();) { - if (!i->until) { - ++i; - continue; - } - if (i->until <= ms) { - QFile(i->path).remove(); - i = _images.erase(i); - } else { - if (!result) { - result = i->until; - } else { - accumulate_min(result, i->until); - } - ++i; - } - } - return result; -} - -class Manager::Impl { -public: - bool showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton); - void clearAll(); - void clearFromHistory(History *history); - - ~Impl(); - -private: - QTimer _clearNotifyPhotosTimer; - friend class Manager; - -}; - -Manager::Impl::~Impl() { - _notifications.clear(); - if (_notificationManager) _notificationManager.Reset(); - if (_notifier) _notifier.Reset(); - if (_notificationFactory) _notificationFactory.Reset(); - - if (_imageSavedFlag) { - psDeleteDir(cWorkingDir() + qsl("tdata/temp")); - } -} - -void Manager::Impl::clearAll() { - if (!_notifier) return; - - auto temp = createAndSwap(_notifications); - for_const (auto ¬ifications, temp) { - for_const (auto ¬ification, notifications) { - _notifier->Hide(notification.p.Get()); - } - } -} - -void Manager::Impl::clearFromHistory(History *history) { - if (!_notifier) return; - - auto i = _notifications.find(history->peer->id); - if (i != _notifications.cend()) { - auto temp = createAndSwap(i.value()); - _notifications.erase(i); - - for (auto j = temp.cbegin(), e = temp.cend(); j != e; ++j) { - _notifier->Hide(j->p.Get()); - } - } -} - -bool Manager::Impl::showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) { - if (!_notificationManager || !_notifier || !_notificationFactory) return false; - - ComPtr toastXml; - bool withSubtitle = !subtitle.isEmpty(); - - HRESULT hr = _notificationManager->GetTemplateContent(withSubtitle ? ToastTemplateType_ToastImageAndText04 : ToastTemplateType_ToastImageAndText02, &toastXml); - if (!SUCCEEDED(hr)) return false; - - hr = SetAudioSilent(toastXml.Get()); - if (!SUCCEEDED(hr)) return false; - - StorageKey key; - QString imagePath; - if (showUserpic) { - key = peer->userpicUniqueKey(); - } else { - key = StorageKey(0, 0); - } - QString image = getImage(key, peer); - std::wstring wimage = QDir::toNativeSeparators(image).toStdWString(); - - hr = SetImageSrc(wimage.c_str(), toastXml.Get()); - if (!SUCCEEDED(hr)) return false; - - ComPtr nodeList; - hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); - if (!SUCCEEDED(hr)) return false; - - UINT32 nodeListLength; - hr = nodeList->get_Length(&nodeListLength); - if (!SUCCEEDED(hr)) return false; - - if (nodeListLength < (withSubtitle ? 3U : 2U)) return false; - - { - ComPtr textNode; - hr = nodeList->Item(0, &textNode); - if (!SUCCEEDED(hr)) return false; - - std::wstring wtitle = title.toStdWString(); - hr = SetNodeValueString(StringReferenceWrapper(wtitle.data(), wtitle.size()).Get(), textNode.Get(), toastXml.Get()); - if (!SUCCEEDED(hr)) return false; - } - if (withSubtitle) { - ComPtr textNode; - hr = nodeList->Item(1, &textNode); - if (!SUCCEEDED(hr)) return false; - - std::wstring wsubtitle = subtitle.toStdWString(); - hr = SetNodeValueString(StringReferenceWrapper(wsubtitle.data(), wsubtitle.size()).Get(), textNode.Get(), toastXml.Get()); - if (!SUCCEEDED(hr)) return false; - } - { - ComPtr textNode; - hr = nodeList->Item(withSubtitle ? 2 : 1, &textNode); - if (!SUCCEEDED(hr)) return false; - - std::wstring wmsg = msg.toStdWString(); - hr = SetNodeValueString(StringReferenceWrapper(wmsg.data(), wmsg.size()).Get(), textNode.Get(), toastXml.Get()); - if (!SUCCEEDED(hr)) return false; - } - - ComPtr toast; - hr = _notificationFactory->CreateToastNotification(toastXml.Get(), &toast); - if (!SUCCEEDED(hr)) return false; - - EventRegistrationToken activatedToken, dismissedToken, failedToken; - ComPtr eventHandler(new ToastEventHandler(peer->id, msgId)); - - hr = toast->add_Activated(eventHandler.Get(), &activatedToken); - if (!SUCCEEDED(hr)) return false; - - hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); - if (!SUCCEEDED(hr)) return false; - - hr = toast->add_Failed(eventHandler.Get(), &failedToken); - if (!SUCCEEDED(hr)) return false; - - auto i = _notifications.find(peer->id); - if (i != _notifications.cend()) { - auto j = i->find(msgId); - if (j != i->cend()) { - ComPtr notify = j->p; - i->erase(j); - _notifier->Hide(notify.Get()); - i = _notifications.find(peer->id); - } - } - if (i == _notifications.cend()) { - i = _notifications.insert(peer->id, QMap()); - } - hr = _notifier->Show(toast.Get()); - if (!SUCCEEDED(hr)) { - i = _notifications.find(peer->id); - if (i != _notifications.cend() && i->isEmpty()) _notifications.erase(i); - return false; - } - _notifications[peer->id].insert(msgId, toast); - - return true; -} - -Manager::Manager() : _impl(std_::make_unique()) { - connect(&_impl->_clearNotifyPhotosTimer, SIGNAL(timeout()), this, SLOT(onClearNotifyPhotos())); -} - -void Manager::clearNotifyPhotosInMs(int ms) { - if (ms < 0) { - ms = -ms; - if (_impl->_clearNotifyPhotosTimer.isActive() && _impl->_clearNotifyPhotosTimer.remainingTime() <= ms) { - return; - } - } - _impl->_clearNotifyPhotosTimer.start(ms); -} - -void Manager::onClearNotifyPhotos() { - auto ms = getms(true); - auto minuntil = Toasts::clearImages(ms); - if (minuntil) { - clearNotifyPhotosInMs(int32(minuntil - ms)); - } -} - -Manager::~Manager() = default; - -void Manager::doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) { - _impl->showNotification(peer, msgId, title, subtitle, showUserpic, msg, showReplyButton); -} - -void Manager::doClearAllFast() { - _impl->clearAll(); -} - -void Manager::doClearFromHistory(History *history) { - _impl->clearFromHistory(history); -} - -} // namespace Toasts -} // namespace Platform diff --git a/Telegram/SourceFiles/platform/win/windows_toasts.h b/Telegram/SourceFiles/platform/win/windows_toasts.h deleted file mode 100644 index 517f4aedc..000000000 --- a/Telegram/SourceFiles/platform/win/windows_toasts.h +++ /dev/null @@ -1,61 +0,0 @@ -/* -This file is part of Telegram Desktop, -the official desktop version of Telegram messaging app, see https://telegram.org - -Telegram Desktop 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. - -It 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. - -In addition, as a special exception, the copyright holders give permission -to link the code of portions of this program with the OpenSSL library. - -Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE -Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org -*/ -#pragma once - -#include "window/notifications_manager.h" - -namespace Platform { -namespace Toasts { - -class Manager; - -void start(); -Manager *manager(); -void finish(); - -// Returns the next ms when clearImages() should be called. -uint64 clearImages(uint64 ms); - -class Manager : public QObject, public Window::Notifications::NativeManager { - Q_OBJECT - -public: - Manager(); - - void clearNotifyPhotosInMs(int ms); - - ~Manager(); - -private slots: - void onClearNotifyPhotos(); - -private: - void doShowNativeNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, bool showUserpic, const QString &msg, bool showReplyButton) override; - void doClearAllFast() override; - void doClearFromHistory(History *history) override; - - class Impl; - std_::unique_ptr _impl; - -}; - -} // namespace Toasts -} // namespace Platform diff --git a/Telegram/SourceFiles/pspecific_win.cpp b/Telegram/SourceFiles/pspecific_win.cpp index 61fcde541..72ade2e76 100644 --- a/Telegram/SourceFiles/pspecific_win.cpp +++ b/Telegram/SourceFiles/pspecific_win.cpp @@ -22,7 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "pspecific.h" #include "platform/win/main_window_win.h" -#include "platform/win/windows_toasts.h" +#include "platform/win/notifications_manager_win.h" #include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_dlls.h" #include "platform/win/windows_event_filter.h" diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index ac05db88e..e3249ab52 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -24,6 +24,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org #include "platform/platform_notifications_manager.h" #include "window/notifications_manager_default.h" #include "lang.h" +#include "mainwindow.h" namespace Window { namespace Notifications { @@ -45,6 +46,30 @@ void finish() { Default::finish(); } +void Manager::notificationActivated(PeerId peerId, MsgId msgId) { + onBeforeNotificationActivated(peerId, msgId); + if (auto window = App::wnd()) { + auto history = App::history(peerId); + + window->showFromTray(); + if (App::passcoded()) { + window->setInnerFocus(); + window->notifyClear(); + } else { + auto tomsg = !history->peer->isUser() && (msgId > 0); + if (tomsg) { + auto item = App::histItemById(peerToChannel(peerId), msgId); + if (!item || !item->mentionsMe()) { + tomsg = false; + } + } + Ui::showPeerHistory(history, tomsg ? msgId : ShowAtUnreadMsgId); + window->notifyClear(history); + } + } + onAfterNotificationActivated(peerId, msgId); +} + void NativeManager::doShowNotification(HistoryItem *item, int forwardedCount) { auto hideEverything = (App::passcoded() || Global::ScreenIsLocked()); auto hideName = hideEverything || (Global::NotifyView() > dbinvShowName); diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h index 170ebd2b0..02a2b15b7 100644 --- a/Telegram/SourceFiles/window/notifications_manager.h +++ b/Telegram/SourceFiles/window/notifications_manager.h @@ -49,6 +49,8 @@ public: void clearFromHistory(History *history) { doClearFromHistory(history); } + void notificationActivated(PeerId peerId, MsgId msgId); + virtual ~Manager() = default; protected: @@ -58,6 +60,10 @@ protected: virtual void doClearAllFast() = 0; virtual void doClearFromItem(HistoryItem *item) = 0; virtual void doClearFromHistory(History *history) = 0; + virtual void onBeforeNotificationActivated(PeerId peerId, MsgId msgId) { + } + virtual void onAfterNotificationActivated(PeerId peerId, MsgId msgId) { + } }; diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 3c0c75ba1..5d331b4b7 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -374,20 +374,15 @@ void Widget::startHiding() { void Widget::mousePressEvent(QMouseEvent *e) { if (!_history) return; - auto peerId = _history->peer->id; - auto msgId = (!_history->peer->isUser() && _item && _item->mentionsMe() && _item->id > 0) ? _item->id : ShowAtUnreadMsgId; - if (e->button() == Qt::RightButton) { unlinkHistoryAndNotify(); } else { - App::wnd()->showFromTray(); - if (App::passcoded()) { - App::wnd()->setInnerFocus(); - App::wnd()->notifyClear(); - } else { - Ui::showPeerHistory(peerId, msgId); - } e->ignore(); + if (auto manager = ManagerInstance.data()) { + auto peerId = _history->peer->id; + auto msgId = _item ? _item->id : ShowAtUnreadMsgId; + manager->notificationActivated(peerId, msgId); + } } } diff --git a/Telegram/gyp/Telegram.gyp b/Telegram/gyp/Telegram.gyp index a897d8894..41df54189 100644 --- a/Telegram/gyp/Telegram.gyp +++ b/Telegram/gyp/Telegram.gyp @@ -342,8 +342,6 @@ '<(src_loc)/platform/win/windows_dlls.h', '<(src_loc)/platform/win/windows_event_filter.cpp', '<(src_loc)/platform/win/windows_event_filter.h', - '<(src_loc)/platform/win/windows_toasts.cpp', - '<(src_loc)/platform/win/windows_toasts.h', '<(src_loc)/platform/platform_file_dialog.h', '<(src_loc)/platform/platform_main_window.h', '<(src_loc)/platform/platform_notifications_manager.h', @@ -555,8 +553,6 @@ '<(src_loc)/platform/win/windows_dlls.h', '<(src_loc)/platform/win/windows_event_filter.cpp', '<(src_loc)/platform/win/windows_event_filter.h', - '<(src_loc)/platform/win/windows_toasts.cpp', - '<(src_loc)/platform/win/windows_toasts.h', ], }], ],