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(), ¬ificationManager);
597 if (SUCCEEDED(hr)) {
598 ComPtr<IToastNotifier> notifier;
599 hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), ¬ifier);
600 if (SUCCEEDED(hr)) {
601 ComPtr<IToastNotificationFactory> notificationFactory;
602 hr = DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), ¬ificationFactory);
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(), ¬ification);
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(), ¬ificationManager);
690 if (SUCCEEDED(hr)) {
691 hr = notificationManager->CreateToastNotifierWithId(WinToastStringWrapper(_aumi).Get(), ¬ifier);
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