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(), ¬ificationManager);
669 if (SUCCEEDED(hr)) {
670 ComPtr<IToastNotifier> notifier;
671 hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), ¬ifier);
672 if (SUCCEEDED(hr)) {
673 ComPtr<IToastNotificationFactory> notificationFactory;
674 hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), ¬ificationFactory);
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(), ¬ification);
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(), ¬ificationManager);
760 if (SUCCEEDED(hr)) {
761 hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), ¬ifier);
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