1 /* * Copyright (C) 2016-2019 Mohammed Boujemaoui <mohabouje@gmail.com>
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining a copy of
4  * this software and associated documentation files (the "Software"), to deal in
5  * the Software without restriction, including without limitation the rights to
6  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7  * the Software, and to permit persons to whom the Software is furnished to do so,
8  * subject to the following conditions:
9  *
10  * The above copyright notice and this permission notice shall be included in all
11  * copies or substantial portions of the Software.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15  * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19  */
20 
21 #include "wintoastlib.h"
22 #include <memory>
23 #include <assert.h>
24 #include <unordered_map>
25 #include <array>
26 
27 #pragma comment(lib,"shlwapi")
28 #pragma comment(lib,"user32")
29 
30 #ifdef NDEBUG
31     #define DEBUG_MSG(str) do { } while ( false )
32 #else
33     #define DEBUG_MSG(str) do { std::wcout << str << std::endl; } while( false )
34 #endif
35 
36 #define DEFAULT_SHELL_LINKS_PATH	L"\\Microsoft\\Windows\\Start Menu\\Programs\\"
37 #define DEFAULT_LINK_FORMAT			L".lnk"
38 #define STATUS_SUCCESS (0x00000000)
39 
40 
41 // Quickstart: Handling toast activations from Win32 apps in Windows 10
42 // https://blogs.msdn.microsoft.com/tiles_and_toasts/2015/10/16/quickstart-handling-toast-activations-from-win32-apps-in-windows-10/
43 using namespace WinToastLib;
44 namespace DllImporter {
45 
46     // Function load a function from library
47     template <typename Function>
loadFunctionFromLibrary(HINSTANCE library,LPCSTR name,Function & func)48 	HRESULT loadFunctionFromLibrary(HINSTANCE library, LPCSTR name, Function &func) {
49 		if (!library) {
50 			return E_INVALIDARG;
51 		}
52         func = reinterpret_cast<Function>(GetProcAddress(library, name));
53         return (func != nullptr) ? S_OK : E_FAIL;
54     }
55 
56     typedef HRESULT(FAR STDAPICALLTYPE *f_SetCurrentProcessExplicitAppUserModelID)(__in PCWSTR AppID);
57     typedef HRESULT(FAR STDAPICALLTYPE *f_PropVariantToString)(_In_ REFPROPVARIANT propvar, _Out_writes_(cch) PWSTR psz, _In_ UINT cch);
58     typedef HRESULT(FAR STDAPICALLTYPE *f_RoGetActivationFactory)(_In_ HSTRING activatableClassId, _In_ REFIID iid, _COM_Outptr_ void ** factory);
59     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);
60     typedef PCWSTR(FAR STDAPICALLTYPE *f_WindowsGetStringRawBuffer)(_In_ HSTRING string, _Out_ UINT32 *length);
61     typedef HRESULT(FAR STDAPICALLTYPE *f_WindowsDeleteString)(_In_opt_ HSTRING string);
62 
63     static f_SetCurrentProcessExplicitAppUserModelID    SetCurrentProcessExplicitAppUserModelID;
64     static f_PropVariantToString                        PropVariantToString;
65     static f_RoGetActivationFactory                     RoGetActivationFactory;
66     static f_WindowsCreateStringReference               WindowsCreateStringReference;
67     static f_WindowsGetStringRawBuffer                  WindowsGetStringRawBuffer;
68     static f_WindowsDeleteString                        WindowsDeleteString;
69 
70 
71     template<class T>
_1_GetActivationFactory(_In_ HSTRING activatableClassId,_COM_Outptr_ T ** factory)72     _Check_return_ __inline HRESULT _1_GetActivationFactory(_In_ HSTRING activatableClassId, _COM_Outptr_ T** factory) {
73         return RoGetActivationFactory(activatableClassId, IID_INS_ARGS(factory));
74     }
75 
76     template<typename T>
Wrap_GetActivationFactory(_In_ HSTRING activatableClassId,_Inout_ Details::ComPtrRef<T> factory)77     inline HRESULT Wrap_GetActivationFactory(_In_ HSTRING activatableClassId, _Inout_ Details::ComPtrRef<T> factory) noexcept {
78         return _1_GetActivationFactory(activatableClassId, factory.ReleaseAndGetAddressOf());
79     }
80 
initialize()81     inline HRESULT initialize() {
82         HINSTANCE LibShell32 = LoadLibraryW(L"SHELL32.DLL");
83         HRESULT hr = loadFunctionFromLibrary(LibShell32, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID);
84         if (SUCCEEDED(hr)) {
85             HINSTANCE LibPropSys = LoadLibraryW(L"PROPSYS.DLL");
86             hr = loadFunctionFromLibrary(LibPropSys, "PropVariantToString", PropVariantToString);
87             if (SUCCEEDED(hr)) {
88                 HINSTANCE LibComBase = LoadLibraryW(L"COMBASE.DLL");
89                 const bool succeded = SUCCEEDED(loadFunctionFromLibrary(LibComBase, "RoGetActivationFactory", RoGetActivationFactory))
90 										&& SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsCreateStringReference", WindowsCreateStringReference))
91 										&& SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsGetStringRawBuffer", WindowsGetStringRawBuffer))
92 										&& SUCCEEDED(loadFunctionFromLibrary(LibComBase, "WindowsDeleteString", WindowsDeleteString));
93 				return succeded ? S_OK : E_FAIL;
94             }
95         }
96         return hr;
97     }
98 }
99 
100 class WinToastStringWrapper {
101 public:
WinToastStringWrapper(_In_reads_ (length)PCWSTR stringRef,_In_ UINT32 length)102     WinToastStringWrapper(_In_reads_(length) PCWSTR stringRef, _In_ UINT32 length) noexcept {
103         HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef, length, &_header, &_hstring);
104         if (!SUCCEEDED(hr)) {
105             RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
106         }
107     }
108 
WinToastStringWrapper(_In_ const std::wstring & stringRef)109     WinToastStringWrapper(_In_ const std::wstring &stringRef) noexcept {
110         HRESULT hr = DllImporter::WindowsCreateStringReference(stringRef.c_str(), static_cast<UINT32>(stringRef.length()), &_header, &_hstring);
111         if (FAILED(hr)) {
112             RaiseException(static_cast<DWORD>(STATUS_INVALID_PARAMETER), EXCEPTION_NONCONTINUABLE, 0, nullptr);
113         }
114     }
115 
~WinToastStringWrapper()116     ~WinToastStringWrapper() {
117         DllImporter::WindowsDeleteString(_hstring);
118     }
119 
Get() const120     inline HSTRING Get() const noexcept {
121         return _hstring;
122     }
123 private:
124     HSTRING _hstring;
125     HSTRING_HEADER _header;
126 
127 };
128 
129 class InternalDateTime : public IReference<DateTime> {
130 public:
Now()131     static INT64 Now() {
132         FILETIME now;
133         GetSystemTimeAsFileTime(&now);
134         return ((((INT64)now.dwHighDateTime) << 32) | now.dwLowDateTime);
135     }
136 
InternalDateTime(DateTime dateTime)137     InternalDateTime(DateTime dateTime) : _dateTime(dateTime) {}
138 
InternalDateTime(INT64 millisecondsFromNow)139     InternalDateTime(INT64 millisecondsFromNow) {
140         _dateTime.UniversalTime = Now() + millisecondsFromNow * 10000;
141     }
142 
143     virtual ~InternalDateTime() = default;
144 
operator INT64()145     operator INT64() {
146         return _dateTime.UniversalTime;
147     }
148 
get_Value(DateTime * dateTime)149     HRESULT STDMETHODCALLTYPE get_Value(DateTime *dateTime) {
150         *dateTime = _dateTime;
151         return S_OK;
152     }
153 
QueryInterface(const IID & riid,void ** ppvObject)154     HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObject) {
155         if (!ppvObject) {
156             return E_POINTER;
157         }
158         if (riid == __uuidof(IUnknown) || riid == __uuidof(IReference<DateTime>)) {
159             *ppvObject = static_cast<IUnknown*>(static_cast<IReference<DateTime>*>(this));
160             return S_OK;
161         }
162         return E_NOINTERFACE;
163     }
164 
Release()165     ULONG STDMETHODCALLTYPE Release() {
166         return 1;
167     }
168 
AddRef()169     ULONG STDMETHODCALLTYPE AddRef() {
170         return 2;
171     }
172 
GetIids(ULONG *,IID **)173     HRESULT STDMETHODCALLTYPE GetIids(ULONG*, IID**) {
174         return E_NOTIMPL;
175     }
176 
GetRuntimeClassName(HSTRING *)177     HRESULT STDMETHODCALLTYPE GetRuntimeClassName(HSTRING*) {
178         return E_NOTIMPL;
179     }
180 
GetTrustLevel(TrustLevel *)181     HRESULT STDMETHODCALLTYPE GetTrustLevel(TrustLevel*) {
182         return E_NOTIMPL;
183     }
184 
185 protected:
186     DateTime _dateTime;
187 };
188 
189 namespace Util {
190 
191     typedef LONG NTSTATUS, *PNTSTATUS;
192     typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
getRealOSVersion()193     inline RTL_OSVERSIONINFOW getRealOSVersion() {
194         HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
195         if (hMod) {
196             RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
197             if (fxPtr != nullptr) {
198                 RTL_OSVERSIONINFOW rovi = { 0 };
199                 rovi.dwOSVersionInfoSize = sizeof(rovi);
200                 if (STATUS_SUCCESS == fxPtr(&rovi)) {
201                     return rovi;
202                 }
203             }
204         }
205         RTL_OSVERSIONINFOW rovi = { 0 };
206         return rovi;
207     }
208 
defaultExecutablePath(_In_ WCHAR * path,_In_ DWORD nSize=MAX_PATH)209     inline HRESULT defaultExecutablePath(_In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
210         DWORD written = GetModuleFileNameExW(GetCurrentProcess(), nullptr, path, nSize);
211         DEBUG_MSG("Default executable path: " << path);
212         return (written > 0) ? S_OK : E_FAIL;
213     }
214 
215 
commonShellLinksDirectory(_In_ const WCHAR * baseEnv,_In_ WCHAR * path,_In_ DWORD nSize)216     inline HRESULT commonShellLinksDirectory(_In_ const WCHAR* baseEnv, _In_ WCHAR* path, _In_ DWORD nSize) {
217         DWORD written = GetEnvironmentVariableW(baseEnv, path, nSize);
218         HRESULT hr = written > 0 ? S_OK : E_INVALIDARG;
219         if (SUCCEEDED(hr)) {
220             errno_t result = wcscat_s(path, nSize, DEFAULT_SHELL_LINKS_PATH);
221             hr = (result == 0) ? S_OK : E_INVALIDARG;
222             DEBUG_MSG("Default shell link path: " << path);
223         }
224         return hr;
225     }
226 
commonShellLinkPath(_In_ const WCHAR * baseEnv,const std::wstring & appname,_In_ WCHAR * path,_In_ DWORD nSize)227     inline HRESULT commonShellLinkPath(_In_ const WCHAR* baseEnv, const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize) {
228         HRESULT hr = commonShellLinksDirectory(baseEnv, path, nSize);
229         if (SUCCEEDED(hr)) {
230             const std::wstring appLink(appname + DEFAULT_LINK_FORMAT);
231             errno_t result = wcscat_s(path, nSize, appLink.c_str());
232             hr = (result == 0) ? S_OK : E_INVALIDARG;
233             DEBUG_MSG("Default shell link file path: " << path);
234         }
235         return hr;
236     }
237 
defaultUserShellLinkPath(const std::wstring & appname,_In_ WCHAR * path,_In_ DWORD nSize=MAX_PATH)238     inline HRESULT defaultUserShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
239       return commonShellLinkPath(L"APPDATA", appname, path, nSize);
240     }
241 
defaultSystemShellLinkPath(const std::wstring & appname,_In_ WCHAR * path,_In_ DWORD nSize=MAX_PATH)242     inline HRESULT defaultSystemShellLinkPath(const std::wstring& appname, _In_ WCHAR* path, _In_ DWORD nSize = MAX_PATH) {
243       return commonShellLinkPath(L"PROGRAMDATA", appname, path, nSize);
244     }
245 
AsString(ComPtr<IXmlDocument> & xmlDocument)246     inline PCWSTR AsString(ComPtr<IXmlDocument> &xmlDocument) {
247         HSTRING xml;
248         ComPtr<IXmlNodeSerializer> ser;
249         HRESULT hr = xmlDocument.As<IXmlNodeSerializer>(&ser);
250         hr = ser->GetXml(&xml);
251         if (SUCCEEDED(hr))
252             return DllImporter::WindowsGetStringRawBuffer(xml, nullptr);
253         return nullptr;
254     }
255 
AsString(HSTRING hstring)256     inline PCWSTR AsString(HSTRING hstring) {
257         return DllImporter::WindowsGetStringRawBuffer(hstring, nullptr);
258     }
259 
setNodeStringValue(const std::wstring & string,IXmlNode * node,IXmlDocument * xml)260     inline HRESULT setNodeStringValue(const std::wstring& string, IXmlNode *node, IXmlDocument *xml) {
261         ComPtr<IXmlText> textNode;
262         HRESULT hr = xml->CreateTextNode( WinToastStringWrapper(string).Get(), &textNode);
263         if (SUCCEEDED(hr)) {
264             ComPtr<IXmlNode> stringNode;
265             hr = textNode.As(&stringNode);
266             if (SUCCEEDED(hr)) {
267                 ComPtr<IXmlNode> appendedChild;
268                 hr = node->AppendChild(stringNode.Get(), &appendedChild);
269             }
270         }
271         return hr;
272     }
273 
setEventHandlers(_In_ IToastNotification * notification,_In_ std::shared_ptr<IWinToastHandler> eventHandler,_In_ INT64 expirationTime)274     inline HRESULT setEventHandlers(_In_ IToastNotification* notification, _In_ std::shared_ptr<IWinToastHandler> eventHandler, _In_ INT64 expirationTime) {
275         EventRegistrationToken activatedToken, dismissedToken, failedToken;
276         HRESULT hr = notification->add_Activated(
277                     Callback < Implements < RuntimeClassFlags<ClassicCom>,
278                     ITypedEventHandler<ToastNotification*, IInspectable* >> >(
279                     [eventHandler](IToastNotification*, IInspectable* inspectable)
280                 {
281                     IToastActivatedEventArgs *activatedEventArgs;
282                     HRESULT hr = inspectable->QueryInterface(&activatedEventArgs);
283                     if (SUCCEEDED(hr)) {
284                         HSTRING argumentsHandle;
285                         hr = activatedEventArgs->get_Arguments(&argumentsHandle);
286                         if (SUCCEEDED(hr)) {
287                             PCWSTR arguments = Util::AsString(argumentsHandle);
288                             if (arguments && *arguments) {
289                                 eventHandler->toastActivated(static_cast<int>(wcstol(arguments, nullptr, 10)));
290                                 return S_OK;
291                             }
292                         }
293                     }
294                     eventHandler->toastActivated();
295                     return S_OK;
296                 }).Get(), &activatedToken);
297 
298         if (SUCCEEDED(hr)) {
299             hr = notification->add_Dismissed(Callback < Implements < RuntimeClassFlags<ClassicCom>,
300                      ITypedEventHandler<ToastNotification*, ToastDismissedEventArgs* >> >(
301                      [eventHandler, expirationTime](IToastNotification*, IToastDismissedEventArgs* e)
302                  {
303                      ToastDismissalReason reason;
304                      if (SUCCEEDED(e->get_Reason(&reason)))
305                      {
306                          if (reason == ToastDismissalReason_UserCanceled && expirationTime && InternalDateTime::Now() >= expirationTime)
307                             reason = ToastDismissalReason_TimedOut;
308                          eventHandler->toastDismissed(static_cast<IWinToastHandler::WinToastDismissalReason>(reason));
309                      }
310                      return S_OK;
311                  }).Get(), &dismissedToken);
312             if (SUCCEEDED(hr)) {
313                 hr = notification->add_Failed(Callback < Implements < RuntimeClassFlags<ClassicCom>,
314                     ITypedEventHandler<ToastNotification*, ToastFailedEventArgs* >> >(
315                     [eventHandler](IToastNotification*, IToastFailedEventArgs*)
316                 {
317                     eventHandler->toastFailed();
318                     return S_OK;
319                 }).Get(), &failedToken);
320             }
321         }
322         return hr;
323     }
324 
addAttribute(_In_ IXmlDocument * xml,const std::wstring & name,IXmlNamedNodeMap * attributeMap)325     inline HRESULT addAttribute(_In_ IXmlDocument *xml, const std::wstring &name, IXmlNamedNodeMap *attributeMap) {
326         ComPtr<ABI::Windows::Data::Xml::Dom::IXmlAttribute> srcAttribute;
327         HRESULT hr = xml->CreateAttribute(WinToastStringWrapper(name).Get(), &srcAttribute);
328         if (SUCCEEDED(hr)) {
329             ComPtr<IXmlNode> node;
330             hr = srcAttribute.As(&node);
331             if (SUCCEEDED(hr)) {
332                 ComPtr<IXmlNode> pNode;
333                 hr = attributeMap->SetNamedItem(node.Get(), &pNode);
334             }
335         }
336         return hr;
337     }
338 
createElement(_In_ IXmlDocument * xml,_In_ const std::wstring & root_node,_In_ const std::wstring & element_name,_In_ const std::vector<std::wstring> & attribute_names)339     inline HRESULT createElement(_In_ IXmlDocument *xml, _In_ const std::wstring& root_node, _In_ const std::wstring& element_name, _In_ const std::vector<std::wstring>& attribute_names) {
340         ComPtr<IXmlNodeList> rootList;
341         HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(root_node).Get(), &rootList);
342         if (SUCCEEDED(hr)) {
343             ComPtr<IXmlNode> root;
344             hr = rootList->Item(0, &root);
345             if (SUCCEEDED(hr)) {
346                 ComPtr<ABI::Windows::Data::Xml::Dom::IXmlElement> audioElement;
347                 hr = xml->CreateElement(WinToastStringWrapper(element_name).Get(), &audioElement);
348                 if (SUCCEEDED(hr)) {
349                     ComPtr<IXmlNode> audioNodeTmp;
350                     hr = audioElement.As(&audioNodeTmp);
351                     if (SUCCEEDED(hr)) {
352                         ComPtr<IXmlNode> audioNode;
353                         hr = root->AppendChild(audioNodeTmp.Get(), &audioNode);
354                         if (SUCCEEDED(hr)) {
355                             ComPtr<IXmlNamedNodeMap> attributes;
356                             hr = audioNode->get_Attributes(&attributes);
357                             if (SUCCEEDED(hr)) {
358                                 for (const auto& it : attribute_names) {
359                                     hr = addAttribute(xml, it, attributes.Get());
360                                 }
361                             }
362                         }
363                     }
364                 }
365             }
366         }
367         return hr;
368     }
369 }
370 
instance()371 WinToast* WinToast::instance() {
372     static WinToast instance;
373     return &instance;
374 }
375 
WinToast()376 WinToast::WinToast() :
377     _isInitialized(false),
378     _hasCoInitialized(false)
379 {
380 	if (!isCompatible()) {
381 		DEBUG_MSG(L"Warning: Your system is not compatible with this library ");
382 	}
383 }
384 
~WinToast()385 WinToast::~WinToast() {
386     if (_hasCoInitialized) {
387         CoUninitialize();
388     }
389 }
390 
setAppName(_In_ const std::wstring & appName)391 void WinToast::setAppName(_In_ const std::wstring& appName) {
392     _appName = appName;
393 }
394 
395 
setAppUserModelId(_In_ const std::wstring & aumi)396 void WinToast::setAppUserModelId(_In_ const std::wstring& aumi) {
397     _aumi = aumi;
398     DEBUG_MSG(L"Default App User Model Id: " << _aumi.c_str());
399 }
400 
setShortcutPolicy(_In_ ShortcutPolicy shortcutPolicy)401 void WinToast::setShortcutPolicy(_In_ ShortcutPolicy shortcutPolicy) {
402     _shortcutPolicy = shortcutPolicy;
403 }
404 
isCompatible()405 bool WinToast::isCompatible() {
406 	DllImporter::initialize();
407     return !((DllImporter::SetCurrentProcessExplicitAppUserModelID == nullptr)
408         || (DllImporter::PropVariantToString == nullptr)
409         || (DllImporter::RoGetActivationFactory == nullptr)
410         || (DllImporter::WindowsCreateStringReference == nullptr)
411         || (DllImporter::WindowsDeleteString == nullptr));
412 }
413 
isSupportingModernFeatures()414 bool WinToastLib::WinToast::isSupportingModernFeatures() {
415     constexpr auto MinimumSupportedVersion = 6;
416     return Util::getRealOSVersion().dwMajorVersion > MinimumSupportedVersion;
417 
418 }
configureAUMI(_In_ const std::wstring & companyName,_In_ const std::wstring & productName,_In_ const std::wstring & subProduct,_In_ const std::wstring & versionInformation)419 std::wstring WinToast::configureAUMI(_In_ const std::wstring &companyName,
420                                                _In_ const std::wstring &productName,
421                                                _In_ const std::wstring &subProduct,
422                                                _In_ const std::wstring &versionInformation)
423 {
424     std::wstring aumi = companyName;
425     aumi += L"." + productName;
426     if (subProduct.length() > 0) {
427         aumi += L"." + subProduct;
428         if (versionInformation.length() > 0) {
429             aumi += L"." + versionInformation;
430         }
431     }
432 
433     if (aumi.length() > SCHAR_MAX) {
434         DEBUG_MSG("Error: max size allowed for AUMI: 128 characters.");
435     }
436     return aumi;
437 }
438 
strerror(WinToastError error)439 const std::wstring& WinToast::strerror(WinToastError error) {
440     static const std::unordered_map<WinToastError, std::wstring> Labels = {
441         {WinToastError::NoError, L"No error. The process was executed correctly"},
442         {WinToastError::NotInitialized, L"The library has not been initialized"},
443         {WinToastError::SystemNotSupported, L"The OS does not support WinToast"},
444         {WinToastError::ShellLinkNotCreated, L"The library was not able to create a Shell Link for the app"},
445         {WinToastError::InvalidAppUserModelID, L"The AUMI is not a valid one"},
446         {WinToastError::InvalidParameters, L"The parameters used to configure the library are not valid normally because an invalid AUMI or App Name"},
447         {WinToastError::NotDisplayed, L"The toast was created correctly but WinToast was not able to display the toast"},
448         {WinToastError::UnknownError, L"Unknown error"}
449     };
450 
451     const auto iter = Labels.find(error);
452     assert(iter != Labels.end());
453     return iter->second;
454 }
455 
createShortcut()456 enum WinToast::ShortcutResult WinToast::createShortcut() {
457     if (_aumi.empty() || _appName.empty()) {
458         DEBUG_MSG(L"Error: App User Model Id or Appname is empty!");
459         return SHORTCUT_MISSING_PARAMETERS;
460     }
461 
462     if (!isCompatible()) {
463         DEBUG_MSG(L"Your OS is not compatible with this library! =(");
464         return SHORTCUT_INCOMPATIBLE_OS;
465     }
466 
467     if (!_hasCoInitialized) {
468         HRESULT initHr = CoInitializeEx(nullptr, COINIT::COINIT_MULTITHREADED);
469         if (initHr != RPC_E_CHANGED_MODE) {
470             if (FAILED(initHr) && initHr != S_FALSE) {
471                 DEBUG_MSG(L"Error on COM library initialization!");
472                 return SHORTCUT_COM_INIT_FAILURE;
473             }
474             else {
475                 _hasCoInitialized = true;
476             }
477         }
478     }
479 
480     bool wasChanged;
481     HRESULT hr = validateShellLinkHelper(wasChanged);
482     if (SUCCEEDED(hr))
483         return wasChanged ? SHORTCUT_WAS_CHANGED : SHORTCUT_UNCHANGED;
484 
485     hr = createShellLinkHelper();
486     return SUCCEEDED(hr) ? SHORTCUT_WAS_CREATED : SHORTCUT_CREATE_FAILED;
487 }
488 
initialize(_Out_ WinToastError * error)489 bool WinToast::initialize(_Out_ WinToastError* error) {
490     _isInitialized = false;
491     setError(error, WinToastError::NoError);
492 
493     if (!isCompatible()) {
494         setError(error, WinToastError::SystemNotSupported);
495         DEBUG_MSG(L"Error: system not supported.");
496         return false;
497     }
498 
499 
500     if (_aumi.empty() || _appName.empty()) {
501         setError(error, WinToastError::InvalidParameters);
502         DEBUG_MSG(L"Error while initializing, did you set up a valid AUMI and App name?");
503         return false;
504     }
505 
506     if (_shortcutPolicy != SHORTCUT_POLICY_IGNORE) {
507         if (createShortcut() < 0) {
508             setError(error, WinToastError::ShellLinkNotCreated);
509             DEBUG_MSG(L"Error while attaching the AUMI to the current proccess =(");
510             return false;
511         }
512     }
513 
514     if (FAILED(DllImporter::SetCurrentProcessExplicitAppUserModelID(_aumi.c_str()))) {
515         setError(error, WinToastError::InvalidAppUserModelID);
516         DEBUG_MSG(L"Error while attaching the AUMI to the current proccess =(");
517         return false;
518     }
519 
520     _isInitialized = true;
521     return _isInitialized;
522 }
523 
isInitialized() const524 bool WinToast::isInitialized() const {
525     return _isInitialized;
526 }
527 
appName() const528 const std::wstring& WinToast::appName() const {
529     return _appName;
530 }
531 
appUserModelId() const532 const std::wstring& WinToast::appUserModelId() const {
533     return _aumi;
534 }
535 
536 
validateShellLinkHelper(_Out_ bool & wasChanged)537 HRESULT	WinToast::validateShellLinkHelper(_Out_ bool& wasChanged) {
538 	WCHAR	path[MAX_PATH] = { L'\0' };
539     Util::defaultUserShellLinkPath(_appName, path);
540     // Check if the file exist
541     DWORD attr = GetFileAttributesW(path);
542     if (attr >= 0xFFFFFFF) {
543         // The shortcut may be in the system Start Menu.
544         WCHAR   systemPath[MAX_PATH] = { L'\0' };
545         Util::defaultSystemShellLinkPath(_appName, systemPath);
546         attr = GetFileAttributesW(systemPath);
547         if (attr >= 0xFFFFFFF) {
548             DEBUG_MSG("Error, shell link not found. Try to create a new one in: " << path);
549             return E_FAIL;
550         }
551         wcscpy(path, systemPath);
552     }
553 
554     // Let's load the file as shell link to validate.
555     // - Create a shell link
556     // - Create a persistant file
557     // - Load the path as data for the persistant file
558     // - Read the property AUMI and validate with the current
559     // - Review if AUMI is equal.
560     ComPtr<IShellLink> shellLink;
561     HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
562     if (SUCCEEDED(hr)) {
563         ComPtr<IPersistFile> persistFile;
564         hr = shellLink.As(&persistFile);
565         if (SUCCEEDED(hr)) {
566             hr = persistFile->Load(path, STGM_READ);
567             if (SUCCEEDED(hr)) {
568                 ComPtr<IPropertyStore> propertyStore;
569                 hr = shellLink.As(&propertyStore);
570                 if (SUCCEEDED(hr)) {
571                     PROPVARIANT appIdPropVar;
572                     hr = propertyStore->GetValue(PKEY_AppUserModel_ID, &appIdPropVar);
573                     if (SUCCEEDED(hr)) {
574                         WCHAR AUMI[MAX_PATH];
575                         hr = DllImporter::PropVariantToString(appIdPropVar, AUMI, MAX_PATH);
576                         wasChanged = false;
577                         if (FAILED(hr) || _aumi != AUMI) {
578                             if (_shortcutPolicy == SHORTCUT_POLICY_REQUIRE_CREATE) {
579                                 // AUMI Changed for the same app, let's update the current value! =)
580                                 wasChanged = true;
581                                 PropVariantClear(&appIdPropVar);
582                                 hr = InitPropVariantFromString(_aumi.c_str(), &appIdPropVar);
583                                 if (SUCCEEDED(hr)) {
584                                     hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
585                                     if (SUCCEEDED(hr)) {
586                                         hr = propertyStore->Commit();
587                                         if (SUCCEEDED(hr) && SUCCEEDED(persistFile->IsDirty())) {
588                                             hr = persistFile->Save(path, TRUE);
589                                         }
590                                     }
591                                 }
592                             } else {
593                                 // Not allowed to touch the shortcut to fix the AUMI
594                                 hr = E_FAIL;
595                             }
596                         }
597                         PropVariantClear(&appIdPropVar);
598                     }
599                 }
600             }
601         }
602     }
603     return hr;
604 }
605 
606 
607 
createShellLinkHelper()608 HRESULT	WinToast::createShellLinkHelper() {
609     if (_shortcutPolicy != SHORTCUT_POLICY_REQUIRE_CREATE) {
610       return E_FAIL;
611     }
612 
613 	WCHAR   exePath[MAX_PATH]{L'\0'};
614 	WCHAR	slPath[MAX_PATH]{L'\0'};
615     Util::defaultUserShellLinkPath(_appName, slPath);
616     Util::defaultExecutablePath(exePath);
617     ComPtr<IShellLinkW> shellLink;
618     HRESULT hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
619     if (SUCCEEDED(hr)) {
620         hr = shellLink->SetPath(exePath);
621         if (SUCCEEDED(hr)) {
622             hr = shellLink->SetArguments(L"");
623             if (SUCCEEDED(hr)) {
624                 hr = shellLink->SetWorkingDirectory(exePath);
625                 if (SUCCEEDED(hr)) {
626                     ComPtr<IPropertyStore> propertyStore;
627                     hr = shellLink.As(&propertyStore);
628                     if (SUCCEEDED(hr)) {
629                         PROPVARIANT appIdPropVar;
630                         hr = InitPropVariantFromString(_aumi.c_str(), &appIdPropVar);
631                         if (SUCCEEDED(hr)) {
632                             hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
633                             if (SUCCEEDED(hr)) {
634                                 hr = propertyStore->Commit();
635                                 if (SUCCEEDED(hr)) {
636                                     ComPtr<IPersistFile> persistFile;
637                                     hr = shellLink.As(&persistFile);
638                                     if (SUCCEEDED(hr)) {
639                                         hr = persistFile->Save(slPath, TRUE);
640                                     }
641                                 }
642                             }
643                             PropVariantClear(&appIdPropVar);
644                         }
645                     }
646                 }
647             }
648         }
649     }
650     return hr;
651 }
652 
showToast(_In_ const WinToastTemplate & toast,_In_ IWinToastHandler * handler,_Out_ WinToastError * error)653 INT64 WinToast::showToast(_In_ const WinToastTemplate& toast, _In_  IWinToastHandler* handler, _Out_ WinToastError* error)  {
654     setError(error, WinToastError::NoError);
655     INT64 id = -1;
656     if (!isInitialized()) {
657         setError(error, WinToastError::NotInitialized);
658         DEBUG_MSG("Error when launching the toast. WinToast is not initialized.");
659         return id;
660     }
661     if (!handler) {
662         setError(error, WinToastError::InvalidHandler);
663         DEBUG_MSG("Error when launching the toast. Handler cannot be nullptr.");
664         return id;
665     }
666 
667     ComPtr<IToastNotificationManagerStatics> notificationManager;
668     HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &notificationManager);
669     if (SUCCEEDED(hr)) {
670         ComPtr<IToastNotifier> notifier;
671         hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), &notifier);
672         if (SUCCEEDED(hr)) {
673             ComPtr<IToastNotificationFactory> notificationFactory;
674             hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &notificationFactory);
675             if (SUCCEEDED(hr)) {
676 				ComPtr<IXmlDocument> xmlDocument;
677 				HRESULT hr = notificationManager->GetTemplateContent(ToastTemplateType(toast.type()), &xmlDocument);
678                 if (SUCCEEDED(hr)) {
679                     for (std::size_t i = 0, fieldsCount = toast.textFieldsCount(); i < fieldsCount && SUCCEEDED(hr); i++) {
680                         hr = setTextFieldHelper(xmlDocument.Get(), toast.textField(WinToastTemplate::TextField(i)), i);
681                     }
682 
683                     // Modern feature are supported Windows > Windows 10
684                     if (SUCCEEDED(hr) && isSupportingModernFeatures()) {
685 
686                         // Note that we do this *after* using toast.textFieldsCount() to
687                         // iterate/fill the template's text fields, since we're adding yet another text field.
688                         if (SUCCEEDED(hr)
689                             && !toast.attributionText().empty()) {
690                             hr = setAttributionTextFieldHelper(xmlDocument.Get(), toast.attributionText());
691                         }
692 
693                         std::array<WCHAR, 12> buf;
694                         for (std::size_t i = 0, actionsCount = toast.actionsCount(); i < actionsCount && SUCCEEDED(hr); i++) {
695                             _snwprintf_s(buf.data(), buf.size(), _TRUNCATE, L"%zd", i);
696                             hr = addActionHelper(xmlDocument.Get(), toast.actionLabel(i), buf.data());
697                         }
698 
699                         if (SUCCEEDED(hr)) {
700                             hr = (toast.audioPath().empty() && toast.audioOption() == WinToastTemplate::AudioOption::Default)
701                                 ? hr : setAudioFieldHelper(xmlDocument.Get(), toast.audioPath(), toast.audioOption());
702                         }
703 
704                         if (SUCCEEDED(hr) && toast.duration() != WinToastTemplate::Duration::System) {
705                             hr = addDurationHelper(xmlDocument.Get(),
706                                 (toast.duration() == WinToastTemplate::Duration::Short) ? L"short" : L"long");
707                         }
708 
709                     } else {
710                         DEBUG_MSG("Modern features (Actions/Sounds/Attributes) not supported in this os version");
711                     }
712 
713                     if (SUCCEEDED(hr)) {
714                         hr = toast.hasImage() ? setImageFieldHelper(xmlDocument.Get(), toast.imagePath()) : hr;
715                         if (SUCCEEDED(hr)) {
716                             ComPtr<IToastNotification> notification;
717                             hr = notificationFactory->CreateToastNotification(xmlDocument.Get(), &notification);
718                             if (SUCCEEDED(hr)) {
719                                 INT64 expiration = 0, relativeExpiration = toast.expiration();
720                                 if (relativeExpiration > 0) {
721                                     InternalDateTime expirationDateTime(relativeExpiration);
722                                     expiration = expirationDateTime;
723                                     hr = notification->put_ExpirationTime(&expirationDateTime);
724                                 }
725 
726                                 if (SUCCEEDED(hr)) {
727                                     hr = Util::setEventHandlers(notification.Get(), std::shared_ptr<IWinToastHandler>(handler), expiration);
728                                     if (FAILED(hr)) {
729                                         setError(error, WinToastError::InvalidHandler);
730                                     }
731                                 }
732 
733                                 if (SUCCEEDED(hr)) {
734                                     GUID guid;
735                                     hr = CoCreateGuid(&guid);
736                                     if (SUCCEEDED(hr)) {
737                                         id = guid.Data1;
738                                         _buffer[id] = notification;
739                                         DEBUG_MSG("xml: " << Util::AsString(xmlDocument));
740                                         hr = notifier->Show(notification.Get());
741                                         if (FAILED(hr)) {
742                                             setError(error, WinToastError::NotDisplayed);
743                                         }
744                                     }
745                                 }
746                             }
747                         }
748                     }
749                 }
750             }
751         }
752     }
753     return FAILED(hr) ? -1 : id;
754 }
755 
notifier(_In_ bool * succeded) const756 ComPtr<IToastNotifier> WinToast::notifier(_In_ bool* succeded) const  {
757 	ComPtr<IToastNotificationManagerStatics> notificationManager;
758 	ComPtr<IToastNotifier> notifier;
759 	HRESULT hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &notificationManager);
760 	if (SUCCEEDED(hr)) {
761 		hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), &notifier);
762 	}
763 	*succeded = SUCCEEDED(hr);
764 	return notifier;
765 }
766 
hideToast(_In_ INT64 id)767 bool WinToast::hideToast(_In_ INT64 id) {
768     if (!isInitialized()) {
769         DEBUG_MSG("Error when hiding the toast. WinToast is not initialized.");
770         return false;
771     }
772 
773     if (_buffer.find(id) != _buffer.end()) {
774         auto succeded = false;
775         auto notify = notifier(&succeded);
776 		if (succeded) {
777             auto result = notify->Hide(_buffer[id].Get());
778             _buffer.erase(id);
779             return SUCCEEDED(result);
780 		}
781 	}
782     return false;
783 }
784 
clear()785 void WinToast::clear() {
786     auto succeded = false;
787     auto notify = notifier(&succeded);
788 	if (succeded) {
789 		auto end = _buffer.end();
790 		for (auto it = _buffer.begin(); it != end; ++it) {
791 			notify->Hide(it->second.Get());
792 		}
793         _buffer.clear();
794 	}
795 }
796 
797 //
798 // Available as of Windows 10 Anniversary Update
799 // Ref: https://docs.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts
800 //
801 // NOTE: This will add a new text field, so be aware when iterating over
802 //       the toast's text fields or getting a count of them.
803 //
setAttributionTextFieldHelper(_In_ IXmlDocument * xml,_In_ const std::wstring & text)804 HRESULT WinToast::setAttributionTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text) {
805     Util::createElement(xml, L"binding", L"text", { L"placement" });
806     ComPtr<IXmlNodeList> nodeList;
807     HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList);
808     if (SUCCEEDED(hr)) {
809         UINT32 nodeListLength;
810         hr = nodeList->get_Length(&nodeListLength);
811         if (SUCCEEDED(hr)) {
812             for (UINT32 i = 0; i < nodeListLength; i++) {
813                 ComPtr<IXmlNode> textNode;
814                 hr = nodeList->Item(i, &textNode);
815                 if (SUCCEEDED(hr)) {
816                     ComPtr<IXmlNamedNodeMap> attributes;
817                     hr = textNode->get_Attributes(&attributes);
818                     if (SUCCEEDED(hr)) {
819                         ComPtr<IXmlNode> editedNode;
820                         if (SUCCEEDED(hr)) {
821                             hr = attributes->GetNamedItem(WinToastStringWrapper(L"placement").Get(), &editedNode);
822                             if (FAILED(hr) || !editedNode) {
823                                 continue;
824                             }
825                             hr = Util::setNodeStringValue(L"attribution", editedNode.Get(), xml);
826                             if (SUCCEEDED(hr)) {
827                                 return setTextFieldHelper(xml, text, i);
828                             }
829                         }
830                     }
831                 }
832             }
833         }
834     }
835     return hr;
836 }
837 
addDurationHelper(_In_ IXmlDocument * xml,_In_ const std::wstring & duration)838 HRESULT WinToast::addDurationHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& duration) {
839     ComPtr<IXmlNodeList> nodeList;
840     HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList);
841     if (SUCCEEDED(hr)) {
842         UINT32 length;
843         hr = nodeList->get_Length(&length);
844         if (SUCCEEDED(hr)) {
845             ComPtr<IXmlNode> toastNode;
846             hr = nodeList->Item(0, &toastNode);
847             if (SUCCEEDED(hr)) {
848                 ComPtr<IXmlElement> toastElement;
849                 hr = toastNode.As(&toastElement);
850                 if (SUCCEEDED(hr)) {
851                     hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(),
852                                                     WinToastStringWrapper(duration).Get());
853                 }
854             }
855         }
856     }
857     return hr;
858 }
859 
setTextFieldHelper(_In_ IXmlDocument * xml,_In_ const std::wstring & text,_In_ UINT32 pos)860 HRESULT WinToast::setTextFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& text, _In_ UINT32 pos) {
861     ComPtr<IXmlNodeList> nodeList;
862     HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"text").Get(), &nodeList);
863     if (SUCCEEDED(hr)) {
864         ComPtr<IXmlNode> node;
865         hr = nodeList->Item(pos, &node);
866         if (SUCCEEDED(hr)) {
867             hr = Util::setNodeStringValue(text, node.Get(), xml);
868         }
869     }
870     return hr;
871 }
872 
873 
setImageFieldHelper(_In_ IXmlDocument * xml,_In_ const std::wstring & path)874 HRESULT WinToast::setImageFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path)  {
875     assert(path.size() < MAX_PATH);
876 
877     wchar_t imagePath[MAX_PATH] = L"file:///";
878     HRESULT hr = StringCchCatW(imagePath, MAX_PATH, path.c_str());
879     if (SUCCEEDED(hr)) {
880         ComPtr<IXmlNodeList> nodeList;
881         HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"image").Get(), &nodeList);
882         if (SUCCEEDED(hr)) {
883             ComPtr<IXmlNode> node;
884             hr = nodeList->Item(0, &node);
885             if (SUCCEEDED(hr))  {
886                 ComPtr<IXmlNamedNodeMap> attributes;
887                 hr = node->get_Attributes(&attributes);
888                 if (SUCCEEDED(hr)) {
889                     ComPtr<IXmlNode> editedNode;
890                     hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode);
891                     if (SUCCEEDED(hr)) {
892                         Util::setNodeStringValue(imagePath, editedNode.Get(), xml);
893                     }
894                 }
895             }
896         }
897     }
898     return hr;
899 }
900 
setAudioFieldHelper(_In_ IXmlDocument * xml,_In_ const std::wstring & path,_In_opt_ WinToastTemplate::AudioOption option)901 HRESULT WinToast::setAudioFieldHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& path, _In_opt_ WinToastTemplate::AudioOption option) {
902     std::vector<std::wstring> attrs;
903     if (!path.empty()) attrs.push_back(L"src");
904     if (option == WinToastTemplate::AudioOption::Loop) attrs.push_back(L"loop");
905     if (option == WinToastTemplate::AudioOption::Silent) attrs.push_back(L"silent");
906     Util::createElement(xml, L"toast", L"audio", attrs);
907 
908     ComPtr<IXmlNodeList> nodeList;
909     HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"audio").Get(), &nodeList);
910     if (SUCCEEDED(hr)) {
911         ComPtr<IXmlNode> node;
912         hr = nodeList->Item(0, &node);
913         if (SUCCEEDED(hr)) {
914             ComPtr<IXmlNamedNodeMap> attributes;
915             hr = node->get_Attributes(&attributes);
916             if (SUCCEEDED(hr)) {
917                 ComPtr<IXmlNode> editedNode;
918                 if (!path.empty()) {
919                     if (SUCCEEDED(hr)) {
920                         hr = attributes->GetNamedItem(WinToastStringWrapper(L"src").Get(), &editedNode);
921                         if (SUCCEEDED(hr)) {
922                             hr = Util::setNodeStringValue(path, editedNode.Get(), xml);
923                         }
924                     }
925                 }
926 
927                 if (SUCCEEDED(hr)) {
928                     switch (option) {
929                     case WinToastTemplate::AudioOption::Loop:
930                         hr = attributes->GetNamedItem(WinToastStringWrapper(L"loop").Get(), &editedNode);
931                         if (SUCCEEDED(hr)) {
932                             hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml);
933                         }
934                         break;
935                     case WinToastTemplate::AudioOption::Silent:
936                         hr = attributes->GetNamedItem(WinToastStringWrapper(L"silent").Get(), &editedNode);
937                         if (SUCCEEDED(hr)) {
938                             hr = Util::setNodeStringValue(L"true", editedNode.Get(), xml);
939                         }
940                     default:
941                         break;
942                     }
943                 }
944             }
945         }
946     }
947     return hr;
948 }
949 
addActionHelper(_In_ IXmlDocument * xml,_In_ const std::wstring & content,_In_ const std::wstring & arguments)950 HRESULT WinToast::addActionHelper(_In_ IXmlDocument *xml, _In_ const std::wstring& content, _In_ const std::wstring& arguments) {
951 	ComPtr<IXmlNodeList> nodeList;
952 	HRESULT hr = xml->GetElementsByTagName(WinToastStringWrapper(L"actions").Get(), &nodeList);
953     if (SUCCEEDED(hr)) {
954         UINT32 length;
955         hr = nodeList->get_Length(&length);
956         if (SUCCEEDED(hr)) {
957             ComPtr<IXmlNode> actionsNode;
958             if (length > 0) {
959                 hr = nodeList->Item(0, &actionsNode);
960             } else {
961                 hr = xml->GetElementsByTagName(WinToastStringWrapper(L"toast").Get(), &nodeList);
962                 if (SUCCEEDED(hr)) {
963                     hr = nodeList->get_Length(&length);
964                     if (SUCCEEDED(hr)) {
965                         ComPtr<IXmlNode> toastNode;
966                         hr = nodeList->Item(0, &toastNode);
967                         if (SUCCEEDED(hr)) {
968                             ComPtr<IXmlElement> toastElement;
969                             hr = toastNode.As(&toastElement);
970                             if (SUCCEEDED(hr))
971                                         hr = toastElement->SetAttribute(WinToastStringWrapper(L"template").Get(), WinToastStringWrapper(L"ToastGeneric").Get());
972                             if (SUCCEEDED(hr))
973                                         hr = toastElement->SetAttribute(WinToastStringWrapper(L"duration").Get(), WinToastStringWrapper(L"long").Get());
974                             if (SUCCEEDED(hr)) {
975                                 ComPtr<IXmlElement> actionsElement;
976                                 hr = xml->CreateElement(WinToastStringWrapper(L"actions").Get(), &actionsElement);
977                                 if (SUCCEEDED(hr)) {
978                                     hr = actionsElement.As(&actionsNode);
979                                     if (SUCCEEDED(hr)) {
980                                         ComPtr<IXmlNode> appendedChild;
981                                         hr = toastNode->AppendChild(actionsNode.Get(), &appendedChild);
982                                     }
983                                 }
984                             }
985                         }
986                     }
987                 }
988             }
989             if (SUCCEEDED(hr)) {
990                 ComPtr<IXmlElement> actionElement;
991                 hr = xml->CreateElement(WinToastStringWrapper(L"action").Get(), &actionElement);
992                 if (SUCCEEDED(hr))
993                     hr = actionElement->SetAttribute(WinToastStringWrapper(L"content").Get(), WinToastStringWrapper(content).Get());
994                 if (SUCCEEDED(hr))
995                     hr = actionElement->SetAttribute(WinToastStringWrapper(L"arguments").Get(), WinToastStringWrapper(arguments).Get());
996                 if (SUCCEEDED(hr)) {
997                     ComPtr<IXmlNode> actionNode;
998                     hr = actionElement.As(&actionNode);
999                     if (SUCCEEDED(hr)) {
1000                         ComPtr<IXmlNode> appendedChild;
1001                         hr = actionsNode->AppendChild(actionNode.Get(), &appendedChild);
1002                     }
1003                 }
1004             }
1005         }
1006     }
1007     return hr;
1008 }
1009 
setError(_Out_ WinToastError * error,_In_ WinToastError value)1010 void WinToast::setError(_Out_ WinToastError* error, _In_ WinToastError value) {
1011     if (error) {
1012         *error = value;
1013     }
1014 }
1015 
WinToastTemplate(_In_ WinToastTemplateType type)1016 WinToastTemplate::WinToastTemplate(_In_ WinToastTemplateType type) : _type(type) {
1017     static constexpr std::size_t TextFieldsCount[] = { 1, 2, 2, 3, 1, 2, 2, 3};
1018     _textFields = std::vector<std::wstring>(TextFieldsCount[type], L"");
1019 }
1020 
~WinToastTemplate()1021 WinToastTemplate::~WinToastTemplate() {
1022     _textFields.clear();
1023 }
1024 
setTextField(_In_ const std::wstring & txt,_In_ WinToastTemplate::TextField pos)1025 void WinToastTemplate::setTextField(_In_ const std::wstring& txt, _In_ WinToastTemplate::TextField pos) {
1026     const auto position = static_cast<std::size_t>(pos);
1027     assert(position < _textFields.size());
1028     _textFields[position] = txt;
1029 }
1030 
setImagePath(_In_ const std::wstring & imgPath)1031 void WinToastTemplate::setImagePath(_In_ const std::wstring& imgPath) {
1032     _imagePath = imgPath;
1033 }
1034 
setAudioPath(_In_ const std::wstring & audioPath)1035 void WinToastTemplate::setAudioPath(_In_ const std::wstring& audioPath) {
1036     _audioPath = audioPath;
1037 }
1038 
setAudioPath(_In_ AudioSystemFile file)1039 void WinToastTemplate::setAudioPath(_In_ AudioSystemFile file) {
1040     static const std::unordered_map<AudioSystemFile, std::wstring> Files = {
1041         {AudioSystemFile::DefaultSound, L"ms-winsoundevent:Notification.Default"},
1042         {AudioSystemFile::IM, L"ms-winsoundevent:Notification.IM"},
1043         {AudioSystemFile::Mail, L"ms-winsoundevent:Notification.Mail"},
1044         {AudioSystemFile::Reminder, L"ms-winsoundevent:Notification.Reminder"},
1045         {AudioSystemFile::SMS, L"ms-winsoundevent:Notification.SMS"},
1046         {AudioSystemFile::Alarm, L"ms-winsoundevent:Notification.Looping.Alarm"},
1047         {AudioSystemFile::Alarm2, L"ms-winsoundevent:Notification.Looping.Alarm2"},
1048         {AudioSystemFile::Alarm3, L"ms-winsoundevent:Notification.Looping.Alarm3"},
1049         {AudioSystemFile::Alarm4, L"ms-winsoundevent:Notification.Looping.Alarm4"},
1050         {AudioSystemFile::Alarm5, L"ms-winsoundevent:Notification.Looping.Alarm5"},
1051         {AudioSystemFile::Alarm6, L"ms-winsoundevent:Notification.Looping.Alarm6"},
1052         {AudioSystemFile::Alarm7, L"ms-winsoundevent:Notification.Looping.Alarm7"},
1053         {AudioSystemFile::Alarm8, L"ms-winsoundevent:Notification.Looping.Alarm8"},
1054         {AudioSystemFile::Alarm9, L"ms-winsoundevent:Notification.Looping.Alarm9"},
1055         {AudioSystemFile::Alarm10, L"ms-winsoundevent:Notification.Looping.Alarm10"},
1056         {AudioSystemFile::Call, L"ms-winsoundevent:Notification.Looping.Call"},
1057         {AudioSystemFile::Call1, L"ms-winsoundevent:Notification.Looping.Call1"},
1058         {AudioSystemFile::Call2, L"ms-winsoundevent:Notification.Looping.Call2"},
1059         {AudioSystemFile::Call3, L"ms-winsoundevent:Notification.Looping.Call3"},
1060         {AudioSystemFile::Call4, L"ms-winsoundevent:Notification.Looping.Call4"},
1061         {AudioSystemFile::Call5, L"ms-winsoundevent:Notification.Looping.Call5"},
1062         {AudioSystemFile::Call6, L"ms-winsoundevent:Notification.Looping.Call6"},
1063         {AudioSystemFile::Call7, L"ms-winsoundevent:Notification.Looping.Call7"},
1064         {AudioSystemFile::Call8, L"ms-winsoundevent:Notification.Looping.Call8"},
1065         {AudioSystemFile::Call9, L"ms-winsoundevent:Notification.Looping.Call9"},
1066         {AudioSystemFile::Call10, L"ms-winsoundevent:Notification.Looping.Call10"},
1067     };
1068     const auto iter = Files.find(file);
1069     assert(iter != Files.end());
1070     _audioPath = iter->second;
1071 }
1072 
setAudioOption(_In_ WinToastTemplate::AudioOption audioOption)1073 void WinToastTemplate::setAudioOption(_In_ WinToastTemplate::AudioOption audioOption) {
1074     _audioOption = audioOption;
1075 }
1076 
setFirstLine(const std::wstring & text)1077 void WinToastTemplate::setFirstLine(const std::wstring &text) {
1078     setTextField(text, WinToastTemplate::FirstLine);
1079 }
1080 
setSecondLine(const std::wstring & text)1081 void WinToastTemplate::setSecondLine(const std::wstring &text) {
1082     setTextField(text, WinToastTemplate::SecondLine);
1083 }
1084 
setThirdLine(const std::wstring & text)1085 void WinToastTemplate::setThirdLine(const std::wstring &text) {
1086     setTextField(text, WinToastTemplate::ThirdLine);
1087 }
1088 
setDuration(_In_ Duration duration)1089 void WinToastTemplate::setDuration(_In_ Duration duration) {
1090     _duration = duration;
1091 }
1092 
setExpiration(_In_ INT64 millisecondsFromNow)1093 void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow) {
1094     _expiration = millisecondsFromNow;
1095 }
1096 
setAttributionText(_In_ const std::wstring & attributionText)1097 void WinToastTemplate::setAttributionText(_In_ const std::wstring& attributionText) {
1098     _attributionText = attributionText;
1099 }
1100 
addAction(_In_ const std::wstring & label)1101 void WinToastTemplate::addAction(_In_ const std::wstring & label) {
1102 	_actions.push_back(label);
1103 }
1104 
textFieldsCount() const1105 std::size_t WinToastTemplate::textFieldsCount() const {
1106     return _textFields.size();
1107 }
1108 
actionsCount() const1109 std::size_t WinToastTemplate::actionsCount() const {
1110     return _actions.size();
1111 }
1112 
hasImage() const1113 bool WinToastTemplate::hasImage() const {
1114     return _type <  WinToastTemplateType::Text01;
1115 }
1116 
textFields() const1117 const std::vector<std::wstring>& WinToastTemplate::textFields() const {
1118     return _textFields;
1119 }
1120 
textField(_In_ TextField pos) const1121 const std::wstring& WinToastTemplate::textField(_In_ TextField pos) const {
1122     const auto position = static_cast<std::size_t>(pos);
1123     assert(position < _textFields.size());
1124     return _textFields[position];
1125 }
1126 
actionLabel(_In_ std::size_t position) const1127 const std::wstring& WinToastTemplate::actionLabel(_In_ std::size_t position) const {
1128     assert(position < _actions.size());
1129     return _actions[position];
1130 }
1131 
imagePath() const1132 const std::wstring& WinToastTemplate::imagePath() const {
1133     return _imagePath;
1134 }
1135 
audioPath() const1136 const std::wstring& WinToastTemplate::audioPath() const {
1137     return _audioPath;
1138 }
1139 
attributionText() const1140 const std::wstring& WinToastTemplate::attributionText() const {
1141     return _attributionText;
1142 }
1143 
expiration() const1144 INT64 WinToastTemplate::expiration() const {
1145     return _expiration;
1146 }
1147 
type() const1148 WinToastTemplate::WinToastTemplateType WinToastTemplate::type() const {
1149     return _type;
1150 }
1151 
audioOption() const1152 WinToastTemplate::AudioOption WinToastTemplate::audioOption() const {
1153     return _audioOption;
1154 }
1155 
duration() const1156 WinToastTemplate::Duration WinToastTemplate::duration() const {
1157     return _duration;
1158 }
1159