From 7617b96b60d70351784c0b6b79563ef6bb896f89 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 18 Oct 2022 05:49:20 -0700 Subject: [PATCH] Remove CLSID and self generate shortcut --- src/notification_glue.cpp | 9 +- src/notification_glue.h | 7 +- src/wintoastlib.cpp | 1879 +++++++++++++++++++------------------ src/wintoastlib.h | 40 +- 4 files changed, 971 insertions(+), 964 deletions(-) diff --git a/src/notification_glue.cpp b/src/notification_glue.cpp index 378a1ae..c413ded 100644 --- a/src/notification_glue.cpp +++ b/src/notification_glue.cpp @@ -44,11 +44,12 @@ class WinToastHandler : public IWinToastHandler uint64_t m_id; }; -uint64_t PortmasterToastInitialize(const wchar_t *appName, const wchar_t *aumi) { +uint64_t PortmasterToastInitialize(const wchar_t *appName, const wchar_t *aumi, const wchar_t* originalShortcutPath) { WinToast::instance()->setAppName(appName); - WinToast::instance()->setAppUserModelId(aumi); - WinToast::instance()->setShortcutPolicy(WinToast::SHORTCUT_POLICY_IGNORE); - + WinToast::instance()->setAppUserModelId(L"test.amui"); + WinToast::instance()->setShellLinkToCopy(originalShortcutPath); + WinToast::instance()->setShortcutPolicy(WinToast::SHORTCUT_POLICY_REQUIRE_CREATE); + WinToast::WinToastError error = WinToast::NoError; WinToast::instance()->initialize(&error); return error; diff --git a/src/notification_glue.h b/src/notification_glue.h index aae8299..238b4de 100644 --- a/src/notification_glue.h +++ b/src/notification_glue.h @@ -21,12 +21,13 @@ typedef uint64_t(*callback_func)(uint64_t id, int action); /** * @brief Initialize notifications * - * @par appName = application name, appears in the notification header - * @par aumi = Application User Mode ID. Identifer that should be set in the Portmaster start menu shortcut + * @par appName = application name, appears in the notification header + * @par aumi = Application User Mode ID. Identifier that should be set in the Portmaster start menu shortcut + * @par originalShortcutPath = The original shortcut that will be copied on initialization * @return 1 on success 0 on failure * */ -EXPORT uint64_t PortmasterToastInitialize(const wchar_t* appName, const wchar_t* aumi); +EXPORT uint64_t PortmasterToastInitialize(const wchar_t* appName, const wchar_t* aumi, const wchar_t* originalShortcutPath); /** * @brief check if notifications are initialized diff --git a/src/wintoastlib.cpp b/src/wintoastlib.cpp index c03a34f..66a21b9 100644 --- a/src/wintoastlib.cpp +++ b/src/wintoastlib.cpp @@ -26,15 +26,11 @@ #pragma comment(lib,"shlwapi") #pragma comment(lib,"user32") -#pragma comment(lib,"runtimeobject") - -#define ST_WSTRINGIFY(X) L##X -#define ST_STRINGIFY(X) ST_WSTRINGIFY(X) #ifdef NDEBUG - #define DEBUG_MSG(str) do { } while ( false ) +#define DEBUG_MSG(str) do { } while ( false ) #else - #define DEBUG_MSG(str) do { std::wcout << str << std::endl; } while( false ) +#define DEBUG_MSG(str) do { std::wcout << str << std::endl; } while( false ) #endif #define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\" @@ -42,20 +38,20 @@ #define STATUS_SUCCESS (0x00000000) -// Quickstart: Handling toast activations from Win32 apps in Windows 10 -// https://blogs.msdn.microsoft.com/tiles_and_toasts/2015/10/16/quickstart-handling-toast-activations-from-win32-apps-in-windows-10/ + // Quickstart: Handling toast activations from Win32 apps in Windows 10 + // https://blogs.msdn.microsoft.com/tiles_and_toasts/2015/10/16/quickstart-handling-toast-activations-from-win32-apps-in-windows-10/ using namespace WinToastLib; namespace DllImporter { - // Function load a function from library - template + // Function load a function from library + template HRESULT loadFunctionFromLibrary(HINSTANCE library, LPCSTR name, Function &func) { if (!library) { return E_INVALIDARG; } - func = reinterpret_cast(GetProcAddress(library, name)); - return (func != nullptr) ? S_OK : E_FAIL; - } + func = reinterpret_cast(GetProcAddress(library, name)); + return (func != nullptr) ? S_OK : E_FAIL; + } typedef HRESULT(FAR STDAPICALLTYPE *f_SetCurrentProcessExplicitAppUserModelID)(__in PCWSTR AppID); typedef HRESULT(FAR STDAPICALLTYPE *f_PropVariantToString)(_In_ REFPROPVARIANT propvar, _Out_writes_(cch) PWSTR psz, _In_ UINT cch); @@ -64,313 +60,313 @@ namespace DllImporter { typedef PCWSTR(FAR STDAPICALLTYPE *f_WindowsGetStringRawBuffer)(_In_ HSTRING string, _Out_opt_ UINT32 *length); typedef HRESULT(FAR STDAPICALLTYPE *f_WindowsDeleteString)(_In_opt_ HSTRING string); - static f_SetCurrentProcessExplicitAppUserModelID SetCurrentProcessExplicitAppUserModelID; - static f_PropVariantToString PropVariantToString; - static f_RoGetActivationFactory RoGetActivationFactory; - static f_WindowsCreateStringReference WindowsCreateStringReference; - static f_WindowsGetStringRawBuffer WindowsGetStringRawBuffer; - static f_WindowsDeleteString WindowsDeleteString; - - template - _Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) { - return RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory)); - } - - template - inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef factory) noexcept { - return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf()); - } - - inline HRESULT initialize() { - HINSTANCE LibShell32 = LoadLibraryW(L"SHELL32.DLL"); - HRESULT hr = loadFunctionFromLibrary(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID); - if (SUCCEEDED(hr)) { - HINSTANCE LibPropSys = LoadLibraryW(L"PROPSYS.DLL"); - hr = loadFunctionFromLibrary(LibPropSys, "PropVariantToString", PropVariantToString); - if (SUCCEEDED(hr)) { - HINSTANCE LibComBase = LoadLibraryW(L"COMBASE.DLL"); - const bool succeded = SUCCEEDED(loadFunctionFromLibrary(LibComBase, "RoGetActivationFactory", RoGetActivationFactory)) - && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsCreateStringReference", WindowsCreateStringReference)) - && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsGetStringRawBuffer", WindowsGetStringRawBuffer)) - && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsDeleteString", WindowsDeleteString)); + static f_SetCurrentProcessExplicitAppUserModelID SetCurrentProcessExplicitAppUserModelID; + static f_PropVariantToString PropVariantToString; + static f_RoGetActivationFactory RoGetActivationFactory; + static f_WindowsCreateStringReference WindowsCreateStringReference; + static f_WindowsGetStringRawBuffer WindowsGetStringRawBuffer; + static f_WindowsDeleteString WindowsDeleteString; + + template + _Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) { + return RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory)); + } + + template + inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef factory) noexcept { + return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf()); + } + + inline HRESULT initialize() { + HINSTANCE LibShell32 = LoadLibraryW(L"SHELL32.DLL"); + HRESULT hr = loadFunctionFromLibrary(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID); + if (SUCCEEDED(hr)) { + HINSTANCE LibPropSys = LoadLibraryW(L"PROPSYS.DLL"); + hr = loadFunctionFromLibrary(LibPropSys, "PropVariantToString", PropVariantToString); + if (SUCCEEDED(hr)) { + HINSTANCE LibComBase = LoadLibraryW(L"COMBASE.DLL"); + const bool succeded = SUCCEEDED(loadFunctionFromLibrary(LibComBase, "RoGetActivationFactory", RoGetActivationFactory)) + && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsCreateStringReference", WindowsCreateStringReference)) + && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsGetStringRawBuffer", WindowsGetStringRawBuffer)) + && SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsDeleteString", WindowsDeleteString)); return succeded ? S_OK : E_FAIL; - } - } - return hr; - } + } + } + return hr; + } } class WinToastStringWrapper { public: - WinToastStringWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) noexcept { - HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef, length, &m_header, &m_hstring); - if (!SUCCEEDED(hr)) { - RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); - } - } + WinToastStringWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) noexcept { + HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef, length, &m_header, &m_hstring); + if (!SUCCEEDED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } WinToastStringWrapper(_In_ const std::wstring &stringRef) noexcept { - HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast(stringRef.length()), &m_header, &m_hstring); - if (FAILED(hr)) { - RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); - } - } - - ~WinToastStringWrapper() { - DllImporter::WindowsDeleteString(m_hstring); - } - - inline HSTRING Get() const noexcept { - return m_hstring; - } + HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast(stringRef.length()), &m_header, &m_hstring); + if (FAILED(hr)) { + RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); + } + } + + ~WinToastStringWrapper() { + DllImporter::WindowsDeleteString(m_hstring); + } + + inline HSTRING Get() const noexcept { + return m_hstring; + } private: - HSTRING m_hstring; - HSTRING_HEADER m_header; + HSTRING m_hstring; + HSTRING_HEADER m_header; }; class InternalDateTime : public IReference { public: - static INT64 Now() { - FILETIME now; - GetSystemTimeAsFileTime(&now); - return ((((INT64)now.dwHighDateTime) << 32) | now.dwLowDateTime); - } + static INT64 Now() { + FILETIME now; + GetSystemTimeAsFileTime(&now); + return ((((INT64)now.dwHighDateTime) << 32) | now.dwLowDateTime); + } - InternalDateTime(DateTime dateTime) : m_dateTime(dateTime) {} + InternalDateTime(DateTime dateTime) : m_dateTime(dateTime) {} - InternalDateTime(INT64 millisecondsFromNow) { - m_dateTime.UniversalTime = Now() + millisecondsFromNow * 10000; - } + InternalDateTime(INT64 millisecondsFromNow) { + m_dateTime.UniversalTime = Now() + millisecondsFromNow * 10000; + } - virtual ~InternalDateTime() = default; + virtual ~InternalDateTime() = default; - operator INT64() { - return m_dateTime.UniversalTime; - } + operator INT64() { + return m_dateTime.UniversalTime; + } HRESULT STDMETHODCALLTYPE get_Value(DateTime *dateTime) { - *dateTime = m_dateTime; - return S_OK; - } - - HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject) { - if (!ppvObject) { - return E_POINTER; - } - if (riid == __uuidof(IUnknown) || riid == __uuidof(IReference)) { - *ppvObject = static_cast(static_cast*>(this)); - return S_OK; - } - return E_NOINTERFACE; - } - - ULONG STDMETHODCALLTYPE Release() { - return 1; - } - - ULONG STDMETHODCALLTYPE AddRef() { - return 2; - } - - HRESULT STDMETHODCALLTYPE GetIids(ULONG*, IID**) { - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE GetRuntimeClassName(HSTRING*) { - return E_NOTIMPL; - } - - HRESULT STDMETHODCALLTYPE GetTrustLevel(TrustLevel*) { - return E_NOTIMPL; - } + *dateTime = m_dateTime; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) { + return E_POINTER; + } + if (riid == __uuidof(IUnknown) || riid == __uuidof(IReference)) { + *ppvObject = static_cast(static_cast*>(this)); + return S_OK; + } + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE Release() { + return 1; + } + + ULONG STDMETHODCALLTYPE AddRef() { + return 2; + } + + HRESULT STDMETHODCALLTYPE GetIids(ULONG*, IID**) { + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE GetRuntimeClassName(HSTRING*) { + return E_NOTIMPL; + } + + HRESULT STDMETHODCALLTYPE GetTrustLevel(TrustLevel*) { + return E_NOTIMPL; + } protected: - DateTime m_dateTime; + DateTime m_dateTime; }; namespace Util { typedef LONG NTSTATUS, *PNTSTATUS; - typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); - inline RTL_OSVERSIONINFOW getRealOSVersion() { - HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); - if (hMod) { - RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion"); - if (fxPtr != nullptr) { - RTL_OSVERSIONINFOW rovi = { 0 }; - rovi.dwOSVersionInfoSize = sizeof(rovi); - if (STATUS_SUCCESS == fxPtr(&rovi)) { - return rovi; - } - } - } - RTL_OSVERSIONINFOW rovi = { 0 }; - return rovi; - } - - inline HRESULT defaultExecutablePath(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { - DWORD written = GetModuleFileNameExW(GetCurrentProcess(), nullptr, path, nSize); - DEBUG_MSG("Default executable path: " << path); - return (written > 0) ? S_OK : E_FAIL; - } - - - inline HRESULT defaultShellLinksDirectory(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { - DWORD written = GetEnvironmentVariableW(L"APPDATA", path, nSize); - HRESULT hr = written > 0 ? S_OK : E_INVALIDARG; - if (SUCCEEDED(hr)) { - errno_t result = wcscat_s(path, nSize, DEFAULT_SHELL_LINKS_PATH); - hr = (result == 0) ? S_OK : E_INVALIDARG; - DEBUG_MSG("Default shell link path: " << path); - } - return hr; - } - - inline HRESULT defaultShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { - HRESULT hr = defaultShellLinksDirectory(path, nSize); - if (SUCCEEDED(hr)) { - const std::wstring appLink(appname + DEFAULT_LINK_FORMAT); - errno_t result = wcscat_s(path, nSize, appLink.c_str()); - hr = (result == 0) ? S_OK : E_INVALIDARG; - DEBUG_MSG("Default shell link file path: " << path); - } - return hr; - } + typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); + inline RTL_OSVERSIONINFOW getRealOSVersion() { + HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); + if (hMod) { + RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion"); + if (fxPtr != nullptr) { + RTL_OSVERSIONINFOW rovi = { 0 }; + rovi.dwOSVersionInfoSize = sizeof(rovi); + if (STATUS_SUCCESS == fxPtr(&rovi)) { + return rovi; + } + } + } + RTL_OSVERSIONINFOW rovi = { 0 }; + return rovi; + } + + inline HRESULT defaultExecutablePath(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { + DWORD written = GetModuleFileNameExW(GetCurrentProcess(), nullptr, path, nSize); + DEBUG_MSG("Default executable path: " << path); + return (written > 0) ? S_OK : E_FAIL; + } + + + inline HRESULT defaultShellLinksDirectory(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { + DWORD written = GetEnvironmentVariableW(L"APPDATA", path, nSize); + HRESULT hr = written > 0 ? S_OK : E_INVALIDARG; + if (SUCCEEDED(hr)) { + errno_t result = wcscat_s(path, nSize, DEFAULT_SHELL_LINKS_PATH); + hr = (result == 0) ? S_OK : E_INVALIDARG; + DEBUG_MSG("Default shell link path: " << path); + } + return hr; + } + + inline HRESULT defaultShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) { + HRESULT hr = defaultShellLinksDirectory(path, nSize); + if (SUCCEEDED(hr)) { + const std::wstring appLink(appname + DEFAULT_LINK_FORMAT); + errno_t result = wcscat_s(path, nSize, appLink.c_str()); + hr = (result == 0) ? S_OK : E_INVALIDARG; + DEBUG_MSG("Default shell link file path: " << path); + } + return hr; + } inline PCWSTR AsString(ComPtr &xmlDocument) { - HSTRING xml; - ComPtr ser; - HRESULT hr = xmlDocument.As(&ser); - hr = ser->GetXml(&xml); - if (SUCCEEDED(hr)) - return DllImporter::WindowsGetStringRawBuffer(xml, nullptr); - return nullptr; - } - - inline PCWSTR AsString(HSTRING hstring) { - return DllImporter::WindowsGetStringRawBuffer(hstring, nullptr); - } + HSTRING xml; + ComPtr ser; + HRESULT hr = xmlDocument.As(&ser); + hr = ser->GetXml(&xml); + if (SUCCEEDED(hr)) + return DllImporter::WindowsGetStringRawBuffer(xml, nullptr); + return nullptr; + } + + inline PCWSTR AsString(HSTRING hstring) { + return DllImporter::WindowsGetStringRawBuffer(hstring, nullptr); + } inline HRESULT setNodeStringValue(const std::wstring& string, IXmlNode *node, IXmlDocument *xml) { - ComPtr textNode; + ComPtr textNode; HRESULT hr = xml->CreateTextNode( WinToastStringWrapper(string).Get(), &textNode); - if (SUCCEEDED(hr)) { - ComPtr stringNode; - hr = textNode.As(&stringNode); - if (SUCCEEDED(hr)) { - ComPtr appendedChild; - hr = node->AppendChild(stringNode.Get(), &appendedChild); - } - } - return hr; - } - - inline HRESULT setEventHandlers(_In_ IToastNotification* notification, _In_ std::shared_ptr eventHandler, _In_ INT64 expirationTime) { - EventRegistrationToken activatedToken, dismissedToken, failedToken; - HRESULT hr = notification->add_Activated( - Callback < Implements < RuntimeClassFlags, - ITypedEventHandler> >( - [eventHandler](IToastNotification*, IInspectable* inspectable) - { + if (SUCCEEDED(hr)) { + ComPtr stringNode; + hr = textNode.As(&stringNode); + if (SUCCEEDED(hr)) { + ComPtr appendedChild; + hr = node->AppendChild(stringNode.Get(), &appendedChild); + } + } + return hr; + } + + inline HRESULT setEventHandlers(_In_ IToastNotification* notification, _In_ std::shared_ptr eventHandler, _In_ INT64 expirationTime) { + EventRegistrationToken activatedToken, dismissedToken, failedToken; + HRESULT hr = notification->add_Activated( + Callback < Implements < RuntimeClassFlags, + ITypedEventHandler> >( + [eventHandler](IToastNotification*, IInspectable* inspectable) + { IToastActivatedEventArgs *activatedEventArgs; - HRESULT hr = inspectable->QueryInterface(&activatedEventArgs); - if (SUCCEEDED(hr)) { - HSTRING argumentsHandle; - hr = activatedEventArgs->get_Arguments(&argumentsHandle); - if (SUCCEEDED(hr)) { - PCWSTR arguments = Util::AsString(argumentsHandle); - if (arguments && *arguments) { - eventHandler->toastActivated(static_cast(wcstol(arguments, nullptr, 10))); - return S_OK; - } - } - } - eventHandler->toastActivated(); - return S_OK; - }).Get(), &activatedToken); - - if (SUCCEEDED(hr)) { - hr = notification->add_Dismissed(Callback < Implements < RuntimeClassFlags, - ITypedEventHandler> >( - [eventHandler, expirationTime](IToastNotification*, IToastDismissedEventArgs* e) - { - ToastDismissalReason reason; - if (SUCCEEDED(e->get_Reason(&reason))) - { - if (reason == ToastDismissalReason_UserCanceled && expirationTime && InternalDateTime::Now() >= expirationTime) - reason = ToastDismissalReason_TimedOut; - eventHandler->toastDismissed(static_cast(reason)); - } - return S_OK; - }).Get(), &dismissedToken); - if (SUCCEEDED(hr)) { - hr = notification->add_Failed(Callback < Implements < RuntimeClassFlags, - ITypedEventHandler> >( - [eventHandler](IToastNotification*, IToastFailedEventArgs*) - { - eventHandler->toastFailed(); - return S_OK; - }).Get(), &failedToken); - } - } - return hr; - } + HRESULT hr = inspectable->QueryInterface(&activatedEventArgs); + if (SUCCEEDED(hr)) { + HSTRING argumentsHandle; + hr = activatedEventArgs->get_Arguments(&argumentsHandle); + if (SUCCEEDED(hr)) { + PCWSTR arguments = Util::AsString(argumentsHandle); + if (arguments && *arguments) { + eventHandler->toastActivated(static_cast(wcstol(arguments, nullptr, 10))); + return S_OK; + } + } + } + eventHandler->toastActivated(); + return S_OK; + }).Get(), &activatedToken); + + if (SUCCEEDED(hr)) { + hr = notification->add_Dismissed(Callback < Implements < RuntimeClassFlags, + ITypedEventHandler> >( + [eventHandler, expirationTime](IToastNotification*, IToastDismissedEventArgs* e) + { + ToastDismissalReason reason; + if (SUCCEEDED(e->get_Reason(&reason))) + { + if (reason == ToastDismissalReason_UserCanceled && expirationTime && InternalDateTime::Now() >= expirationTime) + reason = ToastDismissalReason_TimedOut; + eventHandler->toastDismissed(static_cast(reason)); + } + return S_OK; + }).Get(), &dismissedToken); + if (SUCCEEDED(hr)) { + hr = notification->add_Failed(Callback < Implements < RuntimeClassFlags, + ITypedEventHandler> >( + [eventHandler](IToastNotification*, IToastFailedEventArgs*) + { + eventHandler->toastFailed(); + return S_OK; + }).Get(), &failedToken); + } + } + return hr; + } inline HRESULT addAttribute(_In_ IXmlDocument *xml, const std::wstring &name, IXmlNamedNodeMap *attributeMap) { - ComPtr srcAttribute; - HRESULT hr = xml->CreateAttribute(WinToastStringWrapper(name).Get(), &srcAttribute); - if (SUCCEEDED(hr)) { - ComPtr node; - hr = srcAttribute.As(&node); - if (SUCCEEDED(hr)) { - ComPtr pNode; - hr = attributeMap->SetNamedItem(node.Get(), &pNode); - } - } - return hr; - } + ComPtr srcAttribute; + HRESULT hr = xml->CreateAttribute(WinToastStringWrapper(name).Get(), &srcAttribute); + if (SUCCEEDED(hr)) { + ComPtr node; + hr = srcAttribute.As(&node); + if (SUCCEEDED(hr)) { + ComPtr pNode; + hr = attributeMap->SetNamedItem(node.Get(), &pNode); + } + } + return hr; + } inline HRESULT createElement(_In_ IXmlDocument *xml, _In_ const std::wstring& root_node, _In_ const std::wstring& element_name, _In_ const std::vector& attribute_names) { - ComPtr rootList; - HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(root_node).Get(), &rootList); - if (SUCCEEDED(hr)) { - ComPtr root; - hr = rootList->Item(0, &root); - if (SUCCEEDED(hr)) { - ComPtr audioElement; - hr = xml->CreateElement(WinToastStringWrapper(element_name).Get(), &audioElement); - if (SUCCEEDED(hr)) { - ComPtr audioNodeTmp; - hr = audioElement.As(&audioNodeTmp); - if (SUCCEEDED(hr)) { - ComPtr audioNode; - hr = root->AppendChild(audioNodeTmp.Get(), &audioNode); - if (SUCCEEDED(hr)) { - ComPtr attributes; - hr = audioNode->get_Attributes(&attributes); - if (SUCCEEDED(hr)) { - for (const auto& it : attribute_names) { - hr = addAttribute(xml, it, attributes.Get()); - } - } - } - } - } - } - } - return hr; - } + ComPtr rootList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(root_node).Get(), &rootList); + if (SUCCEEDED(hr)) { + ComPtr root; + hr = rootList->Item(0, &root); + if (SUCCEEDED(hr)) { + ComPtr audioElement; + hr = xml->CreateElement(WinToastStringWrapper(element_name).Get(), &audioElement); + if (SUCCEEDED(hr)) { + ComPtr audioNodeTmp; + hr = audioElement.As(&audioNodeTmp); + if (SUCCEEDED(hr)) { + ComPtr audioNode; + hr = root->AppendChild(audioNodeTmp.Get(), &audioNode); + if (SUCCEEDED(hr)) { + ComPtr attributes; + hr = audioNode->get_Attributes(&attributes); + if (SUCCEEDED(hr)) { + for (const auto& it : attribute_names) { + hr = addAttribute(xml, it, attributes.Get()); + } + } + } + } + } + } + } + return hr; + } } WinToast* WinToast::instance() { - static WinToast instance; - return &instance; + static WinToast instance; + return &instance; } WinToast::WinToast() : - m_isInitialized(false), - m_hasCoInitialized(false) + m_isInitialized(false), + m_hasCoInitialized(false) { if (!isCompatible()) { DEBUG_MSG(L"Warning: Your system is not compatible with this library "); @@ -378,415 +374,457 @@ WinToast::WinToast() : } WinToast::~WinToast() { - if (m_hasCoInitialized) { - Microsoft::WRL::Module::GetModule().DecrementObjectCount(); - Module::GetModule().UnregisterObjects(); - CoUninitialize(); - } + if (m_hasCoInitialized) { + CoUninitialize(); + } } void WinToast::setAppName(_In_ const std::wstring& appName) { - m_appName = appName; + m_appName = appName; } void WinToast::setAppUserModelId(_In_ const std::wstring& aumi) { - m_aumi = aumi; - DEBUG_MSG(L"Default App User Model Id: " << m_aumi.c_str()); + m_aumi = aumi; + DEBUG_MSG(L"Default App User Model Id: " << m_aumi.c_str()); } void WinToast::setShortcutPolicy(_In_ ShortcutPolicy shortcutPolicy) { - m_shortcutPolicy = shortcutPolicy; + m_shortcutPolicy = shortcutPolicy; } bool WinToast::isCompatible() { DllImporter::initialize(); - return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr) - || (DllImporter::PropVariantToString == nullptr) - || (DllImporter::RoGetActivationFactory == nullptr) - || (DllImporter::WindowsCreateStringReference == nullptr) - || (DllImporter::WindowsDeleteString == nullptr)); + return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr) + || (DllImporter::PropVariantToString == nullptr) + || (DllImporter::RoGetActivationFactory == nullptr) + || (DllImporter::WindowsCreateStringReference == nullptr) + || (DllImporter::WindowsDeleteString == nullptr)); } bool WinToastLib::WinToast::isSupportingModernFeatures() { - constexpr auto MinimumSupportedVersion = 6; - return Util::getRealOSVersion().dwMajorVersion > MinimumSupportedVersion; + constexpr auto MinimumSupportedVersion = 6; + return Util::getRealOSVersion().dwMajorVersion > MinimumSupportedVersion; } std::wstring WinToast::configureAUMI(_In_ const std::wstring &companyName, _In_ const std::wstring &productName, _In_ const std::wstring &subProduct, _In_ const std::wstring &versionInformation) { - std::wstring aumi = companyName; - aumi += L"." + productName; - if (subProduct.length() > 0) { - aumi += L"." + subProduct; - if (versionInformation.length() > 0) { - aumi += L"." + versionInformation; - } - } - - if (aumi.length() > SCHAR_MAX) { - DEBUG_MSG("Error: max size allowed for AUMI: 128 characters."); - } - return aumi; + std::wstring aumi = companyName; + aumi += L"." + productName; + if (subProduct.length() > 0) { + aumi += L"." + subProduct; + if (versionInformation.length() > 0) { + aumi += L"." + versionInformation; + } + } + + if (aumi.length() > SCHAR_MAX) { + DEBUG_MSG("Error: max size allowed for AUMI: 128 characters."); + } + return aumi; } const std::wstring& WinToast::strerror(WinToastError error) { - static const std::unordered_map Labels = { - {WinToastError::NoError, L"No error. The process was executed correctly"}, - {WinToastError::NotInitialized, L"The library has not been initialized"}, - {WinToastError::SystemNotSupported, L"The OS does not support WinToast"}, - {WinToastError::ShellLinkNotCreated, L"The library was not able to create a Shell Link for the app"}, - {WinToastError::InvalidAppUserModelID, L"The AUMI is not a valid one"}, - {WinToastError::InvalidParameters, L"The parameters used to configure the library are not valid normally because an invalid AUMI or App Name"}, - {WinToastError::NotDisplayed, L"The toast was created correctly but WinToast was not able to display the toast"}, - {WinToastError::UnknownError, L"Unknown error"} - }; - - const auto iter = Labels.find(error); - assert(iter != Labels.end()); - return iter->second; + static const std::unordered_map Labels = { + {WinToastError::NoError, L"No error. The process was executed correctly"}, + {WinToastError::NotInitialized, L"The library has not been initialized"}, + {WinToastError::SystemNotSupported, L"The OS does not support WinToast"}, + {WinToastError::ShellLinkNotCreated, L"The library was not able to create a Shell Link for the app"}, + {WinToastError::InvalidAppUserModelID, L"The AUMI is not a valid one"}, + {WinToastError::InvalidParameters, L"The parameters used to configure the library are not valid normally because an invalid AUMI or App Name"}, + {WinToastError::NotDisplayed, L"The toast was created correctly but WinToast was not able to display the toast"}, + {WinToastError::UnknownError, L"Unknown error"} + }; + + const auto iter = Labels.find(error); + assert(iter != Labels.end()); + return iter->second; } enum WinToast::ShortcutResult WinToast::createShortcut() { - if (m_aumi.empty() || m_appName.empty()) { - DEBUG_MSG(L"Error: App User Model Id or Appname is empty!"); - return SHORTCUT_MISSING_PARAMETERS; - } - - bool wasChanged = false; - HRESULT hr = validateShellLinkHelper(wasChanged); - if (SUCCEEDED(hr)) - return wasChanged ? SHORTCUT_WAS_CHANGED : SHORTCUT_UNCHANGED; - - hr = createShellLinkHelper(); - return SUCCEEDED(hr) ? SHORTCUT_WAS_CREATED : SHORTCUT_CREATE_FAILED; -} + if (m_aumi.empty() || m_appName.empty()) { + DEBUG_MSG(L"Error: App User Model Id or Appname is empty!"); + return SHORTCUT_MISSING_PARAMETERS; + } -HRESULT RegisterComServer(const wchar_t clsid[]) { - std::wstring clsidStr(clsid); - - // Create the subkey - // Something like SOFTWARE\Classes\CLSID\{23A5B06E-20BB-4E7E-A0AC-6982ED6A6041}\LocalServer32 - std::wstring subKey = LR"(SOFTWARE\Classes\CLSID\{)" + clsidStr + LR"(}\LocalServer32)"; - - wchar_t exePath[MAX_PATH]; - DWORD pathLength = ::GetModuleFileName(nullptr, exePath, ARRAYSIZE(exePath)); - - if(pathLength > 0) { - std::wstring exePathStr = L"\"" + std::wstring(exePath) + L"\""; - DWORD dataSize = static_cast((exePathStr.length() + 1) * sizeof(wchar_t)); - - // Register the CLSID for the COM server. - HRESULT hr = HRESULT_FROM_WIN32(::RegSetKeyValue( - HKEY_CURRENT_USER, - subKey.c_str(), - nullptr, - REG_SZ, - reinterpret_cast(exePathStr.c_str()), - dataSize)); - } - - // Module needs a callback registered before it can be used. - // Since we don't care about when it shuts down, we'll pass an empty lambda here. - Microsoft::WRL::Module::Create([] {}); - - // If a local server process only hosts the COM object then COM expects - // the COM server host to shutdown when the references drop to zero. - // Since the user might still be using the program after activating the notification, - // we don't want to shutdown immediately. Incrementing the object count tells COM that - // we aren't done yet. - Microsoft::WRL::Module::GetModule().IncrementObjectCount(); - - return Module::GetModule().RegisterObjects(); + bool wasChanged = false; + HRESULT hr = validateShellLinkHelper(wasChanged); + if (SUCCEEDED(hr)) + return wasChanged ? SHORTCUT_WAS_CHANGED : SHORTCUT_UNCHANGED; + + hr = createShellLinkHelper(); + return SUCCEEDED(hr) ? SHORTCUT_WAS_CREATED : SHORTCUT_CREATE_FAILED; } bool WinToast::initialize(_Out_opt_ WinToastError* error) { - m_isInitialized = false; - setError(error, WinToastError::NoError); - - if (!isCompatible()) { - setError(error, WinToastError::SystemNotSupported); - DEBUG_MSG(L"Error: system not supported."); - return false; - } - - - if (m_aumi.empty() || m_appName.empty()) { - setError(error, WinToastError::InvalidParameters); - DEBUG_MSG(L"Error while initializing, did you set up a valid AUMI and App name?"); - return false; - } - - if (!isCompatible()) { - DEBUG_MSG(L"Your OS is not compatible with this library!"); - return false; - } - - if (!m_hasCoInitialized) { - HRESULT initHr = CoInitializeEx(nullptr, COINIT::COINIT_MULTITHREADED); - if (initHr != RPC_E_CHANGED_MODE) { - if (FAILED(initHr) && initHr != S_FALSE) { - DEBUG_MSG(L"Error on COM library initialization!"); - return false; - } - else { - m_hasCoInitialized = true; - } - } - } - - HRESULT hr = RegisterComServer(ST_STRINGIFY(NOTIFICATION_CALLBACK_CLSID)); - if (FAILED(hr)) { - DEBUG_MSG(L"Error while initializing the Com Server"); - return false; - } - - if (m_shortcutPolicy != SHORTCUT_POLICY_IGNORE) { - if (createShortcut() < 0) { - setError(error, WinToastError::ShellLinkNotCreated); - DEBUG_MSG(L"Error while attaching the AUMI to the current proccess"); - return false; - } - } - - if (FAILED(DllImporter::SetCurrentProcessExplicitAppUserModelID(m_aumi.c_str()))) { - setError(error, WinToastError::InvalidAppUserModelID); - DEBUG_MSG(L"Error while attaching the AUMI to the current proccess"); - return false; - } - - m_isInitialized = true; - return m_isInitialized; + m_isInitialized = false; + setError(error, WinToastError::NoError); + + if (!isCompatible()) { + setError(error, WinToastError::SystemNotSupported); + DEBUG_MSG(L"Error: system not supported."); + return false; + } + + + if (m_aumi.empty() || m_appName.empty()) { + setError(error, WinToastError::InvalidParameters); + DEBUG_MSG(L"Error while initializing, did you set up a valid AUMI and App name?"); + return false; + } + + if (!isCompatible()) { + DEBUG_MSG(L"Your OS is not compatible with this library!"); + return false; + } + + if (!m_hasCoInitialized) { + HRESULT initHr = CoInitializeEx(nullptr, COINIT::COINIT_MULTITHREADED); + if (initHr != RPC_E_CHANGED_MODE) { + if (FAILED(initHr) && initHr != S_FALSE) { + DEBUG_MSG(L"Error on COM library initialization!"); + return false; + } + else { + m_hasCoInitialized = true; + } + } + } + + if (m_shortcutPolicy != SHORTCUT_POLICY_IGNORE) { + if (createShortcut() < 0) { + setError(error, WinToastError::ShellLinkNotCreated); + DEBUG_MSG(L"Error while attaching the AUMI to the current proccess"); + return false; + } + } + + if (FAILED(DllImporter::SetCurrentProcessExplicitAppUserModelID(m_aumi.c_str()))) { + setError(error, WinToastError::InvalidAppUserModelID); + DEBUG_MSG(L"Error while attaching the AUMI to the current proccess"); + return false; + } + + m_isInitialized = true; + return m_isInitialized; } bool WinToast::isInitialized() const { - return m_isInitialized; + return m_isInitialized; } const std::wstring& WinToast::appName() const { - return m_appName; + return m_appName; } const std::wstring& WinToast::appUserModelId() const { - return m_aumi; + return m_aumi; } HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) { WCHAR path[MAX_PATH] = { L'\0' }; - Util::defaultShellLinkPath(m_appName, path); - // Check if the file exist - DWORD attr = GetFileAttributesW(path); - if (attr >= 0xFFFFFFF) { - DEBUG_MSG("Error, shell link not found. Try to create a new one in: " << path); - return E_FAIL; - } - - // Let's load the file as shell link to validate. - // - Create a shell link - // - Create a persistant file - // - Load the path as data for the persistant file - // - Read the property AUMI and validate with the current - // - Review if AUMI is equal. - ComPtr shellLink; - HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); - if (SUCCEEDED(hr)) { - ComPtr persistFile; - hr = shellLink.As(&persistFile); - if (SUCCEEDED(hr)) { - hr = persistFile->Load(path, STGM_READWRITE); - if (SUCCEEDED(hr)) { - ComPtr propertyStore; - hr = shellLink.As(&propertyStore); - if (SUCCEEDED(hr)) { - PROPVARIANT appIdPropVar; - hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &appIdPropVar); - if (SUCCEEDED(hr)) { - WCHAR AUMI[MAX_PATH]; - hr = DllImporter::PropVariantToString(appIdPropVar, AUMI, MAX_PATH); - wasChanged = false; - if (FAILED(hr) || m_aumi != AUMI) { - if (m_shortcutPolicy == SHORTCUT_POLICY_REQUIRE_CREATE) { - // AUMI Changed for the same app, let's update the current value! =) - wasChanged = true; - PropVariantClear(&appIdPropVar); - hr = InitPropVariantFromString(m_aumi.c_str(), &appIdPropVar); - if (SUCCEEDED(hr)) { - hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar); - if (SUCCEEDED(hr)) { - hr = propertyStore->Commit(); - if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty())) { - hr = persistFile->Save(path, TRUE); - } - } - } + Util::defaultShellLinkPath(m_appName, path); + // Check if the file exist + DWORD attr = GetFileAttributesW(path); + if (attr >= 0xFFFFFFF) { + DEBUG_MSG("Error, shell link not found. Try to create a new one in: " << path); + return E_FAIL; + } + + // Let's load the file as shell link to validate. + // - Create a shell link + // - Create a persistant file + // - Load the path as data for the persistant file + // - Read the property AUMI and validate with the current + // - Review if AUMI is equal. + ComPtr shellLink; + HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + if (SUCCEEDED(hr)) { + ComPtr persistFile; + hr = shellLink.As(&persistFile); + if (SUCCEEDED(hr)) { + hr = persistFile->Load(path, STGM_READWRITE); + if (SUCCEEDED(hr)) { + ComPtr propertyStore; + hr = shellLink.As(&propertyStore); + if (SUCCEEDED(hr)) { + PROPVARIANT appIdPropVar; + hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &appIdPropVar); + if (SUCCEEDED(hr)) { + WCHAR AUMI[MAX_PATH]; + hr = DllImporter::PropVariantToString(appIdPropVar, AUMI, MAX_PATH); + wasChanged = false; + if (FAILED(hr) || m_aumi != AUMI) { + if (m_shortcutPolicy == SHORTCUT_POLICY_REQUIRE_CREATE) { + // AUMI Changed for the same app, let's update the current value! =) + wasChanged = true; + PropVariantClear(&appIdPropVar); + hr = InitPropVariantFromString(m_aumi.c_str(), &appIdPropVar); + if (SUCCEEDED(hr)) { + hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar); + if (SUCCEEDED(hr)) { + hr = propertyStore->Commit(); + if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty())) { + hr = persistFile->Save(path, TRUE); + } + } + } } else { - // Not allowed to touch the shortcut to fix the AUMI - hr = E_FAIL; - } - } - PropVariantClear(&appIdPropVar); - } - } - } - } - } - return hr; + // Not allowed to touch the shortcut to fix the AUMI + hr = E_FAIL; + } + } + PropVariantClear(&appIdPropVar); + } + } + } + } + } + return hr; } HRESULT WinToast::createShellLinkHelper() { - if (m_shortcutPolicy != SHORTCUT_POLICY_REQUIRE_CREATE) { - return E_FAIL; - } - - WCHAR exePath[MAX_PATH]{L'\0'}; - WCHAR slPath[MAX_PATH]{L'\0'}; - Util::defaultShellLinkPath(m_appName, slPath); - Util::defaultExecutablePath(exePath); - ComPtr shellLink; - HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); - if (SUCCEEDED(hr)) { - hr = shellLink->SetPath(exePath); - if (SUCCEEDED(hr)) { - hr = shellLink->SetArguments(L""); - if (SUCCEEDED(hr)) { - hr = shellLink->SetWorkingDirectory(exePath); - if (SUCCEEDED(hr)) { - ComPtr propertyStore; - hr = shellLink.As(&propertyStore); - if (SUCCEEDED(hr)) { - PROPVARIANT appIdPropVar; - hr = InitPropVariantFromString(m_aumi.c_str(), &appIdPropVar); - if (SUCCEEDED(hr)) { - hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar); - if (SUCCEEDED(hr)) { - hr = propertyStore->Commit(); - if (SUCCEEDED(hr)) { - ComPtr persistFile; - hr = shellLink.As(&persistFile); - if (SUCCEEDED(hr)) { - hr = persistFile->Save(slPath, TRUE); - } - } - } - PropVariantClear(&appIdPropVar); - } - } - } - } - } - } - return hr; + if (m_shortcutPolicy != SHORTCUT_POLICY_REQUIRE_CREATE) { + return E_FAIL; + } + + WCHAR exePath[MAX_PATH]{ L'\0' }; + WCHAR slPath[MAX_PATH]{ L'\0' }; + Util::defaultShellLinkPath(m_appName, slPath); + // Util::defaultExecutablePath(exePath); + + IShellLink* iShellLink; + HRESULT hr = CoCreateInstance( + CLSID_ShellLink, + NULL, + CLSCTX_INPROC_SERVER, + IID_IShellLink, + (LPVOID*)&iShellLink + ); + + IPersistFile* iPersistFile; + hr = iShellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&iPersistFile); + + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = iPersistFile->Load(m_originalShellLinkPath.c_str(), STGM_READ); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = iShellLink->Resolve((HWND)0, 0); + if (!SUCCEEDED(hr)) { + return hr; + } + + std::wstring linkTarget(MAX_PATH, '\x00'); + hr = iShellLink->GetPath( + &linkTarget[0], + MAX_PATH, + 0, // WIN32_FIND_DATA* + SLGP_SHORTPATH + ); + + if (!SUCCEEDED(hr)) { + return hr; + } + + std::wstring description(MAX_PATH, '\x00'); + std::wstring cwd(MAX_PATH, '\x00'); + std::wstring path(MAX_PATH, '\x00'); + std::wstring arguments(MAX_PATH, '\x00'); + std::wstring iconLocation(MAX_PATH, '\x00'); + + hr = iShellLink->GetDescription(&description[0], MAX_PATH); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = iShellLink->GetWorkingDirectory(&cwd[0], MAX_PATH); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = iShellLink->GetPath(&path[0], MAX_PATH, nullptr, SLGP_RAWPATH); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = iShellLink->GetArguments(&arguments[0], MAX_PATH); + if (!SUCCEEDED(hr)) { + return hr; + } + int iconIndex = 0; + hr = iShellLink->GetIconLocation(&iconLocation[0], MAX_PATH, &iconIndex); + if (!SUCCEEDED(hr)) { + return hr; + } + + ComPtr shellLink; + hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink)); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = shellLink->SetPath(&path[0]); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = shellLink->SetArguments(&arguments[0]); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = shellLink->SetWorkingDirectory(&cwd[0]); + if (!SUCCEEDED(hr)) { + return hr; + } + + hr = shellLink->SetIconLocation(&iconLocation[0], iconIndex); + if (!SUCCEEDED(hr)) { + return hr; + } + + ComPtr propertyStore; + hr = shellLink.As(&propertyStore); + if (!SUCCEEDED(hr)) { + return hr; + } + PROPVARIANT appIdPropVar; + hr = InitPropVariantFromString(m_aumi.c_str(), &appIdPropVar); + if (!SUCCEEDED(hr)) { + return hr; + } + hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar); + if (!SUCCEEDED(hr)) { + return hr; + } + hr = propertyStore->Commit(); + if (!SUCCEEDED(hr)) { + return hr; + } + ComPtr persistFile; + hr = shellLink.As(&persistFile); + if (!SUCCEEDED(hr)) { + return hr; + } + hr = persistFile->Save(slPath, TRUE); + PropVariantClear(&appIdPropVar); + return hr; } INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ std::shared_ptr handler, _Out_ WinToastError* error) { - setError(error, WinToastError::NoError); - INT64 id = -1; - if (!isInitialized()) { - setError(error, WinToastError::NotInitialized); - DEBUG_MSG("Error when launching the toast. WinToast is not initialized."); - return id; - } - if (!handler) { - setError(error, WinToastError::InvalidHandler); - DEBUG_MSG("Error when launching the toast. Handler cannot be nullptr."); - return id; - } - - ComPtr notificationManager; - HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), ¬ificationManager); - if (SUCCEEDED(hr)) { - ComPtr notifier; - hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(m_aumi).Get(), ¬ifier); - if (SUCCEEDED(hr)) { - ComPtr notificationFactory; - hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), ¬ificationFactory); - if (SUCCEEDED(hr)) { + setError(error, WinToastError::NoError); + INT64 id = -1; + if (!isInitialized()) { + setError(error, WinToastError::NotInitialized); + DEBUG_MSG("Error when launching the toast. WinToast is not initialized."); + return id; + } + if (!handler) { + setError(error, WinToastError::InvalidHandler); + DEBUG_MSG("Error when launching the toast. Handler cannot be nullptr."); + return id; + } + + ComPtr notificationManager; + HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), ¬ificationManager); + if (SUCCEEDED(hr)) { + ComPtr notifier; + hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(m_aumi).Get(), ¬ifier); + if (SUCCEEDED(hr)) { + ComPtr notificationFactory; + hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), ¬ificationFactory); + if (SUCCEEDED(hr)) { ComPtr xmlDocument; HRESULT hr = notificationManager->GetTemplateContent(ToastTemplateType(toast.type()), &xmlDocument); - if (SUCCEEDED(hr)) { - for (UINT32 i = 0, fieldsCount = static_cast(toast.textFieldsCount()); i < fieldsCount && SUCCEEDED(hr); i++) { - hr = setTextFieldHelper(xmlDocument.Get(), toast.textField(WinToastTemplate::TextField(i)), i); - } - - // Modern feature are supported Windows > Windows 10 - if (SUCCEEDED(hr) && isSupportingModernFeatures()) { - - // Note that we do this *after* using toast.textFieldsCount() to - // iterate/fill the template's text fields, since we're adding yet another text field. - if (SUCCEEDED(hr) - && !toast.attributionText().empty()) { - hr = setAttributionTextFieldHelper(xmlDocument.Get(), toast.attributionText()); - } - - std::array buf; - for (std::size_t i = 0, actionsCount = toast.actionsCount(); i < actionsCount && SUCCEEDED(hr); i++) { - _snwprintf_s(buf.data(), buf.size(), _TRUNCATE, L"%zd", i); - hr = addActionHelper(xmlDocument.Get(), toast.actionLabel(i), buf.data()); - } - - if (SUCCEEDED(hr)) { - hr = (toast.audioPath().empty() && toast.audioOption() == WinToastTemplate::AudioOption::Default) - ? hr : setAudioFieldHelper(xmlDocument.Get(), toast.audioPath(), toast.audioOption()); - } - - if (SUCCEEDED(hr) && toast.duration() != WinToastTemplate::Duration::System) { - hr = addDurationHelper(xmlDocument.Get(), - (toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long"); - } - - if (SUCCEEDED(hr)) { - hr = addScenarioHelper(xmlDocument.Get(), toast.scenario()); - } + if (SUCCEEDED(hr)) { + for (UINT32 i = 0, fieldsCount = static_cast(toast.textFieldsCount()); i < fieldsCount && SUCCEEDED(hr); i++) { + hr = setTextFieldHelper(xmlDocument.Get(), toast.textField(WinToastTemplate::TextField(i)), i); + } + + // Modern feature are supported Windows > Windows 10 + if (SUCCEEDED(hr) && isSupportingModernFeatures()) { + + // Note that we do this *after* using toast.textFieldsCount() to + // iterate/fill the template's text fields, since we're adding yet another text field. + if (SUCCEEDED(hr) + && !toast.attributionText().empty()) { + hr = setAttributionTextFieldHelper(xmlDocument.Get(), toast.attributionText()); + } + + std::array buf; + for (std::size_t i = 0, actionsCount = toast.actionsCount(); i < actionsCount && SUCCEEDED(hr); i++) { + _snwprintf_s(buf.data(), buf.size(), _TRUNCATE, L"%zd", i); + hr = addActionHelper(xmlDocument.Get(), toast.actionLabel(i), buf.data()); + } + + if (SUCCEEDED(hr)) { + hr = (toast.audioPath().empty() && toast.audioOption() == WinToastTemplate::AudioOption::Default) + ? hr : setAudioFieldHelper(xmlDocument.Get(), toast.audioPath(), toast.audioOption()); + } + + if (SUCCEEDED(hr) && toast.duration() != WinToastTemplate::Duration::System) { + hr = addDurationHelper(xmlDocument.Get(), + (toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long"); + } + + if (SUCCEEDED(hr)) { + hr = addScenarioHelper(xmlDocument.Get(), toast.scenario()); + } } else { - DEBUG_MSG("Modern features (Actions/Sounds/Attributes) not supported in this os version"); - } - - if (SUCCEEDED(hr)) { - hr = toast.hasImage() ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath()) : hr; - if (SUCCEEDED(hr)) { - ComPtr notification; - hr = notificationFactory->CreateToastNotification(xmlDocument.Get(), ¬ification); - if (SUCCEEDED(hr)) { - INT64 expiration = 0, relativeExpiration = toast.expiration(); - if (relativeExpiration > 0) { - InternalDateTime expirationDateTime(relativeExpiration); - expiration = expirationDateTime; - hr = notification->put_ExpirationTime(&expirationDateTime); - } - - if (SUCCEEDED(hr)) { - hr = Util::setEventHandlers(notification.Get(), handler, expiration); - if (FAILED(hr)) { - setError(error, WinToastError::InvalidHandler); - } - } - - if (SUCCEEDED(hr)) { - GUID guid; - hr = CoCreateGuid(&guid); - if (SUCCEEDED(hr)) { - id = guid.Data1; - m_buffer[id] = notification; - DEBUG_MSG("xml: " << Util::AsString(xmlDocument)); - hr = notifier->Show(notification.Get()); - if (FAILED(hr)) { - setError(error, WinToastError::NotDisplayed); - } - } - } - } - } - } - } - } - } - } - return FAILED(hr) ? -1 : id; + DEBUG_MSG("Modern features (Actions/Sounds/Attributes) not supported in this os version"); + } + + if (SUCCEEDED(hr)) { + hr = toast.hasImage() ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath()) : hr; + if (SUCCEEDED(hr)) { + ComPtr notification; + hr = notificationFactory->CreateToastNotification(xmlDocument.Get(), ¬ification); + if (SUCCEEDED(hr)) { + INT64 expiration = 0, relativeExpiration = toast.expiration(); + if (relativeExpiration > 0) { + InternalDateTime expirationDateTime(relativeExpiration); + expiration = expirationDateTime; + hr = notification->put_ExpirationTime(&expirationDateTime); + } + + if (SUCCEEDED(hr)) { + hr = Util::setEventHandlers(notification.Get(), handler, expiration); + if (FAILED(hr)) { + setError(error, WinToastError::InvalidHandler); + } + } + + if (SUCCEEDED(hr)) { + GUID guid; + hr = CoCreateGuid(&guid); + if (SUCCEEDED(hr)) { + id = guid.Data1; + m_buffer[id] = notification; + DEBUG_MSG("xml: " << Util::AsString(xmlDocument)); + hr = notifier->Show(notification.Get()); + if (FAILED(hr)) { + setError(error, WinToastError::NotDisplayed); + } + } + } + } + } + } + } + } + } + } + return FAILED(hr) ? -1 : id; } ComPtr WinToast::notifier(_In_ bool* succeded) const { @@ -801,32 +839,32 @@ ComPtr WinToast::notifier(_In_ bool* succeded) const { } bool WinToast::hideToast(_In_ INT64 id) { - if (!isInitialized()) { - DEBUG_MSG("Error when hiding the toast. WinToast is not initialized."); - return false; - } - - if (m_buffer.find(id) != m_buffer.end()) { - auto succeded = false; - auto notify = notifier(&succeded); - if (succeded) { - auto result = notify->Hide(m_buffer[id].Get()); - m_buffer.erase(id); - return SUCCEEDED(result); - } - } - return false; + if (!isInitialized()) { + DEBUG_MSG("Error when hiding the toast. WinToast is not initialized."); + return false; + } + + if (m_buffer.find(id) != m_buffer.end()) { + auto succeded = false; + auto notify = notifier(&succeded); + if (succeded) { + auto result = notify->Hide(m_buffer[id].Get()); + m_buffer.erase(id); + return SUCCEEDED(result); + } + } + return false; } void WinToast::clear() { - auto succeded = false; - auto notify = notifier(&succeded); + auto succeded = false; + auto notify = notifier(&succeded); if (succeded) { auto end = m_buffer.end(); for (auto it = m_buffer.begin(); it != end; ++it) { notify->Hide(it->second.Get()); } - m_buffer.clear(); + m_buffer.clear(); } } @@ -838,331 +876,334 @@ void WinToast::clear() { // the toast's text fields or getting a count of them. // HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text) { - Util::createElement(xml, L"binding", L"text", { L"placement" }); - ComPtr nodeList; - HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); - if (SUCCEEDED(hr)) { - UINT32 nodeListLength; - hr = nodeList->get_Length(&nodeListLength); - if (SUCCEEDED(hr)) { - for (UINT32 i = 0; i < nodeListLength; i++) { - ComPtr textNode; - hr = nodeList->Item(i, &textNode); - if (SUCCEEDED(hr)) { - ComPtr attributes; - hr = textNode->get_Attributes(&attributes); - if (SUCCEEDED(hr)) { - ComPtr editedNode; - if (SUCCEEDED(hr)) { - hr = attributes->GetNamedItem(WinToastStringWrapper(L"placement").Get(), &editedNode); - if (FAILED(hr) || !editedNode) { - continue; - } - hr = Util::setNodeStringValue(L"attribution", editedNode.Get(), xml); - if (SUCCEEDED(hr)) { - return setTextFieldHelper(xml, text, i); - } - } - } - } - } - } - } - return hr; + Util::createElement(xml, L"binding", L"text", { L"placement" }); + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); + if (SUCCEEDED(hr)) { + UINT32 nodeListLength; + hr = nodeList->get_Length(&nodeListLength); + if (SUCCEEDED(hr)) { + for (UINT32 i = 0; i < nodeListLength; i++) { + ComPtr textNode; + hr = nodeList->Item(i, &textNode); + if (SUCCEEDED(hr)) { + ComPtr attributes; + hr = textNode->get_Attributes(&attributes); + if (SUCCEEDED(hr)) { + ComPtr editedNode; + if (SUCCEEDED(hr)) { + hr = attributes->GetNamedItem(WinToastStringWrapper(L"placement").Get(), &editedNode); + if (FAILED(hr) || !editedNode) { + continue; + } + hr = Util::setNodeStringValue(L"attribution", editedNode.Get(), xml); + if (SUCCEEDED(hr)) { + return setTextFieldHelper(xml, text, i); + } + } + } + } + } + } + } + return hr; } HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration) { - ComPtr nodeList; - HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); - if (SUCCEEDED(hr)) { - UINT32 length; - hr = nodeList->get_Length(&length); - if (SUCCEEDED(hr)) { - ComPtr toastNode; - hr = nodeList->Item(0, &toastNode); - if (SUCCEEDED(hr)) { - ComPtr toastElement; - hr = toastNode.As(&toastElement); - if (SUCCEEDED(hr)) { - hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), - WinToastStringWrapper(duration).Get()); - } - } - } - } - return hr; + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); + if (SUCCEEDED(hr)) { + UINT32 length; + hr = nodeList->get_Length(&length); + if (SUCCEEDED(hr)) { + ComPtr toastNode; + hr = nodeList->Item(0, &toastNode); + if (SUCCEEDED(hr)) { + ComPtr toastElement; + hr = toastNode.As(&toastElement); + if (SUCCEEDED(hr)) { + hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), + WinToastStringWrapper(duration).Get()); + } + } + } + } + return hr; } HRESULT WinToast::addScenarioHelper(_In_ IXmlDocument* xml, _In_ const std::wstring& scenario) { - ComPtr nodeList; - HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); - if (SUCCEEDED(hr)) { - UINT32 length; - hr = nodeList->get_Length(&length); - if (SUCCEEDED(hr)) { - ComPtr toastNode; - hr = nodeList->Item(0, &toastNode); - if (SUCCEEDED(hr)) { - ComPtr toastElement; - hr = toastNode.As(&toastElement); - if (SUCCEEDED(hr)) { - hr = toastElement->SetAttribute(WinToastStringWrapper(L"scenario").Get(), - WinToastStringWrapper(scenario).Get()); - } - } - } - } - return hr; + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); + if (SUCCEEDED(hr)) { + UINT32 length; + hr = nodeList->get_Length(&length); + if (SUCCEEDED(hr)) { + ComPtr toastNode; + hr = nodeList->Item(0, &toastNode); + if (SUCCEEDED(hr)) { + ComPtr toastElement; + hr = toastNode.As(&toastElement); + if (SUCCEEDED(hr)) { + hr = toastElement->SetAttribute(WinToastStringWrapper(L"scenario").Get(), + WinToastStringWrapper(scenario).Get()); + } + } + } + } + return hr; } HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos) { - ComPtr nodeList; - HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); - if (SUCCEEDED(hr)) { - ComPtr node; - hr = nodeList->Item(pos, &node); - if (SUCCEEDED(hr)) { - hr = Util::setNodeStringValue(text, node.Get(), xml); - } - } - return hr; + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); + if (SUCCEEDED(hr)) { + ComPtr node; + hr = nodeList->Item(pos, &node); + if (SUCCEEDED(hr)) { + hr = Util::setNodeStringValue(text, node.Get(), xml); + } + } + return hr; } - HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path) { - assert(path.size() < MAX_PATH); - - wchar_t imagePath[MAX_PATH] = L"file:///"; - HRESULT hr = StringCchCatW(imagePath, MAX_PATH, path.c_str()); - if (SUCCEEDED(hr)) { - ComPtr nodeList; - HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"image").Get(), &nodeList); - if (SUCCEEDED(hr)) { - ComPtr node; - hr = nodeList->Item(0, &node); + assert(path.size() < MAX_PATH); + + wchar_t imagePath[MAX_PATH] = L"file:///"; + HRESULT hr = StringCchCatW(imagePath, MAX_PATH, path.c_str()); + if (SUCCEEDED(hr)) { + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"image").Get(), &nodeList); + if (SUCCEEDED(hr)) { + ComPtr node; + hr = nodeList->Item(0, &node); if (SUCCEEDED(hr)) { - ComPtr attributes; - hr = node->get_Attributes(&attributes); - if (SUCCEEDED(hr)) { - ComPtr editedNode; - hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode); - if (SUCCEEDED(hr)) { - Util::setNodeStringValue(imagePath, editedNode.Get(), xml); - } - } - } - } - } - return hr; + ComPtr attributes; + hr = node->get_Attributes(&attributes); + if (SUCCEEDED(hr)) { + ComPtr editedNode; + hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode); + if (SUCCEEDED(hr)) { + Util::setNodeStringValue(imagePath, editedNode.Get(), xml); + } + } + } + } + } + return hr; } HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option) { - std::vector attrs; - if (!path.empty()) attrs.push_back(L"src"); - if (option == WinToastTemplate::AudioOption::Loop) attrs.push_back(L"loop"); - if (option == WinToastTemplate::AudioOption::Silent) attrs.push_back(L"silent"); - Util::createElement(xml, L"toast", L"audio", attrs); - - ComPtr nodeList; - HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"audio").Get(), &nodeList); - if (SUCCEEDED(hr)) { - ComPtr node; - hr = nodeList->Item(0, &node); - if (SUCCEEDED(hr)) { - ComPtr attributes; - hr = node->get_Attributes(&attributes); - if (SUCCEEDED(hr)) { - ComPtr editedNode; - if (!path.empty()) { - if (SUCCEEDED(hr)) { - hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode); - if (SUCCEEDED(hr)) { - hr = Util::setNodeStringValue(path, editedNode.Get(), xml); - } - } - } - - if (SUCCEEDED(hr)) { - switch (option) { - case WinToastTemplate::AudioOption::Loop: - hr = attributes->GetNamedItem(WinToastStringWrapper(L"loop").Get(), &editedNode); - if (SUCCEEDED(hr)) { - hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml); - } - break; - case WinToastTemplate::AudioOption::Silent: - hr = attributes->GetNamedItem(WinToastStringWrapper(L"silent").Get(), &editedNode); - if (SUCCEEDED(hr)) { - hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml); - } - default: - break; - } - } - } - } - } - return hr; + std::vector attrs; + if (!path.empty()) attrs.push_back(L"src"); + if (option == WinToastTemplate::AudioOption::Loop) attrs.push_back(L"loop"); + if (option == WinToastTemplate::AudioOption::Silent) attrs.push_back(L"silent"); + Util::createElement(xml, L"toast", L"audio", attrs); + + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"audio").Get(), &nodeList); + if (SUCCEEDED(hr)) { + ComPtr node; + hr = nodeList->Item(0, &node); + if (SUCCEEDED(hr)) { + ComPtr attributes; + hr = node->get_Attributes(&attributes); + if (SUCCEEDED(hr)) { + ComPtr editedNode; + if (!path.empty()) { + if (SUCCEEDED(hr)) { + hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode); + if (SUCCEEDED(hr)) { + hr = Util::setNodeStringValue(path, editedNode.Get(), xml); + } + } + } + + if (SUCCEEDED(hr)) { + switch (option) { + case WinToastTemplate::AudioOption::Loop: + hr = attributes->GetNamedItem(WinToastStringWrapper(L"loop").Get(), &editedNode); + if (SUCCEEDED(hr)) { + hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml); + } + break; + case WinToastTemplate::AudioOption::Silent: + hr = attributes->GetNamedItem(WinToastStringWrapper(L"silent").Get(), &editedNode); + if (SUCCEEDED(hr)) { + hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml); + } + default: + break; + } + } + } + } + } + return hr; } HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& content, _In_ const std::wstring& arguments) { ComPtr nodeList; HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"actions").Get(), &nodeList); - if (SUCCEEDED(hr)) { - UINT32 length; - hr = nodeList->get_Length(&length); - if (SUCCEEDED(hr)) { - ComPtr actionsNode; - if (length > 0) { - hr = nodeList->Item(0, &actionsNode); + if (SUCCEEDED(hr)) { + UINT32 length; + hr = nodeList->get_Length(&length); + if (SUCCEEDED(hr)) { + ComPtr actionsNode; + if (length > 0) { + hr = nodeList->Item(0, &actionsNode); } else { - hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); - if (SUCCEEDED(hr)) { - hr = nodeList->get_Length(&length); - if (SUCCEEDED(hr)) { - ComPtr toastNode; - hr = nodeList->Item(0, &toastNode); - if (SUCCEEDED(hr)) { - ComPtr toastElement; - hr = toastNode.As(&toastElement); - if (SUCCEEDED(hr)) - hr = toastElement->SetAttribute(WinToastStringWrapper(L"template").Get(), WinToastStringWrapper(L"ToastGeneric").Get()); - if (SUCCEEDED(hr)) - hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), WinToastStringWrapper(L"long").Get()); - if (SUCCEEDED(hr)) { - ComPtr actionsElement; - hr = xml->CreateElement(WinToastStringWrapper(L"actions").Get(), &actionsElement); - if (SUCCEEDED(hr)) { - hr = actionsElement.As(&actionsNode); - if (SUCCEEDED(hr)) { - ComPtr appendedChild; - hr = toastNode->AppendChild(actionsNode.Get(), &appendedChild); - } - } - } - } - } - } - } - if (SUCCEEDED(hr)) { - ComPtr actionElement; - hr = xml->CreateElement(WinToastStringWrapper(L"action").Get(), &actionElement); - if (SUCCEEDED(hr)) - hr = actionElement->SetAttribute(WinToastStringWrapper(L"content").Get(), WinToastStringWrapper(content).Get()); - if (SUCCEEDED(hr)) - hr = actionElement->SetAttribute(WinToastStringWrapper(L"arguments").Get(), WinToastStringWrapper(arguments).Get()); - if (SUCCEEDED(hr)) { - ComPtr actionNode; - hr = actionElement.As(&actionNode); - if (SUCCEEDED(hr)) { - ComPtr appendedChild; - hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild); - } - } - } - } - } - return hr; + hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); + if (SUCCEEDED(hr)) { + hr = nodeList->get_Length(&length); + if (SUCCEEDED(hr)) { + ComPtr toastNode; + hr = nodeList->Item(0, &toastNode); + if (SUCCEEDED(hr)) { + ComPtr toastElement; + hr = toastNode.As(&toastElement); + if (SUCCEEDED(hr)) + hr = toastElement->SetAttribute(WinToastStringWrapper(L"template").Get(), WinToastStringWrapper(L"ToastGeneric").Get()); + if (SUCCEEDED(hr)) + hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), WinToastStringWrapper(L"long").Get()); + if (SUCCEEDED(hr)) { + ComPtr actionsElement; + hr = xml->CreateElement(WinToastStringWrapper(L"actions").Get(), &actionsElement); + if (SUCCEEDED(hr)) { + hr = actionsElement.As(&actionsNode); + if (SUCCEEDED(hr)) { + ComPtr appendedChild; + hr = toastNode->AppendChild(actionsNode.Get(), &appendedChild); + } + } + } + } + } + } + } + if (SUCCEEDED(hr)) { + ComPtr actionElement; + hr = xml->CreateElement(WinToastStringWrapper(L"action").Get(), &actionElement); + if (SUCCEEDED(hr)) + hr = actionElement->SetAttribute(WinToastStringWrapper(L"content").Get(), WinToastStringWrapper(content).Get()); + if (SUCCEEDED(hr)) + hr = actionElement->SetAttribute(WinToastStringWrapper(L"arguments").Get(), WinToastStringWrapper(arguments).Get()); + if (SUCCEEDED(hr)) { + ComPtr actionNode; + hr = actionElement.As(&actionNode); + if (SUCCEEDED(hr)) { + ComPtr appendedChild; + hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild); + } + } + } + } + } + return hr; } void WinToast::setError(_Out_opt_ WinToastError* error, _In_ WinToastError value) { - if (error) { - *error = value; - } + if (error) { + *error = value; + } +} + +void WinToast::setShellLinkToCopy(_In_ const std::wstring& path) { + this->m_originalShellLinkPath = path; } WinToastTemplate::WinToastTemplate(_In_ WinToastTemplateType type) : m_type(type) { static constexpr std::size_t TextFieldsCount[] = { 1, 2, 2, 3, 1, 2, 2, 3}; - m_textFields = std::vector(TextFieldsCount[type], L""); + m_textFields = std::vector(TextFieldsCount[type], L""); } WinToastTemplate::~WinToastTemplate() { - m_textFields.clear(); + m_textFields.clear(); } void WinToastTemplate::setTextField(_In_ const std::wstring& txt, _In_ WinToastTemplate::TextField pos) { - const auto position = static_cast(pos); - assert(position < m_textFields.size()); - m_textFields[position] = txt; + const auto position = static_cast(pos); + assert(position < m_textFields.size()); + m_textFields[position] = txt; } void WinToastTemplate::setImagePath(_In_ const std::wstring& imgPath) { - m_imagePath = imgPath; + m_imagePath = imgPath; } void WinToastTemplate::setAudioPath(_In_ const std::wstring& audioPath) { - m_audioPath = audioPath; + m_audioPath = audioPath; } void WinToastTemplate::setAudioPath(_In_ AudioSystemFile file) { - static const std::unordered_map Files = { - {AudioSystemFile::DefaultSound, L"ms-winsoundevent:Notification.Default"}, - {AudioSystemFile::IM, L"ms-winsoundevent:Notification.IM"}, - {AudioSystemFile::Mail, L"ms-winsoundevent:Notification.Mail"}, - {AudioSystemFile::Reminder, L"ms-winsoundevent:Notification.Reminder"}, - {AudioSystemFile::SMS, L"ms-winsoundevent:Notification.SMS"}, - {AudioSystemFile::Alarm, L"ms-winsoundevent:Notification.Looping.Alarm"}, - {AudioSystemFile::Alarm2, L"ms-winsoundevent:Notification.Looping.Alarm2"}, - {AudioSystemFile::Alarm3, L"ms-winsoundevent:Notification.Looping.Alarm3"}, - {AudioSystemFile::Alarm4, L"ms-winsoundevent:Notification.Looping.Alarm4"}, - {AudioSystemFile::Alarm5, L"ms-winsoundevent:Notification.Looping.Alarm5"}, - {AudioSystemFile::Alarm6, L"ms-winsoundevent:Notification.Looping.Alarm6"}, - {AudioSystemFile::Alarm7, L"ms-winsoundevent:Notification.Looping.Alarm7"}, - {AudioSystemFile::Alarm8, L"ms-winsoundevent:Notification.Looping.Alarm8"}, - {AudioSystemFile::Alarm9, L"ms-winsoundevent:Notification.Looping.Alarm9"}, - {AudioSystemFile::Alarm10, L"ms-winsoundevent:Notification.Looping.Alarm10"}, - {AudioSystemFile::Call, L"ms-winsoundevent:Notification.Looping.Call"}, - {AudioSystemFile::Call1, L"ms-winsoundevent:Notification.Looping.Call1"}, - {AudioSystemFile::Call2, L"ms-winsoundevent:Notification.Looping.Call2"}, - {AudioSystemFile::Call3, L"ms-winsoundevent:Notification.Looping.Call3"}, - {AudioSystemFile::Call4, L"ms-winsoundevent:Notification.Looping.Call4"}, - {AudioSystemFile::Call5, L"ms-winsoundevent:Notification.Looping.Call5"}, - {AudioSystemFile::Call6, L"ms-winsoundevent:Notification.Looping.Call6"}, - {AudioSystemFile::Call7, L"ms-winsoundevent:Notification.Looping.Call7"}, - {AudioSystemFile::Call8, L"ms-winsoundevent:Notification.Looping.Call8"}, - {AudioSystemFile::Call9, L"ms-winsoundevent:Notification.Looping.Call9"}, - {AudioSystemFile::Call10, L"ms-winsoundevent:Notification.Looping.Call10"}, - }; - const auto iter = Files.find(file); - assert(iter != Files.end()); - m_audioPath = iter->second; + static const std::unordered_map Files = { + {AudioSystemFile::DefaultSound, L"ms-winsoundevent:Notification.Default"}, + {AudioSystemFile::IM, L"ms-winsoundevent:Notification.IM"}, + {AudioSystemFile::Mail, L"ms-winsoundevent:Notification.Mail"}, + {AudioSystemFile::Reminder, L"ms-winsoundevent:Notification.Reminder"}, + {AudioSystemFile::SMS, L"ms-winsoundevent:Notification.SMS"}, + {AudioSystemFile::Alarm, L"ms-winsoundevent:Notification.Looping.Alarm"}, + {AudioSystemFile::Alarm2, L"ms-winsoundevent:Notification.Looping.Alarm2"}, + {AudioSystemFile::Alarm3, L"ms-winsoundevent:Notification.Looping.Alarm3"}, + {AudioSystemFile::Alarm4, L"ms-winsoundevent:Notification.Looping.Alarm4"}, + {AudioSystemFile::Alarm5, L"ms-winsoundevent:Notification.Looping.Alarm5"}, + {AudioSystemFile::Alarm6, L"ms-winsoundevent:Notification.Looping.Alarm6"}, + {AudioSystemFile::Alarm7, L"ms-winsoundevent:Notification.Looping.Alarm7"}, + {AudioSystemFile::Alarm8, L"ms-winsoundevent:Notification.Looping.Alarm8"}, + {AudioSystemFile::Alarm9, L"ms-winsoundevent:Notification.Looping.Alarm9"}, + {AudioSystemFile::Alarm10, L"ms-winsoundevent:Notification.Looping.Alarm10"}, + {AudioSystemFile::Call, L"ms-winsoundevent:Notification.Looping.Call"}, + {AudioSystemFile::Call1, L"ms-winsoundevent:Notification.Looping.Call1"}, + {AudioSystemFile::Call2, L"ms-winsoundevent:Notification.Looping.Call2"}, + {AudioSystemFile::Call3, L"ms-winsoundevent:Notification.Looping.Call3"}, + {AudioSystemFile::Call4, L"ms-winsoundevent:Notification.Looping.Call4"}, + {AudioSystemFile::Call5, L"ms-winsoundevent:Notification.Looping.Call5"}, + {AudioSystemFile::Call6, L"ms-winsoundevent:Notification.Looping.Call6"}, + {AudioSystemFile::Call7, L"ms-winsoundevent:Notification.Looping.Call7"}, + {AudioSystemFile::Call8, L"ms-winsoundevent:Notification.Looping.Call8"}, + {AudioSystemFile::Call9, L"ms-winsoundevent:Notification.Looping.Call9"}, + {AudioSystemFile::Call10, L"ms-winsoundevent:Notification.Looping.Call10"}, + }; + const auto iter = Files.find(file); + assert(iter != Files.end()); + m_audioPath = iter->second; } void WinToastTemplate::setAudioOption(_In_ WinToastTemplate::AudioOption audioOption) { - m_audioOption = audioOption; + m_audioOption = audioOption; } void WinToastTemplate::setFirstLine(_In_ const std::wstring &text) { - setTextField(text, WinToastTemplate::FirstLine); + setTextField(text, WinToastTemplate::FirstLine); } void WinToastTemplate::setSecondLine(_In_ const std::wstring &text) { - setTextField(text, WinToastTemplate::SecondLine); + setTextField(text, WinToastTemplate::SecondLine); } void WinToastTemplate::setThirdLine(_In_ const std::wstring &text) { - setTextField(text, WinToastTemplate::ThirdLine); + setTextField(text, WinToastTemplate::ThirdLine); } void WinToastTemplate::setDuration(_In_ Duration duration) { - m_duration = duration; + m_duration = duration; } void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow) { - m_expiration = millisecondsFromNow; + m_expiration = millisecondsFromNow; } void WinToastLib::WinToastTemplate::setScenario(Scenario scenario) { - switch (scenario) { - case Scenario::Default: m_scenario = L"Default"; break; - case Scenario::Alarm: m_scenario = L"Alarm"; break; - case Scenario::IncomingCall: m_scenario = L"IncomingCall"; break; - case Scenario::Reminder: m_scenario = L"Reminder"; break; - } + switch (scenario) { + case Scenario::Default: m_scenario = L"Default"; break; + case Scenario::Alarm: m_scenario = L"Alarm"; break; + case Scenario::IncomingCall: m_scenario = L"IncomingCall"; break; + case Scenario::Reminder: m_scenario = L"Reminder"; break; + } } void WinToastTemplate::setAttributionText(_In_ const std::wstring& attributionText) { - m_attributionText = attributionText; + m_attributionText = attributionText; } void WinToastTemplate::addAction(_In_ const std::wstring & label) { @@ -1170,11 +1211,11 @@ void WinToastTemplate::addAction(_In_ const std::wstring & label) { } std::size_t WinToastTemplate::textFieldsCount() const { - return m_textFields.size(); + return m_textFields.size(); } std::size_t WinToastTemplate::actionsCount() const { - return m_actions.size(); + return m_actions.size(); } bool WinToastTemplate::hasImage() const { @@ -1182,48 +1223,48 @@ bool WinToastTemplate::hasImage() const { } const std::vector& WinToastTemplate::textFields() const { - return m_textFields; + return m_textFields; } const std::wstring& WinToastTemplate::textField(_In_ TextField pos) const { - const auto position = static_cast(pos); - assert(position < m_textFields.size()); - return m_textFields[position]; + const auto position = static_cast(pos); + assert(position < m_textFields.size()); + return m_textFields[position]; } const std::wstring& WinToastTemplate::actionLabel(_In_ std::size_t position) const { - assert(position < m_actions.size()); - return m_actions[position]; + assert(position < m_actions.size()); + return m_actions[position]; } const std::wstring& WinToastTemplate::imagePath() const { - return m_imagePath; + return m_imagePath; } const std::wstring& WinToastTemplate::audioPath() const { - return m_audioPath; + return m_audioPath; } const std::wstring& WinToastTemplate::attributionText() const { - return m_attributionText; + return m_attributionText; } const std::wstring& WinToastLib::WinToastTemplate::scenario() const { - return m_scenario; + return m_scenario; } INT64 WinToastTemplate::expiration() const { - return m_expiration; + return m_expiration; } WinToastTemplate::WinToastTemplateType WinToastTemplate::type() const { - return m_type; + return m_type; } WinToastTemplate::AudioOption WinToastTemplate::audioOption() const { - return m_audioOption; + return m_audioOption; } WinToastTemplate::Duration WinToastTemplate::duration() const { - return m_duration; + return m_duration; } diff --git a/src/wintoastlib.h b/src/wintoastlib.h index 90c1122..f46e29f 100644 --- a/src/wintoastlib.h +++ b/src/wintoastlib.h @@ -46,9 +46,6 @@ using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Notifications; using namespace Windows::Foundation; -// Unique class ID for the application. It must be also set in the shortcut. -#define NOTIFICATION_CALLBACK_CLSID "7F00FB48-65D5-4BA8-A35B-F194DA7E1A51" - namespace WinToastLib { class IWinToastHandler { @@ -214,6 +211,7 @@ namespace WinToastLib { void setAppName(_In_ const std::wstring &appName); void setShortcutPolicy(_In_ ShortcutPolicy policy); HRESULT validateShellLinkHelper(_Out_ bool& wasChanged); + void setShellLinkToCopy(_In_ const std::wstring& path); protected: bool m_isInitialized{false}; @@ -222,6 +220,7 @@ namespace WinToastLib { std::wstring m_appName{}; std::wstring m_aumi{}; std::map> m_buffer{}; + std::wstring m_originalShellLinkPath; HRESULT createShellLinkHelper(); HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path); @@ -236,39 +235,4 @@ namespace WinToastLib { }; } -// The fallowing code registers a callback for the notifications, it is requerd when the CLSID (Class ID) is set in the shortcut. -// Windows will be able to execute the callback even if the application is closed -// Note that the callback is not complete the Activete function is empty, noting will hapen if the application is closed. -typedef struct {} NOTIFICATION_USER_INPUT_DATA; - -MIDL_INTERFACE("53E31837-6600-4A81-9395-75CFFE746F94") -INotificationActivationCallback : public IUnknown -{ -public: - virtual HRESULT STDMETHODCALLTYPE Activate( - __RPC__in_string LPCWSTR appUserModelId, __RPC__in_opt_string LPCWSTR invokedArgs, - __RPC__in_ecount_full_opt(count) const NOTIFICATION_USER_INPUT_DATA * data, - ULONG count) = 0; -}; - -class DECLSPEC_UUID(NOTIFICATION_CALLBACK_CLSID) NotificationActivator WrlSealed WrlFinal - : public Microsoft::WRL::RuntimeClass< - Microsoft::WRL::RuntimeClassFlags, - INotificationActivationCallback> -{ -public: - virtual HRESULT STDMETHODCALLTYPE Activate( - _In_ LPCWSTR appUserModelId, - _In_ LPCWSTR invokedArgs, - _In_reads_(dataCount) const NOTIFICATION_USER_INPUT_DATA * data, - ULONG dataCount) override - { - // Not used but requerd for the callbacks to work with CLSID - return S_OK; - } -}; - -// Flag class as COM creatable -CoCreatableClass(NotificationActivator); - #endif // WINTOASTLIB_H