mirror of https://github.com/procxx/kepka.git
Unified code for notification activation events in Windows version.
This commit is contained in:
parent
c2aa8d3c77
commit
7f950d2de2
|
@ -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> _impl;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <Shobjidl.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <roapi.h>
|
||||
#include <wrl\client.h>
|
||||
#include <wrl\implements.h>
|
||||
#include <windows.ui.notifications.h>
|
||||
|
||||
#include <strsafe.h>
|
||||
#include <intsafe.h>
|
||||
|
||||
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<Manager> ManagerInstance;
|
||||
|
||||
ComPtr<IToastNotificationManagerStatics> _notificationManager;
|
||||
ComPtr<IToastNotifier> _notifier;
|
||||
ComPtr<IToastNotificationFactory> _notificationFactory;
|
||||
|
||||
struct NotificationPtr {
|
||||
NotificationPtr() {
|
||||
}
|
||||
NotificationPtr(const ComPtr<IToastNotification> &ptr) : p(ptr) {
|
||||
}
|
||||
ComPtr<IToastNotification> p;
|
||||
};
|
||||
using Notifications = QMap<PeerId, QMap<MsgId, NotificationPtr>>;
|
||||
Notifications _notifications;
|
||||
struct Image {
|
||||
uint64 until;
|
||||
QString path;
|
||||
};
|
||||
using Images = QMap<StorageKey, Image>;
|
||||
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<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
~StringReferenceWrapper() {
|
||||
Dlls::WindowsDeleteString(_hstring);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
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<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t _>
|
||||
StringReferenceWrapper(_In_reads_(_) wchar_t(&stringRef)[_]) throw() {
|
||||
UINT32 length;
|
||||
HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
RaiseException(static_cast<DWORD>(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<class T>
|
||||
_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<typename T>
|
||||
inline HRESULT wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef<T> 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<IXmlText> inputText;
|
||||
|
||||
HRESULT hr = xml->CreateTextNode(inputString, &inputText);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
ComPtr<IXmlNode> inputTextNode;
|
||||
|
||||
hr = inputText.As(&inputTextNode);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
ComPtr<IXmlNode> pAppendedChild;
|
||||
return node->AppendChild(inputTextNode.Get(), &pAppendedChild);
|
||||
}
|
||||
|
||||
HRESULT SetAudioSilent(_In_ IXmlDocument *toastXml) {
|
||||
ComPtr<IXmlNodeList> nodeList;
|
||||
HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
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 {
|
||||
ComPtr<IXmlElement> 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<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) {
|
||||
wchar_t imageSrc[MAX_PATH] = L"file:///";
|
||||
HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
ComPtr<IXmlNodeList> nodeList;
|
||||
hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
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 : public Implements<DesktopToastActivatedEventHandler, DesktopToastDismissedEventHandler, DesktopToastFailedEventHandler> {
|
||||
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<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 _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<uint64>(), 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<IXmlDocument> 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<IXmlNodeList> 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<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;
|
||||
}
|
||||
if (withSubtitle) {
|
||||
ComPtr<IXmlNode> 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<IXmlNode> 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<IToastNotification> toast;
|
||||
hr = _notificationFactory->CreateToastNotification(toastXml.Get(), &toast);
|
||||
if (!SUCCEEDED(hr)) return false;
|
||||
|
||||
EventRegistrationToken activatedToken, dismissedToken, failedToken;
|
||||
ComPtr<ToastEventHandler> 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<IToastNotification> 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<MsgId, NotificationPtr>());
|
||||
}
|
||||
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<Impl>()) {
|
||||
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
|
||||
|
|
|
@ -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> _impl;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Notifications
|
||||
} // namespace Platform
|
||||
|
|
|
@ -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 <Shobjidl.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <roapi.h>
|
||||
#include <wrl\client.h>
|
||||
#include <wrl\implements.h>
|
||||
#include <windows.ui.notifications.h>
|
||||
|
||||
#include <strsafe.h>
|
||||
#include <intsafe.h>
|
||||
|
||||
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<Manager> ManagerInstance;
|
||||
|
||||
ComPtr<IToastNotificationManagerStatics> _notificationManager;
|
||||
ComPtr<IToastNotifier> _notifier;
|
||||
ComPtr<IToastNotificationFactory> _notificationFactory;
|
||||
|
||||
struct NotificationPtr {
|
||||
NotificationPtr() {
|
||||
}
|
||||
NotificationPtr(const ComPtr<IToastNotification> &ptr) : p(ptr) {
|
||||
}
|
||||
ComPtr<IToastNotification> p;
|
||||
};
|
||||
using Notifications = QMap<PeerId, QMap<MsgId, NotificationPtr>>;
|
||||
Notifications _notifications;
|
||||
struct Image {
|
||||
uint64 until;
|
||||
QString path;
|
||||
};
|
||||
using Images = QMap<StorageKey, Image>;
|
||||
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<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
~StringReferenceWrapper() {
|
||||
Dlls::WindowsDeleteString(_hstring);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
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<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t _>
|
||||
StringReferenceWrapper(_In_reads_(_) wchar_t(&stringRef)[_]) throw() {
|
||||
UINT32 length;
|
||||
HRESULT hr = SizeTToUInt32(wcslen(stringRef), &length);
|
||||
if (!SUCCEEDED(hr)) {
|
||||
RaiseException(static_cast<DWORD>(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<class T>
|
||||
_Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) {
|
||||
return Dlls::RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline HRESULT wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef<T> 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<IXmlText> inputText;
|
||||
|
||||
HRESULT hr = xml->CreateTextNode(inputString, &inputText);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
ComPtr<IXmlNode> inputTextNode;
|
||||
|
||||
hr = inputText.As(&inputTextNode);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
ComPtr<IXmlNode> pAppendedChild;
|
||||
return node->AppendChild(inputTextNode.Get(), &pAppendedChild);
|
||||
}
|
||||
|
||||
HRESULT SetAudioSilent(_In_ IXmlDocument *toastXml) {
|
||||
ComPtr<IXmlNodeList> nodeList;
|
||||
HRESULT hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"audio").Get(), &nodeList);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
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 {
|
||||
ComPtr<IXmlElement> 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<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) {
|
||||
wchar_t imageSrc[MAX_PATH] = L"file:///";
|
||||
HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
ComPtr<IXmlNodeList> nodeList;
|
||||
hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList);
|
||||
if (!SUCCEEDED(hr)) return hr;
|
||||
|
||||
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 : public Implements<DesktopToastActivatedEventHandler, DesktopToastDismissedEventHandler, DesktopToastFailedEventHandler> {
|
||||
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<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 _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<uint64>(), 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<IXmlDocument> 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<IXmlNodeList> 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<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;
|
||||
}
|
||||
if (withSubtitle) {
|
||||
ComPtr<IXmlNode> 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<IXmlNode> 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<IToastNotification> toast;
|
||||
hr = _notificationFactory->CreateToastNotification(toastXml.Get(), &toast);
|
||||
if (!SUCCEEDED(hr)) return false;
|
||||
|
||||
EventRegistrationToken activatedToken, dismissedToken, failedToken;
|
||||
ComPtr<ToastEventHandler> 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<IToastNotification> 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<MsgId, NotificationPtr>());
|
||||
}
|
||||
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<Impl>()) {
|
||||
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
|
|
@ -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> _impl;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Toasts
|
||||
} // namespace Platform
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
}],
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue