diff --git a/src/wintoastlib.cpp b/src/wintoastlib.cpp index 726950a..0654c52 100644 --- a/src/wintoastlib.cpp +++ b/src/wintoastlib.cpp @@ -1,4 +1,4 @@ -/* * Copyright (C) 2016-2019 Mohammed Boujemaoui +/* * Copyright (C) 2016-2023 Mohammed Boujemaoui * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -19,24 +19,30 @@ */ #include "wintoastlib.h" + #include #include #include #include +#include -#pragma comment(lib,"shlwapi") -#pragma comment(lib,"user32") +#pragma comment(lib, "shlwapi") +#pragma comment(lib, "user32") #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\\" -#define DEFAULT_LINK_FORMAT L".lnk" -#define STATUS_SUCCESS (0x00000000) - +#define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\" +#define DEFAULT_LINK_FORMAT L".lnk" +#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/ @@ -45,57 +51,61 @@ namespace DllImporter { // Function load a function from library template - HRESULT loadFunctionFromLibrary(HINSTANCE library, LPCSTR name, Function &func) { - if (!library) { - return E_INVALIDARG; - } + 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; } - 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); - typedef HRESULT(FAR STDAPICALLTYPE *f_RoGetActivationFactory)(_In_ HSTRING activatableClassId, _In_ REFIID iid, _COM_Outptr_ void ** factory); - typedef HRESULT(FAR STDAPICALLTYPE *f_WindowsCreateStringReference)(_In_reads_opt_(length + 1) PCWSTR sourceString, UINT32 length, _Out_ HSTRING_HEADER * hstringHeader, _Outptr_result_maybenull_ _Result_nullonfailure_ HSTRING * string); - 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) { + 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); + typedef HRESULT(FAR STDAPICALLTYPE* f_RoGetActivationFactory)(_In_ HSTRING activatableClassId, _In_ REFIID iid, + _COM_Outptr_ void** factory); + typedef HRESULT(FAR STDAPICALLTYPE* f_WindowsCreateStringReference)(_In_reads_opt_(length + 1) PCWSTR sourceString, UINT32 length, + _Out_ HSTRING_HEADER* hstringHeader, + _Outptr_result_maybenull_ _Result_nullonfailure_ HSTRING* string); + 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 + __inline _Check_return_ HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) { return RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory)); } - template + 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); + HRESULT hr = + loadFunctionFromLibrary(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID); if (SUCCEEDED(hr)) { HINSTANCE LibPropSys = LoadLibraryW(L"PROPSYS.DLL"); - hr = loadFunctionFromLibrary(LibPropSys, "PropVariantToString", PropVariantToString); + 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; + bool const 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; } -} +} // namespace DllImporter class WinToastStringWrapper { public: @@ -106,8 +116,9 @@ class WinToastStringWrapper { } } - WinToastStringWrapper(_In_ const std::wstring &stringRef) noexcept { - HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast(stringRef.length()), &_header, &_hstring); + WinToastStringWrapper(_In_ std::wstring const& stringRef) noexcept { + HRESULT hr = + DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast(stringRef.length()), &_header, &_hstring); if (FAILED(hr)) { RaiseException(static_cast(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr); } @@ -120,10 +131,10 @@ class WinToastStringWrapper { inline HSTRING Get() const noexcept { return _hstring; } + private: HSTRING _hstring; HSTRING_HEADER _header; - }; class InternalDateTime : public IReference { @@ -131,7 +142,7 @@ class InternalDateTime : public IReference { static INT64 Now() { FILETIME now; GetSystemTimeAsFileTime(&now); - return ((((INT64)now.dwHighDateTime) << 32) | now.dwLowDateTime); + return ((((INT64) now.dwHighDateTime) << 32) | now.dwLowDateTime); } InternalDateTime(DateTime dateTime) : _dateTime(dateTime) {} @@ -146,7 +157,7 @@ class InternalDateTime : public IReference { return _dateTime.UniversalTime; } - HRESULT STDMETHODCALLTYPE get_Value(DateTime *dateTime) { + HRESULT STDMETHODCALLTYPE get_Value(DateTime* dateTime) { *dateTime = _dateTime; return S_OK; } @@ -195,14 +206,14 @@ namespace Util { if (hMod) { RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion"); if (fxPtr != nullptr) { - RTL_OSVERSIONINFOW rovi = { 0 }; + RTL_OSVERSIONINFOW rovi = {0}; rovi.dwOSVersionInfoSize = sizeof(rovi); if (STATUS_SUCCESS == fxPtr(&rovi)) { return rovi; } } } - RTL_OSVERSIONINFOW rovi = { 0 }; + RTL_OSVERSIONINFOW rovi = {0}; return rovi; } @@ -212,47 +223,46 @@ namespace Util { 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; + 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; + 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) { + inline HRESULT defaultShellLinkPath(_In_ std::wstring const& 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; + hr = (result == 0) ? S_OK : E_INVALIDARG; DEBUG_MSG("Default shell link file path: " << path); } return hr; } - - inline PCWSTR AsString(ComPtr &xmlDocument) { + inline PCWSTR AsString(_In_ ComPtr& xmlDocument) { HSTRING xml; ComPtr ser; HRESULT hr = xmlDocument.As(&ser); - hr = ser->GetXml(&xml); - if (SUCCEEDED(hr)) + hr = ser->GetXml(&xml); + if (SUCCEEDED(hr)) { return DllImporter::WindowsGetStringRawBuffer(xml, nullptr); + } return nullptr; } - inline PCWSTR AsString(HSTRING hstring) { + inline PCWSTR AsString(_In_ HSTRING hstring) { return DllImporter::WindowsGetStringRawBuffer(hstring, nullptr); } - inline HRESULT setNodeStringValue(const std::wstring& string, IXmlNode *node, IXmlDocument *xml) { + inline HRESULT setNodeStringValue(_In_ std::wstring const& string, _Out_opt_ IXmlNode* node, _Out_ IXmlDocument* xml) { ComPtr textNode; - HRESULT hr = xml->CreateTextNode( WinToastStringWrapper(string).Get(), &textNode); + HRESULT hr = xml->CreateTextNode(WinToastStringWrapper(string).Get(), &textNode); if (SUCCEEDED(hr)) { ComPtr stringNode; hr = textNode.As(&stringNode); @@ -264,15 +274,16 @@ namespace Util { return hr; } - inline HRESULT setEventHandlers(_In_ IToastNotification* notification, _In_ std::shared_ptr eventHandler, _In_ INT64 expirationTime) { - EventRegistrationToken activatedToken, dismissedToken, failedToken; + template + inline HRESULT setEventHandlers(_In_ IToastNotification* notification, _In_ std::shared_ptr eventHandler, + _In_ INT64 expirationTime, _Out_ EventRegistrationToken& activatedToken, + _Out_ EventRegistrationToken& dismissedToken, _Out_ EventRegistrationToken& failedToken, + _In_ FunctorT&& markAsReadyForDeletionFunc) { HRESULT hr = notification->add_Activated( - Callback < Implements < RuntimeClassFlags, - ITypedEventHandler> >( - [eventHandler](IToastNotification*, IInspectable* inspectable) - { - IToastActivatedEventArgs *activatedEventArgs; - HRESULT hr = inspectable->QueryInterface(&activatedEventArgs); + Callback, ITypedEventHandler>>( + [eventHandler, markAsReadyForDeletionFunc](IToastNotification* notify, IInspectable* inspectable) { + ComPtr activatedEventArgs; + HRESULT hr = inspectable->QueryInterface(activatedEventArgs.GetAddressOf()); if (SUCCEEDED(hr)) { HSTRING argumentsHandle; hr = activatedEventArgs->get_Arguments(&argumentsHandle); @@ -280,42 +291,53 @@ namespace Util { PCWSTR arguments = Util::AsString(argumentsHandle); if (arguments && *arguments) { eventHandler->toastActivated(static_cast(wcstol(arguments, nullptr, 10))); + DllImporter::WindowsDeleteString(argumentsHandle); + markAsReadyForDeletionFunc(); return S_OK; } + DllImporter::WindowsDeleteString(argumentsHandle); } } eventHandler->toastActivated(); + markAsReadyForDeletionFunc(); return S_OK; - }).Get(), &activatedToken); + }) + .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); + hr = notification->add_Dismissed( + Callback, ITypedEventHandler>>( + [eventHandler, expirationTime, markAsReadyForDeletionFunc](IToastNotification* notify, 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)); + } + markAsReadyForDeletionFunc(); + 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); + hr = notification->add_Failed( + Callback, ITypedEventHandler>>( + [eventHandler, markAsReadyForDeletionFunc](IToastNotification* notify, IToastFailedEventArgs* e) { + eventHandler->toastFailed(); + markAsReadyForDeletionFunc(); + return S_OK; + }) + .Get(), + &failedToken); } } return hr; } - inline HRESULT addAttribute(_In_ IXmlDocument *xml, const std::wstring &name, IXmlNamedNodeMap *attributeMap) { + inline HRESULT addAttribute(_In_ IXmlDocument* xml, std::wstring const& name, IXmlNamedNodeMap* attributeMap) { ComPtr srcAttribute; HRESULT hr = xml->CreateAttribute(WinToastStringWrapper(name).Get(), &srcAttribute); if (SUCCEEDED(hr)) { @@ -329,7 +351,8 @@ namespace Util { 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) { + inline HRESULT createElement(_In_ IXmlDocument* xml, _In_ std::wstring const& root_node, _In_ std::wstring const& element_name, + _In_ std::vector const& attribute_names) { ComPtr rootList; HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(root_node).Get(), &rootList); if (SUCCEEDED(hr)) { @@ -348,7 +371,7 @@ namespace Util { ComPtr attributes; hr = audioNode->get_Attributes(&attributes); if (SUCCEEDED(hr)) { - for (const auto& it : attribute_names) { + for (auto const& it : attribute_names) { hr = addAttribute(xml, it, attributes.Get()); } } @@ -359,34 +382,32 @@ namespace Util { } return hr; } -} +} // namespace Util WinToast* WinToast::instance() { static WinToast instance; return &instance; } -WinToast::WinToast() : - _isInitialized(false), - _hasCoInitialized(false) -{ - if (!isCompatible()) { - DEBUG_MSG(L"Warning: Your system is not compatible with this library "); - } +WinToast::WinToast() : _isInitialized(false), _hasCoInitialized(false) { + if (!isCompatible()) { + DEBUG_MSG(L"Warning: Your system is not compatible with this library "); + } } WinToast::~WinToast() { + clear(); + if (_hasCoInitialized) { CoUninitialize(); } } -void WinToast::setAppName(_In_ const std::wstring& appName) { +void WinToast::setAppName(_In_ std::wstring const& appName) { _appName = appName; } - -void WinToast::setAppUserModelId(_In_ const std::wstring& aumi) { +void WinToast::setAppUserModelId(_In_ std::wstring const& aumi) { _aumi = aumi; DEBUG_MSG(L"Default App User Model Id: " << _aumi.c_str()); } @@ -396,24 +417,23 @@ void WinToast::setShortcutPolicy(_In_ ShortcutPolicy shortcutPolicy) { } bool WinToast::isCompatible() { - DllImporter::initialize(); - return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr) - || (DllImporter::PropVariantToString == nullptr) - || (DllImporter::RoGetActivationFactory == nullptr) - || (DllImporter::WindowsCreateStringReference == nullptr) - || (DllImporter::WindowsDeleteString == nullptr)); + DllImporter::initialize(); + 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; +} +bool WinToastLib::WinToast::isWin10AnniversaryOrHigher() { + return Util::getRealOSVersion().dwBuildNumber >= 14393; } -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 WinToast::configureAUMI(_In_ std::wstring const& companyName, _In_ std::wstring const& productName, + _In_ std::wstring const& subProduct, _In_ std::wstring const& versionInformation) { std::wstring aumi = companyName; aumi += L"." + productName; if (subProduct.length() > 0) { @@ -429,19 +449,19 @@ std::wstring WinToast::configureAUMI(_In_ const std::wstring &companyName, return aumi; } -const std::wstring& WinToast::strerror(WinToastError error) { +std::wstring const& 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"} + {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"Invalid parameters, please double-check the 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); + auto const iter = Labels.find(error); assert(iter != Labels.end()); return iter->second; } @@ -463,8 +483,7 @@ enum WinToast::ShortcutResult WinToast::createShortcut() { if (FAILED(initHr) && initHr != S_FALSE) { DEBUG_MSG(L"Error on COM library initialization!"); return SHORTCUT_COM_INIT_FAILURE; - } - else { + } else { _hasCoInitialized = true; } } @@ -472,8 +491,9 @@ enum WinToast::ShortcutResult WinToast::createShortcut() { bool wasChanged; HRESULT hr = validateShellLinkHelper(wasChanged); - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) { return wasChanged ? SHORTCUT_WAS_CHANGED : SHORTCUT_UNCHANGED; + } hr = createShellLinkHelper(); return SUCCEEDED(hr) ? SHORTCUT_WAS_CREATED : SHORTCUT_CREATE_FAILED; @@ -489,7 +509,6 @@ bool WinToast::initialize(_Out_opt_ WinToastError* error) { return false; } - if (_aumi.empty() || _appName.empty()) { setError(error, WinToastError::InvalidParameters); DEBUG_MSG(L"Error while initializing, did you set up a valid AUMI and App name?"); @@ -518,17 +537,16 @@ bool WinToast::isInitialized() const { return _isInitialized; } -const std::wstring& WinToast::appName() const { +std::wstring const& WinToast::appName() const { return _appName; } -const std::wstring& WinToast::appUserModelId() const { +std::wstring const& WinToast::appUserModelId() const { return _aumi; } - -HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) { - WCHAR path[MAX_PATH] = { L'\0' }; +HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) { + WCHAR path[MAX_PATH] = {L'\0'}; Util::defaultShellLinkPath(_appName, path); // Check if the file exist DWORD attr = GetFileAttributesW(path); @@ -558,7 +576,7 @@ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) { hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &appIdPropVar); if (SUCCEEDED(hr)) { WCHAR AUMI[MAX_PATH]; - hr = DllImporter::PropVariantToString(appIdPropVar, AUMI, MAX_PATH); + hr = DllImporter::PropVariantToString(appIdPropVar, AUMI, MAX_PATH); wasChanged = false; if (FAILED(hr) || _aumi != AUMI) { if (_shortcutPolicy == SHORTCUT_POLICY_REQUIRE_CREATE) { @@ -589,15 +607,13 @@ HRESULT WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) { return hr; } - - -HRESULT WinToast::createShellLinkHelper() { +HRESULT WinToast::createShellLinkHelper() { if (_shortcutPolicy != SHORTCUT_POLICY_REQUIRE_CREATE) { - return E_FAIL; + return E_FAIL; } - WCHAR exePath[MAX_PATH]{L'\0'}; - WCHAR slPath[MAX_PATH]{L'\0'}; + WCHAR exePath[MAX_PATH]{L'\0'}; + WCHAR slPath[MAX_PATH]{L'\0'}; Util::defaultShellLinkPath(_appName, slPath); Util::defaultExecutablePath(exePath); ComPtr shellLink; @@ -636,7 +652,8 @@ HRESULT WinToast::createShellLinkHelper() { return hr; } -INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_ WinToastError* error) { +INT64 WinToast::showToast(_In_ WinToastTemplate const& toast, _In_ IWinToastHandler* eventHandler, _Out_ WinToastError* error) { + std::shared_ptr handler(eventHandler); setError(error, WinToastError::NoError); INT64 id = -1; if (!isInitialized()) { @@ -651,16 +668,21 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan } ComPtr notificationManager; - HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), ¬ificationManager); + HRESULT hr = DllImporter::Wrap_GetActivationFactory( + WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), ¬ificationManager); if (SUCCEEDED(hr)) { ComPtr notifier; hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), ¬ifier); if (SUCCEEDED(hr)) { ComPtr notificationFactory; - hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), ¬ificationFactory); + 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); + ComPtr xmlDocument; + hr = notificationManager->GetTemplateContent(ToastTemplateType(toast.type()), &xmlDocument); + if (SUCCEEDED(hr) && toast.isToastGeneric()) { + hr = setBindToastGenericHelper(xmlDocument.Get()); + } 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); @@ -668,11 +690,9 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan // 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()) { + if (SUCCEEDED(hr) && !toast.attributionText().empty()) { hr = setAttributionTextFieldHelper(xmlDocument.Get(), toast.attributionText()); } @@ -684,12 +704,13 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan if (SUCCEEDED(hr)) { hr = (toast.audioPath().empty() && toast.audioOption() == WinToastTemplate::AudioOption::Default) - ? hr : setAudioFieldHelper(xmlDocument.Get(), toast.audioPath(), toast.audioOption()); + ? 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"); + (toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long"); } if (SUCCEEDED(hr)) { @@ -701,7 +722,14 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan } if (SUCCEEDED(hr)) { - hr = toast.hasImage() ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath()) : hr; + bool isWin10AnniversaryOrAbove = WinToast::isWin10AnniversaryOrHigher(); + bool isCircleCropHint = isWin10AnniversaryOrAbove ? toast.isCropHintCircle() : false; + hr = toast.hasImage() + ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath(), toast.isToastGeneric(), isCircleCropHint) + : hr; + if (SUCCEEDED(hr) && isWin10AnniversaryOrAbove && toast.hasHeroImage()) { + hr = setHeroImageHelper(xmlDocument.Get(), toast.heroImagePath(), toast.isInlineHeroImage()); + } if (SUCCEEDED(hr)) { ComPtr notification; hr = notificationFactory->CreateToastNotification(xmlDocument.Get(), ¬ification); @@ -710,22 +738,25 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan if (relativeExpiration > 0) { InternalDateTime expirationDateTime(relativeExpiration); expiration = expirationDateTime; - hr = notification->put_ExpirationTime(&expirationDateTime); + hr = notification->put_ExpirationTime(&expirationDateTime); } - if (SUCCEEDED(hr)) { - hr = Util::setEventHandlers(notification.Get(), std::shared_ptr(handler), expiration); + EventRegistrationToken activatedToken, dismissedToken, failedToken; + + GUID guid; + HRESULT hrGuid = CoCreateGuid(&guid); + id = guid.Data1; + if (SUCCEEDED(hr) && SUCCEEDED(hrGuid)) { + hr = Util::setEventHandlers(notification.Get(), handler, expiration, activatedToken, dismissedToken, + failedToken, [this, id]() { markAsReadyForDeletion(id); }); if (FAILED(hr)) { setError(error, WinToastError::InvalidHandler); } } if (SUCCEEDED(hr)) { - GUID guid; - hr = CoCreateGuid(&guid); if (SUCCEEDED(hr)) { - id = guid.Data1; - _buffer[id] = notification; + _buffer.emplace(id, NotifyData(notification, activatedToken, dismissedToken, failedToken)); DEBUG_MSG("xml: " << Util::AsString(xmlDocument)); hr = notifier->Show(notification.Get()); if (FAILED(hr)) { @@ -743,15 +774,34 @@ INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHan return FAILED(hr) ? -1 : id; } -ComPtr WinToast::notifier(_In_ bool* succeded) const { - ComPtr notificationManager; - ComPtr notifier; - HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), ¬ificationManager); - if (SUCCEEDED(hr)) { - hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), ¬ifier); - } - *succeded = SUCCEEDED(hr); - return notifier; +ComPtr WinToast::notifier(_In_ bool* succeded) const { + ComPtr notificationManager; + ComPtr notifier; + HRESULT hr = DllImporter::Wrap_GetActivationFactory( + WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), ¬ificationManager); + if (SUCCEEDED(hr)) { + hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), ¬ifier); + } + *succeded = SUCCEEDED(hr); + return notifier; +} + +void WinToast::markAsReadyForDeletion(_In_ INT64 id) { + // Flush the buffer by removing all the toasts that are ready for deletion + for (auto it = _buffer.begin(); it != _buffer.end();) { + if (it->second.isReadyForDeletion()) { + it->second.RemoveTokens(); + it = _buffer.erase(it); + } else { + ++it; + } + } + + // Mark the toast as ready for deletion (if it exists) so that it will be removed from the buffer in the next iteration + auto const iter = _buffer.find(id); + if (iter != _buffer.end()) { + _buffer[id].markAsReadyForDeletion(); + } } bool WinToast::hideToast(_In_ INT64 id) { @@ -760,28 +810,42 @@ bool WinToast::hideToast(_In_ INT64 id) { return false; } - if (_buffer.find(id) != _buffer.end()) { - auto succeded = false; - auto notify = notifier(&succeded); - if (succeded) { - auto result = notify->Hide(_buffer[id].Get()); - _buffer.erase(id); - return SUCCEEDED(result); - } - } - return false; + auto iter = _buffer.find(id); + if (iter == _buffer.end()) { + return false; + } + + auto succeded = false; + auto notify = notifier(&succeded); + if (!succeded) { + return false; + } + + auto& notifyData = iter->second; + auto result = notify->Hide(notifyData.notification()); + if (FAILED(result)) { + DEBUG_MSG("Error when hiding the toast. Error code: " << result); + return false; + } + + notifyData.RemoveTokens(); + _buffer.erase(iter); + return SUCCEEDED(result); } void WinToast::clear() { auto succeded = false; - auto notify = notifier(&succeded); - if (succeded) { - auto end = _buffer.end(); - for (auto it = _buffer.begin(); it != end; ++it) { - notify->Hide(it->second.Get()); - } - _buffer.clear(); - } + auto notify = notifier(&succeded); + if (!succeded) { + return; + } + + for (auto& data : _buffer) { + auto& notifyData = data.second; + notify->Hide(notifyData.notification()); + notifyData.RemoveTokens(); + } + _buffer.clear(); } // @@ -791,8 +855,8 @@ void WinToast::clear() { // NOTE: This will add a new text field, so be aware when iterating over // 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" }); +HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text) { + Util::createElement(xml, L"binding", L"text", {L"placement"}); ComPtr nodeList; HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); if (SUCCEEDED(hr)) { @@ -825,7 +889,7 @@ HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ con return hr; } -HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration) { +HRESULT WinToast::addDurationHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& duration) { ComPtr nodeList; HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); if (SUCCEEDED(hr)) { @@ -838,8 +902,7 @@ HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstr ComPtr toastElement; hr = toastNode.As(&toastElement); if (SUCCEEDED(hr)) { - hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), - WinToastStringWrapper(duration).Get()); + hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), WinToastStringWrapper(duration).Get()); } } } @@ -847,7 +910,7 @@ HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstr return hr; } -HRESULT WinToast::addScenarioHelper(_In_ IXmlDocument* xml, _In_ const std::wstring& scenario) { +HRESULT WinToast::addScenarioHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& scenario) { ComPtr nodeList; HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList); if (SUCCEEDED(hr)) { @@ -860,8 +923,7 @@ HRESULT WinToast::addScenarioHelper(_In_ IXmlDocument* xml, _In_ const std::wstr ComPtr toastElement; hr = toastNode.As(&toastElement); if (SUCCEEDED(hr)) { - hr = toastElement->SetAttribute(WinToastStringWrapper(L"scenario").Get(), - WinToastStringWrapper(scenario).Get()); + hr = toastElement->SetAttribute(WinToastStringWrapper(L"scenario").Get(), WinToastStringWrapper(scenario).Get()); } } } @@ -869,7 +931,7 @@ HRESULT WinToast::addScenarioHelper(_In_ IXmlDocument* xml, _In_ const std::wstr return hr; } -HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos) { +HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text, _In_ UINT32 pos) { ComPtr nodeList; HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList); if (SUCCEEDED(hr)) { @@ -882,19 +944,49 @@ HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wst return hr; } +HRESULT WinToast::setBindToastGenericHelper(_In_ IXmlDocument* xml) { + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"binding").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"template").Get(), WinToastStringWrapper(L"ToastGeneric").Get()); + } + } + } + } + return hr; +} -HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path) { +HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isToastGeneric, + _In_ bool isCropHintCircle) { assert(path.size() < MAX_PATH); wchar_t imagePath[MAX_PATH] = L"file:///"; - HRESULT hr = StringCchCatW(imagePath, MAX_PATH, path.c_str()); + 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 imageElement; + HRESULT hrImage = node.As(&imageElement); + if (SUCCEEDED(hr) && SUCCEEDED(hrImage) && isToastGeneric) { + hr = imageElement->SetAttribute(WinToastStringWrapper(L"placement").Get(), WinToastStringWrapper(L"appLogoOverride").Get()); + if (SUCCEEDED(hr) && isCropHintCircle) { + hr = imageElement->SetAttribute(WinToastStringWrapper(L"hint-crop").Get(), WinToastStringWrapper(L"circle").Get()); + } + } + if (SUCCEEDED(hr)) { ComPtr attributes; hr = node->get_Attributes(&attributes); if (SUCCEEDED(hr)) { @@ -910,11 +1002,18 @@ HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::ws return hr; } -HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option) { +HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& 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"); + 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; @@ -938,19 +1037,19 @@ HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::ws 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; + 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; } } } @@ -959,9 +1058,9 @@ HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::ws 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); +HRESULT WinToast::addActionHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& content, _In_ std::wstring const& arguments) { + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"actions").Get(), &nodeList); if (SUCCEEDED(hr)) { UINT32 length; hr = nodeList->get_Length(&length); @@ -979,10 +1078,14 @@ HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstrin 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)) { + 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); @@ -1001,10 +1104,12 @@ HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstrin if (SUCCEEDED(hr)) { ComPtr actionElement; hr = xml->CreateElement(WinToastStringWrapper(L"action").Get(), &actionElement); - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) { hr = actionElement->SetAttribute(WinToastStringWrapper(L"content").Get(), WinToastStringWrapper(content).Get()); - if (SUCCEEDED(hr)) + } + if (SUCCEEDED(hr)) { hr = actionElement->SetAttribute(WinToastStringWrapper(L"arguments").Get(), WinToastStringWrapper(arguments).Get()); + } if (SUCCEEDED(hr)) { ComPtr actionNode; hr = actionElement.As(&actionNode); @@ -1019,6 +1124,40 @@ HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstrin return hr; } +HRESULT WinToast::setHeroImageHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isInlineImage) { + ComPtr nodeList; + HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"binding").Get(), &nodeList); + if (SUCCEEDED(hr)) { + UINT32 length; + hr = nodeList->get_Length(&length); + if (SUCCEEDED(hr)) { + ComPtr bindingNode; + if (length > 0) { + hr = nodeList->Item(0, &bindingNode); + } + if (SUCCEEDED(hr)) { + ComPtr imageElement; + hr = xml->CreateElement(WinToastStringWrapper(L"image").Get(), &imageElement); + if (SUCCEEDED(hr) && isInlineImage == false) { + hr = imageElement->SetAttribute(WinToastStringWrapper(L"placement").Get(), WinToastStringWrapper(L"hero").Get()); + } + if (SUCCEEDED(hr)) { + hr = imageElement->SetAttribute(WinToastStringWrapper(L"src").Get(), WinToastStringWrapper(path).Get()); + } + if (SUCCEEDED(hr)) { + ComPtr actionNode; + hr = imageElement.As(&actionNode); + if (SUCCEEDED(hr)) { + ComPtr appendedChild; + hr = bindingNode->AppendChild(actionNode.Get(), &appendedChild); + } + } + } + } + } + return hr; +} + void WinToast::setError(_Out_opt_ WinToastError* error, _In_ WinToastError value) { if (error) { *error = value; @@ -1026,58 +1165,67 @@ void WinToast::setError(_Out_opt_ WinToastError* error, _In_ WinToastError value } WinToastTemplate::WinToastTemplate(_In_ WinToastTemplateType type) : _type(type) { - static constexpr std::size_t TextFieldsCount[] = { 1, 2, 2, 3, 1, 2, 2, 3}; - _textFields = std::vector(TextFieldsCount[type], L""); + constexpr static std::size_t TextFieldsCount[] = {1, 2, 2, 3, 1, 2, 2, 3}; + _textFields = std::vector(TextFieldsCount[type], L""); } WinToastTemplate::~WinToastTemplate() { _textFields.clear(); } -void WinToastTemplate::setTextField(_In_ const std::wstring& txt, _In_ WinToastTemplate::TextField pos) { - const auto position = static_cast(pos); - assert(position < _textFields.size()); +void WinToastTemplate::setTextField(_In_ std::wstring const& txt, _In_ WinToastTemplate::TextField pos) { + auto const position = static_cast(pos); + if (position >= _textFields.size()) { + DEBUG_MSG("The selected template type supports only " << _textFields.size() << " text lines"); + return; + } _textFields[position] = txt; } -void WinToastTemplate::setImagePath(_In_ const std::wstring& imgPath) { +void WinToastTemplate::setImagePath(_In_ std::wstring const& imgPath, _In_ CropHint cropHint) { _imagePath = imgPath; + _cropHint = cropHint; } -void WinToastTemplate::setAudioPath(_In_ const std::wstring& audioPath) { +void WinToastTemplate::setHeroImagePath(_In_ std::wstring const& imgPath, _In_ bool inlineImage) { + _heroImagePath = imgPath; + _inlineHeroImage = inlineImage; +} + +void WinToastTemplate::setAudioPath(_In_ std::wstring const& audioPath) { _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"}, + {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); + auto const iter = Files.find(file); assert(iter != Files.end()); _audioPath = iter->second; } @@ -1086,15 +1234,15 @@ void WinToastTemplate::setAudioOption(_In_ WinToastTemplate::AudioOption audioOp _audioOption = audioOption; } -void WinToastTemplate::setFirstLine(_In_ const std::wstring &text) { +void WinToastTemplate::setFirstLine(_In_ std::wstring const& text) { setTextField(text, WinToastTemplate::FirstLine); } -void WinToastTemplate::setSecondLine(_In_ const std::wstring &text) { +void WinToastTemplate::setSecondLine(_In_ std::wstring const& text) { setTextField(text, WinToastTemplate::SecondLine); } -void WinToastTemplate::setThirdLine(_In_ const std::wstring &text) { +void WinToastTemplate::setThirdLine(_In_ std::wstring const& text) { setTextField(text, WinToastTemplate::ThirdLine); } @@ -1106,21 +1254,29 @@ void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow) { _expiration = millisecondsFromNow; } -void WinToastLib::WinToastTemplate::setScenario(Scenario scenario) { +void WinToastLib::WinToastTemplate::setScenario(_In_ Scenario scenario) { switch (scenario) { - case Scenario::Default: _scenario = L"Default"; break; - case Scenario::Alarm: _scenario = L"Alarm"; break; - case Scenario::IncomingCall: _scenario = L"IncomingCall"; break; - case Scenario::Reminder: _scenario = L"Reminder"; break; + case Scenario::Default: + _scenario = L"Default"; + break; + case Scenario::Alarm: + _scenario = L"Alarm"; + break; + case Scenario::IncomingCall: + _scenario = L"IncomingCall"; + break; + case Scenario::Reminder: + _scenario = L"Reminder"; + break; } } -void WinToastTemplate::setAttributionText(_In_ const std::wstring& attributionText) { +void WinToastTemplate::setAttributionText(_In_ std::wstring const& attributionText) { _attributionText = attributionText; } -void WinToastTemplate::addAction(_In_ const std::wstring & label) { - _actions.push_back(label); +void WinToastTemplate::addAction(_In_ std::wstring const& label) { + _actions.push_back(label); } std::size_t WinToastTemplate::textFieldsCount() const { @@ -1132,37 +1288,45 @@ std::size_t WinToastTemplate::actionsCount() const { } bool WinToastTemplate::hasImage() const { - return _type < WinToastTemplateType::Text01; + return _type < WinToastTemplateType::Text01; +} + +bool WinToastTemplate::hasHeroImage() const { + return hasImage() && !_heroImagePath.empty(); } -const std::vector& WinToastTemplate::textFields() const { +std::vector const& WinToastTemplate::textFields() const { return _textFields; } -const std::wstring& WinToastTemplate::textField(_In_ TextField pos) const { - const auto position = static_cast(pos); +std::wstring const& WinToastTemplate::textField(_In_ TextField pos) const { + auto const position = static_cast(pos); assert(position < _textFields.size()); return _textFields[position]; } -const std::wstring& WinToastTemplate::actionLabel(_In_ std::size_t position) const { +std::wstring const& WinToastTemplate::actionLabel(_In_ std::size_t position) const { assert(position < _actions.size()); return _actions[position]; } -const std::wstring& WinToastTemplate::imagePath() const { +std::wstring const& WinToastTemplate::imagePath() const { return _imagePath; } -const std::wstring& WinToastTemplate::audioPath() const { +std::wstring const& WinToastTemplate::heroImagePath() const { + return _heroImagePath; +} + +std::wstring const& WinToastTemplate::audioPath() const { return _audioPath; } -const std::wstring& WinToastTemplate::attributionText() const { +std::wstring const& WinToastTemplate::attributionText() const { return _attributionText; } -const std::wstring& WinToastLib::WinToastTemplate::scenario() const { +std::wstring const& WinToastLib::WinToastTemplate::scenario() const { return _scenario; } @@ -1181,3 +1345,15 @@ WinToastTemplate::AudioOption WinToastTemplate::audioOption() const { WinToastTemplate::Duration WinToastTemplate::duration() const { return _duration; } + +bool WinToastTemplate::isToastGeneric() const { + return hasHeroImage() || _cropHint == WinToastTemplate::Circle; +} + +bool WinToastTemplate::isInlineHeroImage() const { + return _inlineHeroImage; +} + +bool WinToastTemplate::isCropHintCircle() const { + return _cropHint == CropHint::Circle; +} diff --git a/src/wintoastlib.h b/src/wintoastlib.h index fab5727..783bad4 100644 --- a/src/wintoastlib.h +++ b/src/wintoastlib.h @@ -1,4 +1,4 @@ -/* * Copyright (C) 2016-2019 Mohammed Boujemaoui +/* * Copyright (C) 2016-2023 Mohammed Boujemaoui * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in @@ -20,6 +20,7 @@ #ifndef WINTOASTLIB_H #define WINTOASTLIB_H + #include #include #include @@ -38,27 +39,29 @@ #include #include #include +#include + using namespace Microsoft::WRL; using namespace ABI::Windows::Data::Xml::Dom; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::UI::Notifications; using namespace Windows::Foundation; - namespace WinToastLib { class IWinToastHandler { public: enum WinToastDismissalReason { - UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled, + UserCanceled = ToastDismissalReason::ToastDismissalReason_UserCanceled, ApplicationHidden = ToastDismissalReason::ToastDismissalReason_ApplicationHidden, - TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut + TimedOut = ToastDismissalReason::ToastDismissalReason_TimedOut }; - virtual ~IWinToastHandler() = default; - virtual void toastActivated() const = 0; - virtual void toastActivated(int actionIndex) const = 0; + + virtual ~IWinToastHandler() = default; + virtual void toastActivated() const = 0; + virtual void toastActivated(int actionIndex) const = 0; virtual void toastDismissed(WinToastDismissalReason state) const = 0; - virtual void toastFailed() const = 0; + virtual void toastFailed() const = 0; }; class WinToastTemplate { @@ -67,15 +70,16 @@ namespace WinToastLib { enum Duration { System, Short, Long }; enum AudioOption { Default = 0, Silent, Loop }; enum TextField { FirstLine = 0, SecondLine, ThirdLine }; + enum WinToastTemplateType { ImageAndText01 = ToastTemplateType::ToastTemplateType_ToastImageAndText01, ImageAndText02 = ToastTemplateType::ToastTemplateType_ToastImageAndText02, ImageAndText03 = ToastTemplateType::ToastTemplateType_ToastImageAndText03, ImageAndText04 = ToastTemplateType::ToastTemplateType_ToastImageAndText04, - Text01 = ToastTemplateType::ToastTemplateType_ToastText01, - Text02 = ToastTemplateType::ToastTemplateType_ToastText02, - Text03 = ToastTemplateType::ToastTemplateType_ToastText03, - Text04 = ToastTemplateType::ToastTemplateType_ToastText04, + Text01 = ToastTemplateType::ToastTemplateType_ToastText01, + Text02 = ToastTemplateType::ToastTemplateType_ToastText02, + Text03 = ToastTemplateType::ToastTemplateType_ToastText03, + Text04 = ToastTemplateType::ToastTemplateType_ToastText04 }; enum AudioSystemFile { @@ -107,49 +111,63 @@ namespace WinToastLib { Call10, }; + enum CropHint { + Square, + Circle, + }; WinToastTemplate(_In_ WinToastTemplateType type = WinToastTemplateType::ImageAndText02); ~WinToastTemplate(); - void setFirstLine(_In_ const std::wstring& text); - void setSecondLine(_In_ const std::wstring& text); - void setThirdLine(_In_ const std::wstring& text); - void setTextField(_In_ const std::wstring& txt, _In_ TextField pos); - void setAttributionText(_In_ const std::wstring& attributionText); - void setImagePath(_In_ const std::wstring& imgPath); + void setFirstLine(_In_ std::wstring const& text); + void setSecondLine(_In_ std::wstring const& text); + void setThirdLine(_In_ std::wstring const& text); + void setTextField(_In_ std::wstring const& txt, _In_ TextField pos); + void setAttributionText(_In_ std::wstring const& attributionText); + void setImagePath(_In_ std::wstring const& imgPath, _In_ CropHint cropHint = CropHint::Square); + void setHeroImagePath(_In_ std::wstring const& imgPath, _In_ bool inlineImage = false); void setAudioPath(_In_ WinToastTemplate::AudioSystemFile audio); - void setAudioPath(_In_ const std::wstring& audioPath); + void setAudioPath(_In_ std::wstring const& audioPath); void setAudioOption(_In_ WinToastTemplate::AudioOption audioOption); void setDuration(_In_ Duration duration); void setExpiration(_In_ INT64 millisecondsFromNow); void setScenario(_In_ Scenario scenario); - void addAction(_In_ const std::wstring& label); + void addAction(_In_ std::wstring const& label); std::size_t textFieldsCount() const; std::size_t actionsCount() const; bool hasImage() const; - const std::vector& textFields() const; - const std::wstring& textField(_In_ TextField pos) const; - const std::wstring& actionLabel(_In_ std::size_t pos) const; - const std::wstring& imagePath() const; - const std::wstring& audioPath() const; - const std::wstring& attributionText() const; - const std::wstring& scenario() const; + bool hasHeroImage() const; + std::vector const& textFields() const; + std::wstring const& textField(_In_ TextField pos) const; + std::wstring const& actionLabel(_In_ std::size_t pos) const; + std::wstring const& imagePath() const; + std::wstring const& heroImagePath() const; + std::wstring const& audioPath() const; + std::wstring const& attributionText() const; + std::wstring const& scenario() const; INT64 expiration() const; WinToastTemplateType type() const; WinToastTemplate::AudioOption audioOption() const; Duration duration() const; + bool isToastGeneric() const; + bool isInlineHeroImage() const; + bool isCropHintCircle() const; + private: - std::vector _textFields{}; - std::vector _actions{}; - std::wstring _imagePath{}; - std::wstring _audioPath{}; - std::wstring _attributionText{}; - std::wstring _scenario{L"Default"}; - INT64 _expiration{0}; - AudioOption _audioOption{WinToastTemplate::AudioOption::Default}; - WinToastTemplateType _type{WinToastTemplateType::Text01}; - Duration _duration{Duration::System}; + std::vector _textFields{}; + std::vector _actions{}; + std::wstring _imagePath{}; + std::wstring _heroImagePath{}; + bool _inlineHeroImage{false}; + std::wstring _audioPath{}; + std::wstring _attributionText{}; + std::wstring _scenario{L"Default"}; + INT64 _expiration{0}; + AudioOption _audioOption{WinToastTemplate::AudioOption::Default}; + WinToastTemplateType _type{WinToastTemplateType::Text01}; + Duration _duration{Duration::System}; + CropHint _cropHint{CropHint::Square}; }; class WinToast { @@ -167,14 +185,14 @@ namespace WinToastLib { }; enum ShortcutResult { - SHORTCUT_UNCHANGED = 0, + SHORTCUT_UNCHANGED = 0, SHORTCUT_WAS_CHANGED = 1, SHORTCUT_WAS_CREATED = 2, SHORTCUT_MISSING_PARAMETERS = -1, - SHORTCUT_INCOMPATIBLE_OS = -2, - SHORTCUT_COM_INIT_FAILURE = -3, - SHORTCUT_CREATE_FAILED = -4 + SHORTCUT_INCOMPATIBLE_OS = -2, + SHORTCUT_COM_INIT_FAILURE = -3, + SHORTCUT_CREATE_FAILED = -4 }; enum ShortcutPolicy { @@ -182,8 +200,7 @@ namespace WinToastLib { SHORTCUT_POLICY_IGNORE = 0, /* Require a shortcut with matching AUMI, don't create or modify an existing one. */ SHORTCUT_POLICY_REQUIRE_NO_CREATE = 1, - /* Require a shortcut with matching AUMI, create if missing, modify if not matching. - * This is the default. */ + /* Require a shortcut with matching AUMI, create if missing, modify if not matching. This is the default. */ SHORTCUT_POLICY_REQUIRE_CREATE = 2, }; @@ -192,43 +209,99 @@ namespace WinToastLib { static WinToast* instance(); static bool isCompatible(); static bool isSupportingModernFeatures(); - static std::wstring configureAUMI(_In_ const std::wstring& companyName, - _In_ const std::wstring& productName, - _In_ const std::wstring& subProduct = std::wstring(), - _In_ const std::wstring& versionInformation = std::wstring()); - static const std::wstring& strerror(_In_ WinToastError error); + static bool isWin10AnniversaryOrHigher(); + static std::wstring configureAUMI(_In_ std::wstring const& companyName, _In_ std::wstring const& productName, + _In_ std::wstring const& subProduct = std::wstring(), + _In_ std::wstring const& versionInformation = std::wstring()); + static std::wstring const& strerror(_In_ WinToastError error); virtual bool initialize(_Out_opt_ WinToastError* error = nullptr); virtual bool isInitialized() const; virtual bool hideToast(_In_ INT64 id); - virtual INT64 showToast(_In_ const WinToastTemplate& toast, _In_ IWinToastHandler* handler, _Out_opt_ WinToastError* error = nullptr); + virtual INT64 showToast(_In_ WinToastTemplate const& toast, _In_ IWinToastHandler* eventHandler, + _Out_opt_ WinToastError* error = nullptr); virtual void clear(); virtual enum ShortcutResult createShortcut(); - const std::wstring& appName() const; - const std::wstring& appUserModelId() const; - void setAppUserModelId(_In_ const std::wstring& aumi); - void setAppName(_In_ const std::wstring& appName); + std::wstring const& appName() const; + std::wstring const& appUserModelId() const; + void setAppUserModelId(_In_ std::wstring const& aumi); + void setAppName(_In_ std::wstring const& appName); void setShortcutPolicy(_In_ ShortcutPolicy policy); protected: - bool _isInitialized{false}; - bool _hasCoInitialized{false}; - ShortcutPolicy _shortcutPolicy{SHORTCUT_POLICY_REQUIRE_CREATE}; - std::wstring _appName{}; - std::wstring _aumi{}; - std::map> _buffer{}; + struct NotifyData { + NotifyData(){}; + NotifyData(_In_ ComPtr notify, _In_ EventRegistrationToken activatedToken, + _In_ EventRegistrationToken dismissedToken, _In_ EventRegistrationToken failedToken) : + _notify(notify), _activatedToken(activatedToken), _dismissedToken(dismissedToken), _failedToken(failedToken) {} + + ~NotifyData() { + RemoveTokens(); + } + + void RemoveTokens() { + if (!_readyForDeletion) { + return; + } + + if (_previouslyTokenRemoved) { + return; + } + + if (!_notify.Get()) { + return; + } + + _notify->remove_Activated(_activatedToken); + _notify->remove_Dismissed(_dismissedToken); + _notify->remove_Failed(_failedToken); + _previouslyTokenRemoved = true; + } + + void markAsReadyForDeletion() { + _readyForDeletion = true; + } + + bool isReadyForDeletion() const { + return _readyForDeletion; + } + + IToastNotification* notification() { + return _notify.Get(); + } + + private: + ComPtr _notify{nullptr}; + EventRegistrationToken _activatedToken{}; + EventRegistrationToken _dismissedToken{}; + EventRegistrationToken _failedToken{}; + bool _readyForDeletion{false}; + bool _previouslyTokenRemoved{false}; + }; + + bool _isInitialized{false}; + bool _hasCoInitialized{false}; + ShortcutPolicy _shortcutPolicy{SHORTCUT_POLICY_REQUIRE_CREATE}; + std::wstring _appName{}; + std::wstring _aumi{}; + std::map _buffer{}; + void markAsReadyForDeletion(_In_ INT64 id); HRESULT validateShellLinkHelper(_Out_ bool& wasChanged); HRESULT createShellLinkHelper(); - HRESULT setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path); - HRESULT setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default); - HRESULT setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos); - HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text); - HRESULT addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& action, _In_ const std::wstring& arguments); - HRESULT addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration); - HRESULT addScenarioHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& scenario); + HRESULT setImageFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isToastGeneric, bool isCropHintCircle); + HRESULT setHeroImageHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, _In_ bool isInlineImage); + HRESULT setBindToastGenericHelper(_In_ IXmlDocument* xml); + HRESULT + setAudioFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& path, + _In_opt_ WinToastTemplate::AudioOption option = WinToastTemplate::AudioOption::Default); + HRESULT setTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text, _In_ UINT32 pos); + HRESULT setAttributionTextFieldHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& text); + HRESULT addActionHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& action, _In_ std::wstring const& arguments); + HRESULT addDurationHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& duration); + HRESULT addScenarioHelper(_In_ IXmlDocument* xml, _In_ std::wstring const& scenario); ComPtr notifier(_In_ bool* succeded) const; void setError(_Out_opt_ WinToastError* error, _In_ WinToastError value); }; -} +} // namespace WinToastLib #endif // WINTOASTLIB_H diff --git a/src/wintoastlibc.cpp b/src/wintoastlibc.cpp index cd90a95..5b4c36d 100644 --- a/src/wintoastlibc.cpp +++ b/src/wintoastlibc.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2022 Peter Zhigalov + * Copyright (c) 2022-2023 Peter Zhigalov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -159,6 +159,39 @@ void WTLCAPI WTLC_Template_setImagePath(_In_ WTLC_Template * toast, _In_ LPCWSTR {} } +void WTLCAPI WTLC_Template_setImagePathWithCropHint(_In_ WTLC_Template * toast, _In_ LPCWSTR imgPath, _In_ WTLC_CropHint cropHint) +{ + try + { + WinToastLib::WinToastTemplate::CropHint _cropHint; + switch(cropHint) + { +#define ADD_CASE(X) case WTLC_CropHint_ ##X : _cropHint = WinToastLib::WinToastTemplate:: X ; break + ADD_CASE(Square); + ADD_CASE(Circle); +#undef ADD_CASE + default: + _cropHint = WinToastLib::WinToastTemplate::Square; + break; + } + if(toast) + toast->_.setImagePath(imgPath, _cropHint); + } + catch(...) + {} +} + +void WTLCAPI WTLC_Template_setHeroImagePath(_In_ WTLC_Template * toast, _In_ LPCWSTR imgPath, _In_ BOOL inlineImage) +{ + try + { + if(toast) + toast->_.setHeroImagePath(imgPath, inlineImage); + } + catch(...) + {} +} + void WTLCAPI WTLC_Template_setAudioSystemFile(_In_ WTLC_Template * toast, _In_ WTLC_AudioSystemFile audio) { try @@ -344,6 +377,18 @@ BOOL WTLCAPI WTLC_Template_hasImage(_In_ WTLC_Template * toast) return FALSE; } +BOOL WTLCAPI WTLC_Template_hasHeroImage(_In_ WTLC_Template * toast) +{ + try + { + if(toast) + return toast->_.hasHeroImage(); + } + catch(...) + {} + return FALSE; +} + LPCWSTR WTLCAPI WTLC_Template_textField(_In_ WTLC_Template * toast, _In_ WTLC_TextField pos) { try @@ -392,6 +437,18 @@ LPCWSTR WTLCAPI WTLC_Template_imagePath(_In_ WTLC_Template * toast) return NULL; } +LPCWSTR WTLCAPI WTLC_Template_heroImagePath(_In_ WTLC_Template * toast) +{ + try + { + if(toast) + return toast->_.heroImagePath().c_str(); + } + catch(...) + {} + return NULL; +} + LPCWSTR WTLCAPI WTLC_Template_audioPath(_In_ WTLC_Template * toast) { try @@ -520,6 +577,42 @@ WTLC_Duration WTLCAPI WTLC_Template_duration(_In_ WTLC_Template * toast) return WTLC_Duration_System; } +BOOL WTLCAPI WTLC_Template_isToastGeneric(_In_ WTLC_Template * toast) +{ + try + { + if(toast) + return toast->_.isToastGeneric(); + } + catch(...) + {} + return FALSE; +} + +BOOL WTLCAPI WTLC_Template_isInlineHeroImage(_In_ WTLC_Template * toast) +{ + try + { + if(toast) + return toast->_.isInlineHeroImage(); + } + catch(...) + {} + return FALSE; +} + +BOOL WTLCAPI WTLC_Template_isCropHintCircle(_In_ WTLC_Template * toast) +{ + try + { + if(toast) + return toast->_.isCropHintCircle(); + } + catch(...) + {} + return FALSE; +} + struct _WTLC_Instance { WinToastLib::WinToast * _; @@ -575,6 +668,18 @@ BOOL WTLCAPI WTLC_isSupportingModernFeatures() } } +BOOL WTLCAPI WTLC_isWin10AnniversaryOrHigher() +{ + try + { + return WinToastLib::WinToast::isWin10AnniversaryOrHigher(); + } + catch(...) + { + return FALSE; + } +} + LPCWSTR WTLCAPI WTLC_strerror(_In_ WTLC_Error error) { try diff --git a/src/wintoastlibc.h b/src/wintoastlibc.h index c2617e7..a426795 100644 --- a/src/wintoastlibc.h +++ b/src/wintoastlibc.h @@ -1,5 +1,5 @@ /** - * Copyright (c) 2022 Peter Zhigalov + * Copyright (c) 2022-2023 Peter Zhigalov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -147,6 +147,13 @@ enum _WTLC_AudioSystemFile }; typedef enum _WTLC_AudioSystemFile WTLC_AudioSystemFile; +enum _WTLC_CropHint +{ + WTLC_CropHint_Square, + WTLC_CropHint_Circle +}; +typedef enum _WTLC_CropHint WTLC_CropHint; + typedef struct _WTLC_Template WTLC_Template; WTLC_LIB WTLC_Template * WTLCAPI WTLC_Template_Create(_In_ WTLC_TemplateType type); @@ -158,6 +165,8 @@ WTLC_LIB void WTLCAPI WTLC_Template_setThirdLine(_In_ WTLC_Template * toast, _In WTLC_LIB void WTLCAPI WTLC_Template_setTextField(_In_ WTLC_Template * toast, _In_ LPCWSTR txt, _In_ WTLC_TextField pos); WTLC_LIB void WTLCAPI WTLC_Template_setAttributionText(_In_ WTLC_Template * toast, _In_ LPCWSTR attributionText); WTLC_LIB void WTLCAPI WTLC_Template_setImagePath(_In_ WTLC_Template * toast, _In_ LPCWSTR imgPath); +WTLC_LIB void WTLCAPI WTLC_Template_setImagePathWithCropHint(_In_ WTLC_Template * toast, _In_ LPCWSTR imgPath, _In_ WTLC_CropHint cropHint); +WTLC_LIB void WTLCAPI WTLC_Template_setHeroImagePath(_In_ WTLC_Template * toast, _In_ LPCWSTR imgPath, _In_ BOOL inlineImage); WTLC_LIB void WTLCAPI WTLC_Template_setAudioSystemFile(_In_ WTLC_Template * toast, _In_ WTLC_AudioSystemFile audio); WTLC_LIB void WTLCAPI WTLC_Template_setAudioPath(_In_ WTLC_Template * toast, _In_ LPCWSTR audioPath); WTLC_LIB void WTLCAPI WTLC_Template_setAudioOption(_In_ WTLC_Template * toast, _In_ WTLC_AudioOption audioOption); @@ -169,9 +178,11 @@ WTLC_LIB void WTLCAPI WTLC_Template_addAction(_In_ WTLC_Template * toast, _In_ L WTLC_LIB size_t WTLCAPI WTLC_Template_textFieldsCount(_In_ WTLC_Template * toast); WTLC_LIB size_t WTLCAPI WTLC_Template_actionsCount(_In_ WTLC_Template * toast); WTLC_LIB BOOL WTLCAPI WTLC_Template_hasImage(_In_ WTLC_Template * toast); +WTLC_LIB BOOL WTLCAPI WTLC_Template_hasHeroImage(_In_ WTLC_Template * toast); WTLC_LIB LPCWSTR WTLCAPI WTLC_Template_textField(_In_ WTLC_Template * toast, _In_ WTLC_TextField pos); WTLC_LIB LPCWSTR WTLCAPI WTLC_Template_actionLabel(_In_ WTLC_Template * toast, _In_ size_t pos); WTLC_LIB LPCWSTR WTLCAPI WTLC_Template_imagePath(_In_ WTLC_Template * toast); +WTLC_LIB LPCWSTR WTLCAPI WTLC_Template_heroImagePath(_In_ WTLC_Template * toast); WTLC_LIB LPCWSTR WTLCAPI WTLC_Template_audioPath(_In_ WTLC_Template * toast); WTLC_LIB LPCWSTR WTLCAPI WTLC_Template_attributionText(_In_ WTLC_Template * toast); WTLC_LIB LPCWSTR WTLCAPI WTLC_Template_scenario(_In_ WTLC_Template * toast); @@ -179,6 +190,9 @@ WTLC_LIB INT64 WTLCAPI WTLC_Template_expiration(_In_ WTLC_Template * toast); WTLC_LIB WTLC_TemplateType WTLCAPI WTLC_Template_type(_In_ WTLC_Template * toast); WTLC_LIB WTLC_AudioOption WTLCAPI WTLC_Template_audioOption(_In_ WTLC_Template * toast); WTLC_LIB WTLC_Duration WTLCAPI WTLC_Template_duration(_In_ WTLC_Template * toast); +WTLC_LIB BOOL WTLCAPI WTLC_Template_isToastGeneric(_In_ WTLC_Template * toast); +WTLC_LIB BOOL WTLCAPI WTLC_Template_isInlineHeroImage(_In_ WTLC_Template * toast); +WTLC_LIB BOOL WTLCAPI WTLC_Template_isCropHintCircle(_In_ WTLC_Template * toast); enum _WTLC_Error { @@ -221,6 +235,7 @@ WTLC_LIB void WTLCAPI WTLC_Instance_Destroy(_In_ WTLC_Instance * instance); WTLC_LIB BOOL WTLCAPI WTLC_isCompatible(void); WTLC_LIB BOOL WTLCAPI WTLC_isSupportingModernFeatures(void); +WTLC_LIB BOOL WTLCAPI WTLC_isWin10AnniversaryOrHigher(void); WTLC_LIB LPCWSTR WTLCAPI WTLC_strerror(_In_ WTLC_Error error); WTLC_LIB BOOL WTLCAPI WTLC_initialize(_In_ WTLC_Instance * instance, _Out_opt_ WTLC_Error * error); WTLC_LIB BOOL WTLCAPI WTLC_isInitialized(_In_ WTLC_Instance * instance); diff --git a/src/wintoastlibc_lazy.c b/src/wintoastlibc_lazy.c index c0dc27a..d30bb69 100644 --- a/src/wintoastlibc_lazy.c +++ b/src/wintoastlibc_lazy.c @@ -1,5 +1,5 @@ /** - * Copyright (c) 2022 Peter Zhigalov + * Copyright (c) 2022-2023 Peter Zhigalov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -58,73 +58,95 @@ static HMODULE s_module = NULL; abort(); \ } -#define FORWARD0(NAME, RTYPE) \ +#define NOARG + +#define FORWARD0_HELPER(NAME, RTYPE, RKEYW) \ RTYPE WTLCAPI NAME (void) { \ typedef RTYPE (WTLCAPI * t)(void); \ t f; \ LOAD(h) \ f = (t)GetProcAddress(h, #NAME); \ SYMCHECK(#NAME, f) \ - return f(); \ + RKEYW f(); \ } -#define FORWARD1(NAME, RTYPE, A1TYPE) \ +#define FORWARD0(NAME, RTYPE) FORWARD0_HELPER(NAME, RTYPE, return) +#define FORWARD0NR(NAME) FORWARD0_HELPER(NAME, void, NOARG) + +#define FORWARD1_HELPER(NAME, RTYPE, RKEYW, A1TYPE) \ RTYPE WTLCAPI NAME (A1TYPE a1) { \ typedef RTYPE (WTLCAPI * t)(A1TYPE); \ t f; \ LOAD(h) \ f = (t)GetProcAddress(h, #NAME); \ SYMCHECK(#NAME, f) \ - return f(a1); \ + RKEYW f(a1); \ } -#define FORWARD2(NAME, RTYPE, A1TYPE, A2TYPE) \ +#define FORWARD1(NAME, RTYPE, A1TYPE) FORWARD1_HELPER(NAME, RTYPE, return, A1TYPE) +#define FORWARD1NR(NAME, A1TYPE) FORWARD1_HELPER(NAME, void, NOARG, A1TYPE) + +#define FORWARD2_HELPER(NAME, RTYPE, RKEYW, A1TYPE, A2TYPE) \ RTYPE WTLCAPI NAME (A1TYPE a1, A2TYPE a2) { \ typedef RTYPE (WTLCAPI * t)(A1TYPE, A2TYPE); \ t f; \ LOAD(h) \ f = (t)GetProcAddress(h, #NAME); \ SYMCHECK(#NAME, f) \ - return f(a1, a2); \ + RKEYW f(a1, a2); \ } -#define FORWARD3(NAME, RTYPE, A1TYPE, A2TYPE, A3TYPE) \ +#define FORWARD2(NAME, RTYPE, A1TYPE, A2TYPE) FORWARD2_HELPER(NAME, RTYPE, return, A1TYPE, A2TYPE) +#define FORWARD2NR(NAME, A1TYPE, A2TYPE) FORWARD2_HELPER(NAME, void, NOARG, A1TYPE, A2TYPE) + +#define FORWARD3_HELPER(NAME, RTYPE, RKEYW, A1TYPE, A2TYPE, A3TYPE) \ RTYPE WTLCAPI NAME (A1TYPE a1, A2TYPE a2, A3TYPE a3) { \ typedef RTYPE (WTLCAPI * t)(A1TYPE, A2TYPE, A3TYPE); \ t f; \ LOAD(h) \ f = (t)GetProcAddress(h, #NAME); \ SYMCHECK(#NAME, f) \ - return f(a1, a2, a3); \ + RKEYW f(a1, a2, a3); \ } -#define FORWARD8(NAME, RTYPE, A1TYPE, A2TYPE, A3TYPE, A4TYPE, A5TYPE, A6TYPE, A7TYPE, A8TYPE) \ +#define FORWARD3(NAME, RTYPE, A1TYPE, A2TYPE, A3TYPE) FORWARD3_HELPER(NAME, RTYPE, return, A1TYPE, A2TYPE, A3TYPE) +#define FORWARD3NR(NAME, A1TYPE, A2TYPE, A3TYPE) FORWARD3_HELPER(NAME, void, NOARG, A1TYPE, A2TYPE, A3TYPE) + +#define FORWARD8_HELPER(NAME, RTYPE, RKEYW, A1TYPE, A2TYPE, A3TYPE, A4TYPE, A5TYPE, A6TYPE, A7TYPE, A8TYPE) \ RTYPE WTLCAPI NAME (A1TYPE a1, A2TYPE a2, A3TYPE a3, A4TYPE a4, A5TYPE a5, A6TYPE a6, A7TYPE a7, A8TYPE a8) { \ typedef RTYPE (WTLCAPI * t)(A1TYPE, A2TYPE, A3TYPE, A4TYPE, A5TYPE, A6TYPE, A7TYPE, A8TYPE); \ t f; \ LOAD(h) \ f = (t)GetProcAddress(h, #NAME); \ SYMCHECK(#NAME, f) \ - return f(a1, a2, a3, a4, a5, a6, a7, a8); \ + RKEYW f(a1, a2, a3, a4, a5, a6, a7, a8); \ } +#define FORWARD8(NAME, RTYPE, A1TYPE, A2TYPE, A3TYPE, A4TYPE, A5TYPE, A6TYPE, A7TYPE, A8TYPE) \ + FORWARD8_HELPER(NAME, RTYPE, return, A1TYPE, A2TYPE, A3TYPE, A4TYPE, A5TYPE, A6TYPE, A7TYPE, A8TYPE) +#define FORWARD8NR(NAME, A1TYPE, A2TYPE, A3TYPE, A4TYPE, A5TYPE, A6TYPE, A7TYPE, A8TYPE) \ + FORWARD8_HELPER(NAME, void, NOARG, A1TYPE, A2TYPE, A3TYPE, A4TYPE, A5TYPE, A6TYPE, A7TYPE, A8TYPE) FORWARD1(WTLC_Template_Create, WTLC_Template *, WTLC_TemplateType) -FORWARD1(WTLC_Template_Destroy, void, WTLC_Template *) -FORWARD2(WTLC_Template_setFirstLine, void, WTLC_Template *, LPCWSTR) -FORWARD2(WTLC_Template_setSecondLine, void, WTLC_Template *, LPCWSTR) -FORWARD2(WTLC_Template_setThirdLine, void, WTLC_Template *, LPCWSTR) -FORWARD3(WTLC_Template_setTextField, void, WTLC_Template *, LPCWSTR, WTLC_TextField) -FORWARD2(WTLC_Template_setAttributionText, void, WTLC_Template *, LPCWSTR) -FORWARD2(WTLC_Template_setImagePath, void, WTLC_Template *, LPCWSTR) -FORWARD2(WTLC_Template_setAudioSystemFile, void, WTLC_Template *, WTLC_AudioSystemFile) -FORWARD2(WTLC_Template_setAudioPath, void, WTLC_Template *, LPCWSTR) -FORWARD2(WTLC_Template_setAudioOption, void, WTLC_Template *, WTLC_AudioOption) -FORWARD2(WTLC_Template_setDuration, void, WTLC_Template *, WTLC_Duration) -FORWARD2(WTLC_Template_setExpiration, void, WTLC_Template *, INT64) -FORWARD2(WTLC_Template_setScenario, void, WTLC_Template *, WTLC_Scenario) -FORWARD2(WTLC_Template_addAction, void, WTLC_Template *, LPCWSTR) +FORWARD1NR(WTLC_Template_Destroy, WTLC_Template *) +FORWARD2NR(WTLC_Template_setFirstLine, WTLC_Template *, LPCWSTR) +FORWARD2NR(WTLC_Template_setSecondLine, WTLC_Template *, LPCWSTR) +FORWARD2NR(WTLC_Template_setThirdLine, WTLC_Template *, LPCWSTR) +FORWARD3NR(WTLC_Template_setTextField, WTLC_Template *, LPCWSTR, WTLC_TextField) +FORWARD2NR(WTLC_Template_setAttributionText, WTLC_Template *, LPCWSTR) +FORWARD2NR(WTLC_Template_setImagePath, WTLC_Template *, LPCWSTR) +FORWARD3NR(WTLC_Template_setImagePathWithCropHint, WTLC_Template *, LPCWSTR, WTLC_CropHint) +FORWARD3NR(WTLC_Template_setHeroImagePath, WTLC_Template *, LPCWSTR, BOOL) +FORWARD2NR(WTLC_Template_setAudioSystemFile, WTLC_Template *, WTLC_AudioSystemFile) +FORWARD2NR(WTLC_Template_setAudioPath, WTLC_Template *, LPCWSTR) +FORWARD2NR(WTLC_Template_setAudioOption, WTLC_Template *, WTLC_AudioOption) +FORWARD2NR(WTLC_Template_setDuration, WTLC_Template *, WTLC_Duration) +FORWARD2NR(WTLC_Template_setExpiration, WTLC_Template *, INT64) +FORWARD2NR(WTLC_Template_setScenario, WTLC_Template *, WTLC_Scenario) +FORWARD2NR(WTLC_Template_addAction, WTLC_Template *, LPCWSTR) FORWARD1(WTLC_Template_textFieldsCount, size_t, WTLC_Template *) FORWARD1(WTLC_Template_actionsCount, size_t, WTLC_Template *) FORWARD1(WTLC_Template_hasImage, BOOL, WTLC_Template *) +FORWARD1(WTLC_Template_hasHeroImage, BOOL, WTLC_Template *) FORWARD2(WTLC_Template_textField, LPCWSTR, WTLC_Template *, WTLC_TextField) FORWARD2(WTLC_Template_actionLabel, LPCWSTR, WTLC_Template *, size_t); FORWARD1(WTLC_Template_imagePath, LPCWSTR, WTLC_Template *); +FORWARD1(WTLC_Template_heroImagePath, LPCWSTR, WTLC_Template *); FORWARD1(WTLC_Template_audioPath, LPCWSTR, WTLC_Template *); FORWARD1(WTLC_Template_attributionText, LPCWSTR, WTLC_Template *); FORWARD1(WTLC_Template_scenario, LPCWSTR, WTLC_Template *); @@ -132,19 +154,23 @@ FORWARD1(WTLC_Template_expiration, INT64, WTLC_Template *); FORWARD1(WTLC_Template_type, WTLC_TemplateType, WTLC_Template *); FORWARD1(WTLC_Template_audioOption, WTLC_AudioOption, WTLC_Template *); FORWARD1(WTLC_Template_duration, WTLC_Duration, WTLC_Template *); +FORWARD1(WTLC_Template_isToastGeneric, BOOL, WTLC_Template *); +FORWARD1(WTLC_Template_isInlineHeroImage, BOOL, WTLC_Template *); +FORWARD1(WTLC_Template_isCropHintCircle, BOOL, WTLC_Template *); FORWARD0(WTLC_Instance_Create, WTLC_Instance *); -FORWARD1(WTLC_Instance_Destroy, void, WTLC_Instance *); +FORWARD1NR(WTLC_Instance_Destroy, WTLC_Instance *); FORWARD0(WTLC_isCompatible, BOOL); FORWARD0(WTLC_isSupportingModernFeatures, BOOL); +FORWARD0(WTLC_isWin10AnniversaryOrHigher, BOOL); FORWARD1(WTLC_strerror, LPCWSTR, WTLC_Error); FORWARD2(WTLC_initialize, BOOL, WTLC_Instance *, WTLC_Error *); FORWARD1(WTLC_isInitialized, BOOL, WTLC_Instance *); FORWARD2(WTLC_hideToast, BOOL, WTLC_Instance *, INT64); FORWARD8(WTLC_showToast, INT64, WTLC_Instance *, WTLC_Template *, void *, WTLC_CB_toastActivated, WTLC_CB_toastActivatedAction, WTLC_CB_toastDismissed, WTLC_CB_toastFailed, WTLC_Error *); -FORWARD1(WTLC_clear, void, WTLC_Instance *); +FORWARD1NR(WTLC_clear, WTLC_Instance *); FORWARD1(WTLC_createShortcut, WTLC_ShortcutResult, WTLC_Instance *); FORWARD1(WTLC_appName, LPCWSTR, WTLC_Instance *); FORWARD1(WTLC_appUserModelId, LPCWSTR, WTLC_Instance *); -FORWARD2(WTLC_setAppUserModelId, void, WTLC_Instance *, LPCWSTR); -FORWARD2(WTLC_setAppName, void, WTLC_Instance *, LPCWSTR); -FORWARD2(WTLC_setShortcutPolicy, void, WTLC_Instance *, WTLC_ShortcutPolicy); +FORWARD2NR(WTLC_setAppUserModelId, WTLC_Instance *, LPCWSTR); +FORWARD2NR(WTLC_setAppName, WTLC_Instance *, LPCWSTR); +FORWARD2NR(WTLC_setShortcutPolicy, WTLC_Instance *, WTLC_ShortcutPolicy);