Rewrite Windows native notifications using C++/WinRT.

This commit is contained in:
John Preston 2021-10-05 12:09:15 +04:00
parent 730412fefe
commit 967e86f4ab
3 changed files with 163 additions and 327 deletions

View file

@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/notifications_utilities.h" #include "window/notifications_utilities.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "base/platform/win/base_windows_wrl.h" #include "base/platform/win/base_windows_co_task_mem.h"
#include "base/platform/win/base_windows_winrt.h"
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_app_user_model_id.h"
#include "platform/win/windows_event_filter.h" #include "platform/win/windows_event_filter.h"
@ -25,17 +26,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <Shobjidl.h> #include <Shobjidl.h>
#include <shellapi.h> #include <shellapi.h>
#include <strsafe.h>
#ifndef __MINGW32__ #ifndef __MINGW32__
#include "base/platform/win/wrl/wrl_implements_h.h" #include <winrt/Windows.Foundation.h>
#include <windows.ui.notifications.h> #include <winrt/Windows.Data.Xml.Dom.h>
#include <winrt/Windows.UI.Notifications.h>
#include <winrt/Windows.UI.Notifications.Management.h>
HICON qt_pixmapToWinHICON(const QPixmap &); HICON qt_pixmapToWinHICON(const QPixmap &);
using namespace Microsoft::WRL; using namespace winrt::Windows::UI::Notifications;
using namespace ABI::Windows::UI::Notifications; using namespace winrt::Windows::Data::Xml::Dom;
using namespace ABI::Windows::Data::Xml::Dom; using namespace winrt::Windows::Foundation;
using namespace Windows::Foundation; using winrt::com_ptr;
#endif // !__MINGW32__ #endif // !__MINGW32__
namespace Platform { namespace Platform {
@ -44,16 +48,13 @@ namespace Notifications {
#ifndef __MINGW32__ #ifndef __MINGW32__
namespace { namespace {
using base::Platform::GetActivationFactory;
using base::Platform::StringReferenceWrapper;
bool init() { bool init() {
if (!IsWindows8OrGreater()) { if (!IsWindows8OrGreater()) {
return false; return false;
} }
if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr) if ((Dlls::SetCurrentProcessExplicitAppUserModelID == nullptr)
|| (Dlls::PropVariantToString == nullptr) || (Dlls::PropVariantToString == nullptr)
|| !base::Platform::SupportsWRL()) { || !base::WinRT::Supported()) {
return false; return false;
} }
@ -68,189 +69,37 @@ bool init() {
return true; return true;
} }
HRESULT SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml) { // Throws.
ComPtr<IXmlText> inputText; void SetNodeValueString(
const XmlDocument &xml,
HRESULT hr = xml->CreateTextNode(inputString, &inputText); const IXmlNode &node,
if (!SUCCEEDED(hr)) return hr; const std::wstring &text) {
ComPtr<IXmlNode> inputTextNode; node.AppendChild(xml.CreateTextNode(text).as<IXmlNode>());
hr = inputText.As(&inputTextNode);
if (!SUCCEEDED(hr)) return hr;
ComPtr<IXmlNode> pAppendedChild;
return node->AppendChild(inputTextNode.Get(), &pAppendedChild);
} }
HRESULT SetAudioSilent(_In_ IXmlDocument *toastXml) { // Throws.
ComPtr<IXmlNodeList> nodeList; void SetAudioSilent(const XmlDocument &toastXml) {
HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList); const auto nodeList = toastXml.GetElementsByTagName(L"audio");
if (!SUCCEEDED(hr)) return hr; if (const auto audioNode = nodeList.Item(0)) {
audioNode.as<IXmlElement>().SetAttribute(L"silent", L"true");
ComPtr<IXmlNode> audioNode;
hr = nodeList->Item(0, &audioNode);
if (!SUCCEEDED(hr)) return hr;
if (audioNode) {
ComPtr<IXmlElement> 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 { } else {
ComPtr<IXmlElement> audioElement; auto audioElement = toastXml.CreateElement(L"audio");
hr = toastXml->CreateElement(StringReferenceWrapper(L"audio").Get(), &audioElement); audioElement.SetAttribute(L"silent", L"true");
if (!SUCCEEDED(hr)) return hr; auto nodeList = toastXml.GetElementsByTagName(L"toast");
nodeList.Item(0).AppendChild(audioElement.as<IXmlNode>());
hr = audioElement->SetAttribute(StringReferenceWrapper(L"silent").Get(), StringReferenceWrapper(L"true").Get());
if (!SUCCEEDED(hr)) return hr;
ComPtr<IXmlNode> audioNode;
hr = audioElement.As(&audioNode);
if (!SUCCEEDED(hr)) return hr;
ComPtr<IXmlNodeList> nodeList;
hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"toast").Get(), &nodeList);
if (!SUCCEEDED(hr)) return hr;
ComPtr<IXmlNode> toastNode;
hr = nodeList->Item(0, &toastNode);
if (!SUCCEEDED(hr)) return hr;
ComPtr<IXmlNode> appendedNode;
hr = toastNode->AppendChild(audioNode.Get(), &appendedNode);
} }
return hr;
} }
HRESULT SetImageSrc(_In_z_ const wchar_t *imagePath, _In_ IXmlDocument *toastXml) { // Throws.
wchar_t imageSrc[MAX_PATH] = L"file:///"; void SetImageSrc(const XmlDocument &toastXml, const std::wstring &path) {
HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath); const auto nodeList = toastXml.GetElementsByTagName(L"image");
if (!SUCCEEDED(hr)) return hr; const auto attributes = nodeList.Item(0).Attributes();
return SetNodeValueString(
ComPtr<IXmlNodeList> nodeList; toastXml,
hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList); attributes.GetNamedItem(L"src"),
if (!SUCCEEDED(hr)) return hr; L"file:///" + path);
ComPtr<IXmlNode> imageNode;
hr = nodeList->Item(0, &imageNode);
if (!SUCCEEDED(hr)) return hr;
ComPtr<IXmlNamedNodeMap> attributes;
hr = imageNode->get_Attributes(&attributes);
if (!SUCCEEDED(hr)) return hr;
ComPtr<IXmlNode> 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<ToastNotification*, ::IInspectable *> DesktopToastActivatedEventHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification*, ToastDismissedEventArgs*> DesktopToastDismissedEventHandler;
typedef ABI::Windows::Foundation::ITypedEventHandler<ToastNotification*, ToastFailedEventArgs*> DesktopToastFailedEventHandler;
class ToastEventHandler final : public Implements<
DesktopToastActivatedEventHandler,
DesktopToastDismissedEventHandler,
DesktopToastFailedEventHandler> {
public:
using NotificationId = Manager::NotificationId;
// We keep a weak pointer to a member field of native notifications manager.
ToastEventHandler(
const std::shared_ptr<Manager*> &guarded,
NotificationId id)
: _id(id)
, _weak(guarded) {
}
void performOnMainQueue(FnMut<void(Manager *manager)> task) {
const auto weak = _weak;
crl::on_main(weak, [=, task = std::move(task)]() mutable {
task(*weak.lock());
});
}
// DesktopToastActivatedEventHandler
IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IInspectable* args) {
const auto my = _id;
performOnMainQueue([my](Manager *manager) {
manager->notificationActivated(my);
});
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:
const auto my = _id;
performOnMainQueue([my](Manager *manager) {
manager->clearNotification(my);
});
break;
}
}
return S_OK;
}
// DesktopToastFailedEventHandler
IFACEMETHODIMP Invoke(_In_ IToastNotification *sender, _In_ IToastFailedEventArgs *e) {
const auto my = _id;
performOnMainQueue([my](Manager *manager) {
manager->clearNotification(my);
});
return S_OK;
}
// IUnknown
IFACEMETHODIMP_(ULONG) AddRef() {
return InterlockedIncrement(&_refCount);
}
IFACEMETHODIMP_(ULONG) Release() {
auto refCount = InterlockedDecrement(&_refCount);
if (refCount == 0) {
delete this;
}
return refCount;
}
IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) {
if (IsEqualIID(riid, IID_IUnknown))
*ppv = static_cast<IUnknown*>(static_cast<DesktopToastActivatedEventHandler*>(this));
else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler)))
*ppv = static_cast<DesktopToastActivatedEventHandler*>(this);
else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler)))
*ppv = static_cast<DesktopToastDismissedEventHandler*>(this);
else if (IsEqualIID(riid, __uuidof(DesktopToastFailedEventHandler)))
*ppv = static_cast<DesktopToastFailedEventHandler*>(this);
else *ppv = nullptr;
if (*ppv) {
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
private:
ULONG _refCount = 0;
NotificationId _id;
std::weak_ptr<Manager*> _weak;
};
auto Checked = false; auto Checked = false;
auto InitSucceeded = false; auto InitSucceeded = false;
@ -311,25 +160,19 @@ bool FocusAssistBlocks = false;
// Thanks https://www.withinrafael.com/2019/09/19/determine-if-your-app-is-in-a-focus-assist-profiles-priority-list/ // Thanks https://www.withinrafael.com/2019/09/19/determine-if-your-app-is-in-a-focus-assist-profiles-priority-list/
void QueryFocusAssist() { void QueryFocusAssist() {
ComPtr<IQuietHoursSettings> quietHoursSettings; const auto quietHoursSettings = base::WinRT::TryCreateInstance<
auto hr = CoCreateInstance( IQuietHoursSettings
CLSID_QuietHoursSettings, >(CLSID_QuietHoursSettings, CLSCTX_LOCAL_SERVER);
nullptr, if (!quietHoursSettings) {
CLSCTX_LOCAL_SERVER,
IID_PPV_ARGS(&quietHoursSettings));
if (!SUCCEEDED(hr) || !quietHoursSettings) {
return; return;
} }
auto profileId = LPWSTR{}; auto profileId = base::CoTaskMemString();
const auto guardProfileId = gsl::finally([&] { auto hr = quietHoursSettings->get_UserSelectedProfile(profileId.put());
if (profileId) CoTaskMemFree(profileId); if (FAILED(hr) || !profileId) {
});
hr = quietHoursSettings->get_UserSelectedProfile(&profileId);
if (!SUCCEEDED(hr) || !profileId) {
return; return;
} }
const auto profileName = QString::fromWCharArray(profileId); const auto profileName = QString::fromWCharArray(profileId.data());
if (profileName.endsWith(".alarmsonly", Qt::CaseInsensitive)) { if (profileName.endsWith(".alarmsonly", Qt::CaseInsensitive)) {
if (!FocusAssistBlocks) { if (!FocusAssistBlocks) {
LOG(("Focus Assist: Alarms Only.")); LOG(("Focus Assist: Alarms Only."));
@ -359,33 +202,27 @@ void QueryFocusAssist() {
} }
}); });
ComPtr<IQuietHoursProfile> profile; com_ptr<IQuietHoursProfile> profile;
hr = quietHoursSettings->GetProfile(profileId, &profile); hr = quietHoursSettings->GetProfile(profileId.data(), profile.put());
if (!SUCCEEDED(hr) || !profile) { if (FAILED(hr) || !profile) {
return; return;
} }
UINT32 count = 0; auto apps = base::CoTaskMemStringArray();
auto apps = (LPWSTR*)nullptr; hr = profile->GetAllowedApps(apps.put_size(), apps.put());
const auto guardApps = gsl::finally([&] { if (FAILED(hr) || !apps) {
if (apps) CoTaskMemFree(apps);
});
hr = profile->GetAllowedApps(&count, &apps);
if (!SUCCEEDED(hr) || !apps) {
return; return;
} }
for (UINT32 i = 0; i < count; i++) { for (const auto &app : apps) {
auto app = apps[i]; if (app && app.data() == appUserModelId) {
const auto guardApp = gsl::finally([&] {
if (app) CoTaskMemFree(app);
});
if (app == appUserModelId) {
blocked = false; blocked = false;
break;
} }
} }
} }
QUERY_USER_NOTIFICATION_STATE UserNotificationState = QUNS_ACCEPTS_NOTIFICATIONS; QUERY_USER_NOTIFICATION_STATE UserNotificationState
= QUNS_ACCEPTS_NOTIFICATIONS;
void QueryUserNotificationState() { void QueryUserNotificationState() {
if (Dlls::SHQueryUserNotificationState != nullptr) { if (Dlls::SHQueryUserNotificationState != nullptr) {
@ -494,23 +331,24 @@ public:
~Private(); ~Private();
private: private:
bool showNotificationInTryCatch(
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton);
Window::Notifications::CachedUserpics _cachedUserpics; Window::Notifications::CachedUserpics _cachedUserpics;
std::shared_ptr<Manager*> _guarded; std::shared_ptr<Manager*> _guarded;
ToastNotifier _notifier = nullptr;
ComPtr<IToastNotificationManagerStatics> _notificationManager; base::flat_map<
ComPtr<IToastNotifier> _notifier; FullPeer,
ComPtr<IToastNotificationFactory> _notificationFactory; base::flat_map<MsgId, ToastNotification>> _notifications;
struct NotificationPtr {
NotificationPtr() {
}
NotificationPtr(const ComPtr<IToastNotification> &ptr) : p(ptr) {
}
ComPtr<IToastNotification> p;
};
base::flat_map<FullPeer, base::flat_map<MsgId, NotificationPtr>> _notifications;
}; };
@ -520,28 +358,17 @@ Manager::Private::Private(Manager *instance, Type type)
} }
bool Manager::Private::init() { bool Manager::Private::init() {
if (!SUCCEEDED(GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &_notificationManager))) { return base::WinRT::Try([&] {
return false; _notifier = ToastNotificationManager::CreateToastNotifier(
} AppUserModelId::getId());
});
auto appUserModelId = AppUserModelId::getId();
if (!SUCCEEDED(_notificationManager->CreateToastNotifierWithId(StringReferenceWrapper(appUserModelId, wcslen(appUserModelId)).Get(), &_notifier))) {
return false;
}
if (!SUCCEEDED(GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &_notificationFactory))) {
return false;
}
return true;
} }
Manager::Private::~Private() { Manager::Private::~Private() {
clearAll(); clearAll();
_notifications.clear(); _notifications.clear();
if (_notificationManager) _notificationManager.Reset(); _notifier = nullptr;
if (_notifier) _notifier.Reset();
if (_notificationFactory) _notificationFactory.Reset();
} }
void Manager::Private::clearAll() { void Manager::Private::clearAll() {
@ -552,7 +379,7 @@ void Manager::Private::clearAll() {
auto temp = base::take(_notifications); auto temp = base::take(_notifications);
for (const auto &[key, notifications] : base::take(_notifications)) { for (const auto &[key, notifications] : base::take(_notifications)) {
for (const auto &[msgId, notification] : notifications) { for (const auto &[msgId, notification] : notifications) {
_notifier->Hide(notification.p.Get()); _notifier.Hide(notification);
} }
} }
} }
@ -571,7 +398,7 @@ void Manager::Private::clearFromHistory(not_null<History*> history) {
_notifications.erase(i); _notifications.erase(i);
for (const auto &[msgId, notification] : temp) { for (const auto &[msgId, notification] : temp) {
_notifier->Hide(notification.p.Get()); _notifier.Hide(notification);
} }
} }
} }
@ -591,7 +418,7 @@ void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
_notifications.erase(i); _notifications.erase(i);
for (const auto &[msgId, notification] : temp) { for (const auto &[msgId, notification] : temp) {
_notifier->Hide(notification.p.Get()); _notifier.Hide(notification);
} }
} }
} }
@ -625,22 +452,38 @@ bool Manager::Private::showNotification(
const QString &msg, const QString &msg,
bool hideNameAndPhoto, bool hideNameAndPhoto,
bool hideReplyButton) { bool hideReplyButton) {
if (!_notificationManager || !_notifier || !_notificationFactory) { if (!_notifier) {
return false; return false;
} }
ComPtr<IXmlDocument> toastXml; return base::WinRT::Try([&] {
bool withSubtitle = !subtitle.isEmpty(); return showNotificationInTryCatch(
peer,
userpicView,
msgId,
title,
subtitle,
msg,
hideNameAndPhoto,
hideReplyButton);
}).value_or(false);
}
HRESULT hr = _notificationManager->GetTemplateContent( bool Manager::Private::showNotificationInTryCatch(
not_null<PeerData*> peer,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
bool hideNameAndPhoto,
bool hideReplyButton) {
const auto withSubtitle = !subtitle.isEmpty();
const auto toastXml = ToastNotificationManager::GetTemplateContent(
(withSubtitle (withSubtitle
? ToastTemplateType_ToastImageAndText04 ? ToastTemplateType::ToastImageAndText04
: ToastTemplateType_ToastImageAndText02), : ToastTemplateType::ToastImageAndText02));
&toastXml); SetAudioSilent(toastXml);
if (!SUCCEEDED(hr)) return false;
hr = SetAudioSilent(toastXml.Get());
if (!SUCCEEDED(hr)) return false;
const auto userpicKey = hideNameAndPhoto const auto userpicKey = hideNameAndPhoto
? InMemoryKey() ? InMemoryKey()
@ -648,50 +491,31 @@ bool Manager::Private::showNotification(
const auto userpicPath = _cachedUserpics.get(userpicKey, peer, userpicView); const auto userpicPath = _cachedUserpics.get(userpicKey, peer, userpicView);
const auto userpicPathWide = QDir::toNativeSeparators(userpicPath).toStdWString(); const auto userpicPathWide = QDir::toNativeSeparators(userpicPath).toStdWString();
hr = SetImageSrc(userpicPathWide.c_str(), toastXml.Get()); SetImageSrc(toastXml, userpicPathWide);
if (!SUCCEEDED(hr)) return false;
ComPtr<IXmlNodeList> nodeList; const auto nodeList = toastXml.GetElementsByTagName(L"text");
hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList); if (nodeList.Length() < (withSubtitle ? 3U : 2U)) {
if (!SUCCEEDED(hr)) return false; return false;
UINT32 nodeListLength;
hr = nodeList->get_Length(&nodeListLength);
if (!SUCCEEDED(hr)) return false;
if (nodeListLength < (withSubtitle ? 3U : 2U)) return false;
{
ComPtr<IXmlNode> 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;
} }
SetNodeValueString(toastXml, nodeList.Item(0), title.toStdWString());
if (withSubtitle) { if (withSubtitle) {
ComPtr<IXmlNode> textNode; SetNodeValueString(
hr = nodeList->Item(1, &textNode); toastXml,
if (!SUCCEEDED(hr)) return false; nodeList.Item(1),
subtitle.toStdWString());
std::wstring wsubtitle = subtitle.toStdWString();
hr = SetNodeValueString(StringReferenceWrapper(wsubtitle.data(), wsubtitle.size()).Get(), textNode.Get(), toastXml.Get());
if (!SUCCEEDED(hr)) return false;
} }
{ SetNodeValueString(
ComPtr<IXmlNode> textNode; toastXml,
hr = nodeList->Item(withSubtitle ? 2 : 1, &textNode); nodeList.Item(withSubtitle ? 2 : 1),
if (!SUCCEEDED(hr)) return false; msg.toStdWString());
std::wstring wmsg = msg.toStdWString(); const auto weak = std::weak_ptr(_guarded);
hr = SetNodeValueString(StringReferenceWrapper(wmsg.data(), wmsg.size()).Get(), textNode.Get(), toastXml.Get()); const auto performOnMainQueue = [=](FnMut<void(Manager *manager)> task) {
if (!SUCCEEDED(hr)) return false; crl::on_main(weak, [=, task = std::move(task)]() mutable {
} task(*weak.lock());
});
ComPtr<IToastNotification> toast; };
hr = _notificationFactory->CreateToastNotification(toastXml.Get(), &toast);
if (!SUCCEEDED(hr)) return false;
const auto key = FullPeer{ const auto key = FullPeer{
.sessionId = peer->session().uniqueId(), .sessionId = peer->session().uniqueId(),
@ -701,38 +525,55 @@ bool Manager::Private::showNotification(
.full = key, .full = key,
.msgId = msgId .msgId = msgId
}; };
auto toast = ToastNotification(toastXml);
EventRegistrationToken activatedToken, dismissedToken, failedToken; const auto token1 = toast.Activated([=](
ComPtr<ToastEventHandler> eventHandler(new ToastEventHandler( const ToastNotification &sender,
_guarded, const winrt::Windows::Foundation::IInspectable &args) {
notificationId)); performOnMainQueue([notificationId](Manager *manager) {
manager->notificationActivated(notificationId);
hr = toast->add_Activated(eventHandler.Get(), &activatedToken); });
if (!SUCCEEDED(hr)) return false; });
const auto token2 = toast.Dismissed([=](
hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); const ToastNotification &sender,
if (!SUCCEEDED(hr)) return false; const ToastDismissedEventArgs &args) {
base::WinRT::Try([&] {
hr = toast->add_Failed(eventHandler.Get(), &failedToken); switch (args.Reason()) {
if (!SUCCEEDED(hr)) return false; case ToastDismissalReason::ApplicationHidden:
case ToastDismissalReason::TimedOut: // Went to Action Center.
break;
case ToastDismissalReason::UserCanceled:
default:
performOnMainQueue([notificationId](Manager *manager) {
manager->clearNotification(notificationId);
});
break;
}
});
});
const auto token3 = toast.Failed([=](
const auto &sender,
const ToastFailedEventArgs &args) {
performOnMainQueue([notificationId](Manager *manager) {
manager->clearNotification(notificationId);
});
});
auto i = _notifications.find(key); auto i = _notifications.find(key);
if (i != _notifications.cend()) { if (i != _notifications.cend()) {
auto j = i->second.find(msgId); auto j = i->second.find(msgId);
if (j != i->second.end()) { if (j != i->second.end()) {
ComPtr<IToastNotification> notify = j->second.p; const auto existing = j->second;
i->second.erase(j); i->second.erase(j);
_notifier->Hide(notify.Get()); _notifier.Hide(existing);
i = _notifications.find(key); i = _notifications.find(key);
} }
} }
if (i == _notifications.cend()) { if (i == _notifications.cend()) {
i = _notifications.emplace( i = _notifications.emplace(
key, key,
base::flat_map<MsgId, NotificationPtr>()).first; base::flat_map<MsgId, ToastNotification>()).first;
} }
hr = _notifier->Show(toast.Get()); if (!base::WinRT::Try([&] { _notifier.Show(toast); })) {
if (!SUCCEEDED(hr)) {
i = _notifications.find(key); i = _notifications.find(key);
if (i != _notifications.cend() && i->second.empty()) { if (i != _notifications.cend() && i->second.empty()) {
_notifications.erase(i); _notifications.erase(i);
@ -740,7 +581,6 @@ bool Manager::Private::showNotification(
return false; return false;
} }
i->second.emplace(msgId, toast); i->second.emplace(msgId, toast);
return true; return true;
} }

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/win/windows_app_user_model_id.h" #include "platform/win/windows_app_user_model_id.h"
#include "platform/win/windows_dlls.h" #include "platform/win/windows_dlls.h"
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "base/platform/win/base_windows_co_task_mem.h"
#include "base/platform/win/base_windows_winrt.h" #include "base/platform/win/base_windows_winrt.h"
#include "base/call_delayed.h" #include "base/call_delayed.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
@ -555,17 +556,12 @@ bool psLaunchMaps(const Data::LocationPoint &point) {
return false; return false;
} }
auto handler = (LPWSTR)nullptr; auto handler = base::CoTaskMemString();
const auto guard = gsl::finally([&] {
if (handler) {
::CoTaskMemFree(handler);
}
});
const auto result = aar->QueryCurrentDefault( const auto result = aar->QueryCurrentDefault(
L"bingmaps", L"bingmaps",
AT_URLPROTOCOL, AT_URLPROTOCOL,
AL_EFFECTIVE, AL_EFFECTIVE,
&handler); handler.put());
if (FAILED(result) || !handler) { if (FAILED(result) || !handler) {
return false; return false;
} }

@ -1 +1 @@
Subproject commit 023cffbb57009009e7d4572adc2bc38867ee5333 Subproject commit fc8b0dcd75453c7a136ee2e6bc17eccba77e5fec