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