1 /* * Copyright (C) 2016-2019 Mohammed Boujemaoui <mohabouje@gmail.com>
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:
10 * The above copyright notice and this permission notice shall be included in all
11 * copies or substantial portions of the Software.
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.
21 #include "wintoastlib.h"
24 #include <unordered_map>
27 #pragma comment(lib,"shlwapi")
28 #pragma comment(lib,"user32")
31 #define DEBUG_MSG(str) do { } while ( false )
33 #define DEBUG_MSG(str) do { std::wcout << str << std::endl; } while( false )
36 #define DEFAULT_SHELL_LINKS_PATH L"\\Microsoft\\Windows\\Start Menu\\Programs\\"
37 #define DEFAULT_LINK_FORMAT L".lnk"
38 #define STATUS_SUCCESS (0x00000000)
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
{
46 // Function load a function from library
47 template <typename Function
>
48 HRESULT
loadFunctionFromLibrary(HINSTANCE library
, LPCSTR name
, Function
&func
) {
52 func
= reinterpret_cast<Function
>(GetProcAddress(library
, name
));
53 return (func
!= nullptr) ? S_OK
: E_FAIL
;
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
);
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
;
72 _Check_return_ __inline HRESULT
_1_GetActivationFactory(_In_ HSTRING activatableClassId
, _COM_Outptr_ T
** factory
) {
73 return RoGetActivationFactory(activatableClassId
, IID_INS_ARGS(factory
));
77 inline HRESULT
Wrap_GetActivationFactory(_In_ HSTRING activatableClassId
, _Inout_
Details::ComPtrRef
<T
> factory
) noexcept
{
78 return _1_GetActivationFactory(activatableClassId
, factory
.ReleaseAndGetAddressOf());
81 inline HRESULT
initialize() {
82 HINSTANCE LibShell32
= LoadLibraryW(L
"SHELL32.DLL");
83 HRESULT hr
= loadFunctionFromLibrary(LibShell32
, "SetCurrentProcessExplicitAppUserModelID", SetCurrentProcessExplicitAppUserModelID
);
85 HINSTANCE LibPropSys
= LoadLibraryW(L
"PROPSYS.DLL");
86 hr
= loadFunctionFromLibrary(LibPropSys
, "PropVariantToString", PropVariantToString
);
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
;
100 class WinToastStringWrapper
{
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);
109 WinToastStringWrapper(_In_
const std::wstring
&stringRef
) noexcept
{
110 HRESULT hr
= DllImporter::WindowsCreateStringReference(stringRef
.c_str(), static_cast<UINT32
>(stringRef
.length()), &_header
, &_hstring
);
112 RaiseException(static_cast<DWORD
>(STATUS_INVALID_PARAMETER
), EXCEPTION_NONCONTINUABLE
, 0, nullptr);
116 ~WinToastStringWrapper() {
117 DllImporter::WindowsDeleteString(_hstring
);
120 inline HSTRING
Get() const noexcept
{
125 HSTRING_HEADER _header
;
129 class InternalDateTime
: public IReference
<DateTime
> {
133 GetSystemTimeAsFileTime(&now
);
134 return ((((INT64
)now
.dwHighDateTime
) << 32) | now
.dwLowDateTime
);
137 InternalDateTime(DateTime dateTime
) : _dateTime(dateTime
) {}
139 InternalDateTime(INT64 millisecondsFromNow
) {
140 _dateTime
.UniversalTime
= Now() + millisecondsFromNow
* 10000;
143 virtual ~InternalDateTime() = default;
146 return _dateTime
.UniversalTime
;
149 HRESULT STDMETHODCALLTYPE
get_Value(DateTime
*dateTime
) {
150 *dateTime
= _dateTime
;
154 HRESULT STDMETHODCALLTYPE
QueryInterface(const IID
& riid
, void** ppvObject
) {
158 if (riid
== __uuidof(IUnknown
) || riid
== __uuidof(IReference
<DateTime
>)) {
159 *ppvObject
= static_cast<IUnknown
*>(static_cast<IReference
<DateTime
>*>(this));
162 return E_NOINTERFACE
;
165 ULONG STDMETHODCALLTYPE
Release() {
169 ULONG STDMETHODCALLTYPE
AddRef() {
173 HRESULT STDMETHODCALLTYPE
GetIids(ULONG
*, IID
**) {
177 HRESULT STDMETHODCALLTYPE
GetRuntimeClassName(HSTRING
*) {
181 HRESULT STDMETHODCALLTYPE
GetTrustLevel(TrustLevel
*) {
191 typedef LONG NTSTATUS
, *PNTSTATUS
;
192 typedef NTSTATUS(WINAPI
* RtlGetVersionPtr
)(PRTL_OSVERSIONINFOW
);
193 inline RTL_OSVERSIONINFOW
getRealOSVersion() {
194 HMODULE hMod
= ::GetModuleHandleW(L
"ntdll.dll");
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
)) {
205 RTL_OSVERSIONINFOW rovi
= { 0 };
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
;
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
;
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
);
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
);
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
);
238 inline HRESULT
defaultUserShellLinkPath(const std::wstring
& appname
, _In_ WCHAR
* path
, _In_ DWORD nSize
= MAX_PATH
) {
239 return commonShellLinkPath(L
"APPDATA", appname
, path
, nSize
);
242 inline HRESULT
defaultSystemShellLinkPath(const std::wstring
& appname
, _In_ WCHAR
* path
, _In_ DWORD nSize
= MAX_PATH
) {
243 return commonShellLinkPath(L
"PROGRAMDATA", appname
, path
, nSize
);
246 inline PCWSTR
AsString(ComPtr
<IXmlDocument
> &xmlDocument
) {
248 ComPtr
<IXmlNodeSerializer
> ser
;
249 HRESULT hr
= xmlDocument
.As
<IXmlNodeSerializer
>(&ser
);
250 hr
= ser
->GetXml(&xml
);
252 return DllImporter::WindowsGetStringRawBuffer(xml
, nullptr);
256 inline PCWSTR
AsString(HSTRING hstring
) {
257 return DllImporter::WindowsGetStringRawBuffer(hstring
, nullptr);
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
);
264 ComPtr
<IXmlNode
> stringNode
;
265 hr
= textNode
.As(&stringNode
);
267 ComPtr
<IXmlNode
> appendedChild
;
268 hr
= node
->AppendChild(stringNode
.Get(), &appendedChild
);
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
)
281 IToastActivatedEventArgs
*activatedEventArgs
;
282 HRESULT hr
= inspectable
->QueryInterface(&activatedEventArgs
);
284 HSTRING argumentsHandle
;
285 hr
= activatedEventArgs
->get_Arguments(&argumentsHandle
);
287 PCWSTR arguments
= Util::AsString(argumentsHandle
);
288 if (arguments
&& *arguments
) {
289 eventHandler
->toastActivated(static_cast<int>(wcstol(arguments
, nullptr, 10)));
294 eventHandler
->toastActivated();
296 }).Get(), &activatedToken
);
299 hr
= notification
->add_Dismissed(Callback
< Implements
< RuntimeClassFlags
<ClassicCom
>,
300 ITypedEventHandler
<ToastNotification
*, ToastDismissedEventArgs
* >> >(
301 [eventHandler
, expirationTime
](IToastNotification
*, IToastDismissedEventArgs
* e
)
303 ToastDismissalReason reason
;
304 if (SUCCEEDED(e
->get_Reason(&reason
)))
306 if (reason
== ToastDismissalReason_UserCanceled
&& expirationTime
&& InternalDateTime::Now() >= expirationTime
)
307 reason
= ToastDismissalReason_TimedOut
;
308 eventHandler
->toastDismissed(static_cast<IWinToastHandler::WinToastDismissalReason
>(reason
));
311 }).Get(), &dismissedToken
);
313 hr
= notification
->add_Failed(Callback
< Implements
< RuntimeClassFlags
<ClassicCom
>,
314 ITypedEventHandler
<ToastNotification
*, ToastFailedEventArgs
* >> >(
315 [eventHandler
](IToastNotification
*, IToastFailedEventArgs
*)
317 eventHandler
->toastFailed();
319 }).Get(), &failedToken
);
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
);
329 ComPtr
<IXmlNode
> node
;
330 hr
= srcAttribute
.As(&node
);
332 ComPtr
<IXmlNode
> pNode
;
333 hr
= attributeMap
->SetNamedItem(node
.Get(), &pNode
);
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
);
343 ComPtr
<IXmlNode
> root
;
344 hr
= rootList
->Item(0, &root
);
346 ComPtr
<ABI::Windows::Data::Xml::Dom::IXmlElement
> audioElement
;
347 hr
= xml
->CreateElement(WinToastStringWrapper(element_name
).Get(), &audioElement
);
349 ComPtr
<IXmlNode
> audioNodeTmp
;
350 hr
= audioElement
.As(&audioNodeTmp
);
352 ComPtr
<IXmlNode
> audioNode
;
353 hr
= root
->AppendChild(audioNodeTmp
.Get(), &audioNode
);
355 ComPtr
<IXmlNamedNodeMap
> attributes
;
356 hr
= audioNode
->get_Attributes(&attributes
);
358 for (const auto& it
: attribute_names
) {
359 hr
= addAttribute(xml
, it
, attributes
.Get());
371 WinToast
* WinToast::instance() {
372 static WinToast instance
;
376 WinToast::WinToast() :
377 _isInitialized(false),
378 _hasCoInitialized(false)
380 if (!isCompatible()) {
381 DEBUG_MSG(L
"Warning: Your system is not compatible with this library ");
385 WinToast::~WinToast() {
386 if (_hasCoInitialized
) {
391 void WinToast::setAppName(_In_
const std::wstring
& appName
) {
396 void WinToast::setAppUserModelId(_In_
const std::wstring
& aumi
) {
398 DEBUG_MSG(L
"Default App User Model Id: " << _aumi
.c_str());
401 void WinToast::setShortcutPolicy(_In_ ShortcutPolicy shortcutPolicy
) {
402 _shortcutPolicy
= shortcutPolicy
;
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));
414 bool WinToastLib::WinToast::isSupportingModernFeatures() {
415 constexpr auto MinimumSupportedVersion
= 6;
416 return Util::getRealOSVersion().dwMajorVersion
> MinimumSupportedVersion
;
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
)
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
;
433 if (aumi
.length() > SCHAR_MAX
) {
434 DEBUG_MSG("Error: max size allowed for AUMI: 128 characters.");
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"}
451 const auto iter
= Labels
.find(error
);
452 assert(iter
!= Labels
.end());
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
;
462 if (!isCompatible()) {
463 DEBUG_MSG(L
"Your OS is not compatible with this library! =(");
464 return SHORTCUT_INCOMPATIBLE_OS
;
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
;
475 _hasCoInitialized
= true;
481 HRESULT hr
= validateShellLinkHelper(wasChanged
);
483 return wasChanged
? SHORTCUT_WAS_CHANGED
: SHORTCUT_UNCHANGED
;
485 hr
= createShellLinkHelper();
486 return SUCCEEDED(hr
) ? SHORTCUT_WAS_CREATED
: SHORTCUT_CREATE_FAILED
;
489 bool WinToast::initialize(_Out_ WinToastError
* error
) {
490 _isInitialized
= false;
491 setError(error
, WinToastError::NoError
);
493 if (!isCompatible()) {
494 setError(error
, WinToastError::SystemNotSupported
);
495 DEBUG_MSG(L
"Error: system not supported.");
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?");
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 =(");
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 =(");
520 _isInitialized
= true;
521 return _isInitialized
;
524 bool WinToast::isInitialized() const {
525 return _isInitialized
;
528 const std::wstring
& WinToast::appName() const {
532 const std::wstring
& WinToast::appUserModelId() const {
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
);
551 wcscpy(path
, systemPath
);
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
));
563 ComPtr
<IPersistFile
> persistFile
;
564 hr
= shellLink
.As(&persistFile
);
566 hr
= persistFile
->Load(path
, STGM_READ
);
568 ComPtr
<IPropertyStore
> propertyStore
;
569 hr
= shellLink
.As(&propertyStore
);
571 PROPVARIANT appIdPropVar
;
572 hr
= propertyStore
->GetValue(PKEY_AppUserModel_ID
, &appIdPropVar
);
574 WCHAR AUMI
[MAX_PATH
];
575 hr
= DllImporter::PropVariantToString(appIdPropVar
, AUMI
, MAX_PATH
);
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! =)
581 PropVariantClear(&appIdPropVar
);
582 hr
= InitPropVariantFromString(_aumi
.c_str(), &appIdPropVar
);
584 hr
= propertyStore
->SetValue(PKEY_AppUserModel_ID
, appIdPropVar
);
586 hr
= propertyStore
->Commit();
587 if (SUCCEEDED(hr
) && SUCCEEDED(persistFile
->IsDirty())) {
588 hr
= persistFile
->Save(path
, TRUE
);
593 // Not allowed to touch the shortcut to fix the AUMI
597 PropVariantClear(&appIdPropVar
);
608 HRESULT
WinToast::createShellLinkHelper() {
609 if (_shortcutPolicy
!= SHORTCUT_POLICY_REQUIRE_CREATE
) {
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
));
620 hr
= shellLink
->SetPath(exePath
);
622 hr
= shellLink
->SetArguments(L
"");
624 hr
= shellLink
->SetWorkingDirectory(exePath
);
626 ComPtr
<IPropertyStore
> propertyStore
;
627 hr
= shellLink
.As(&propertyStore
);
629 PROPVARIANT appIdPropVar
;
630 hr
= InitPropVariantFromString(_aumi
.c_str(), &appIdPropVar
);
632 hr
= propertyStore
->SetValue(PKEY_AppUserModel_ID
, appIdPropVar
);
634 hr
= propertyStore
->Commit();
636 ComPtr
<IPersistFile
> persistFile
;
637 hr
= shellLink
.As(&persistFile
);
639 hr
= persistFile
->Save(slPath
, TRUE
);
643 PropVariantClear(&appIdPropVar
);
653 INT64
WinToast::showToast(_In_
const WinToastTemplate
& toast
, _In_ IWinToastHandler
* handler
, _Out_ WinToastError
* error
) {
654 setError(error
, WinToastError::NoError
);
656 if (!isInitialized()) {
657 setError(error
, WinToastError::NotInitialized
);
658 DEBUG_MSG("Error when launching the toast. WinToast is not initialized.");
662 setError(error
, WinToastError::InvalidHandler
);
663 DEBUG_MSG("Error when launching the toast. Handler cannot be nullptr.");
667 ComPtr
<IToastNotificationManagerStatics
> notificationManager
;
668 HRESULT hr
= DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager
).Get(), ¬ificationManager
);
670 ComPtr
<IToastNotifier
> notifier
;
671 hr
= notificationManager
->CreateToastNotifierWithId(WinToastStringWrapper(_aumi
).Get(), ¬ifier
);
673 ComPtr
<IToastNotificationFactory
> notificationFactory
;
674 hr
= DllImporter::Wrap_GetActivationFactory(WinToastStringWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification
).Get(), ¬ificationFactory
);
676 ComPtr
<IXmlDocument
> xmlDocument
;
677 HRESULT hr
= notificationManager
->GetTemplateContent(ToastTemplateType(toast
.type()), &xmlDocument
);
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
);
683 // Modern feature are supported Windows > Windows 10
684 if (SUCCEEDED(hr
) && isSupportingModernFeatures()) {
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.
689 && !toast
.attributionText().empty()) {
690 hr
= setAttributionTextFieldHelper(xmlDocument
.Get(), toast
.attributionText());
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());
700 hr
= (toast
.audioPath().empty() && toast
.audioOption() == WinToastTemplate::AudioOption::Default
)
701 ? hr
: setAudioFieldHelper(xmlDocument
.Get(), toast
.audioPath(), toast
.audioOption());
704 if (SUCCEEDED(hr
) && toast
.duration() != WinToastTemplate::Duration::System
) {
705 hr
= addDurationHelper(xmlDocument
.Get(),
706 (toast
.duration() == WinToastTemplate::Duration::Short
) ? L
"short" : L
"long");
710 DEBUG_MSG("Modern features (Actions/Sounds/Attributes) not supported in this os version");
714 hr
= toast
.hasImage() ? setImageFieldHelper(xmlDocument
.Get(), toast
.imagePath()) : hr
;
716 ComPtr
<IToastNotification
> notification
;
717 hr
= notificationFactory
->CreateToastNotification(xmlDocument
.Get(), ¬ification
);
719 INT64 expiration
= 0, relativeExpiration
= toast
.expiration();
720 if (relativeExpiration
> 0) {
721 InternalDateTime
expirationDateTime(relativeExpiration
);
722 expiration
= expirationDateTime
;
723 hr
= notification
->put_ExpirationTime(&expirationDateTime
);
727 hr
= Util::setEventHandlers(notification
.Get(), std::shared_ptr
<IWinToastHandler
>(handler
), expiration
);
729 setError(error
, WinToastError::InvalidHandler
);
735 hr
= CoCreateGuid(&guid
);
738 _buffer
[id
] = notification
;
739 DEBUG_MSG("xml: " << Util::AsString(xmlDocument
));
740 hr
= notifier
->Show(notification
.Get());
742 setError(error
, WinToastError::NotDisplayed
);
753 return FAILED(hr
) ? -1 : id
;
756 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
);
761 hr
= notificationManager
->CreateToastNotifierWithId(WinToastStringWrapper(_aumi
).Get(), ¬ifier
);
763 *succeded
= SUCCEEDED(hr
);
767 bool WinToast::hideToast(_In_ INT64 id
) {
768 if (!isInitialized()) {
769 DEBUG_MSG("Error when hiding the toast. WinToast is not initialized.");
773 if (_buffer
.find(id
) != _buffer
.end()) {
774 auto succeded
= false;
775 auto notify
= notifier(&succeded
);
777 auto result
= notify
->Hide(_buffer
[id
].Get());
779 return SUCCEEDED(result
);
785 void WinToast::clear() {
786 auto succeded
= false;
787 auto notify
= notifier(&succeded
);
789 auto end
= _buffer
.end();
790 for (auto it
= _buffer
.begin(); it
!= end
; ++it
) {
791 notify
->Hide(it
->second
.Get());
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
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.
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
);
809 UINT32 nodeListLength
;
810 hr
= nodeList
->get_Length(&nodeListLength
);
812 for (UINT32 i
= 0; i
< nodeListLength
; i
++) {
813 ComPtr
<IXmlNode
> textNode
;
814 hr
= nodeList
->Item(i
, &textNode
);
816 ComPtr
<IXmlNamedNodeMap
> attributes
;
817 hr
= textNode
->get_Attributes(&attributes
);
819 ComPtr
<IXmlNode
> editedNode
;
821 hr
= attributes
->GetNamedItem(WinToastStringWrapper(L
"placement").Get(), &editedNode
);
822 if (FAILED(hr
) || !editedNode
) {
825 hr
= Util::setNodeStringValue(L
"attribution", editedNode
.Get(), xml
);
827 return setTextFieldHelper(xml
, text
, i
);
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
);
843 hr
= nodeList
->get_Length(&length
);
845 ComPtr
<IXmlNode
> toastNode
;
846 hr
= nodeList
->Item(0, &toastNode
);
848 ComPtr
<IXmlElement
> toastElement
;
849 hr
= toastNode
.As(&toastElement
);
851 hr
= toastElement
->SetAttribute(WinToastStringWrapper(L
"duration").Get(),
852 WinToastStringWrapper(duration
).Get());
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
);
864 ComPtr
<IXmlNode
> node
;
865 hr
= nodeList
->Item(pos
, &node
);
867 hr
= Util::setNodeStringValue(text
, node
.Get(), xml
);
874 HRESULT
WinToast::setImageFieldHelper(_In_ IXmlDocument
*xml
, _In_
const std::wstring
& path
) {
875 assert(path
.size() < MAX_PATH
);
877 wchar_t imagePath
[MAX_PATH
] = L
"file:///";
878 HRESULT hr
= StringCchCatW(imagePath
, MAX_PATH
, path
.c_str());
880 ComPtr
<IXmlNodeList
> nodeList
;
881 HRESULT hr
= xml
->GetElementsByTagName(WinToastStringWrapper(L
"image").Get(), &nodeList
);
883 ComPtr
<IXmlNode
> node
;
884 hr
= nodeList
->Item(0, &node
);
886 ComPtr
<IXmlNamedNodeMap
> attributes
;
887 hr
= node
->get_Attributes(&attributes
);
889 ComPtr
<IXmlNode
> editedNode
;
890 hr
= attributes
->GetNamedItem(WinToastStringWrapper(L
"src").Get(), &editedNode
);
892 Util::setNodeStringValue(imagePath
, editedNode
.Get(), xml
);
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
);
908 ComPtr
<IXmlNodeList
> nodeList
;
909 HRESULT hr
= xml
->GetElementsByTagName(WinToastStringWrapper(L
"audio").Get(), &nodeList
);
911 ComPtr
<IXmlNode
> node
;
912 hr
= nodeList
->Item(0, &node
);
914 ComPtr
<IXmlNamedNodeMap
> attributes
;
915 hr
= node
->get_Attributes(&attributes
);
917 ComPtr
<IXmlNode
> editedNode
;
920 hr
= attributes
->GetNamedItem(WinToastStringWrapper(L
"src").Get(), &editedNode
);
922 hr
= Util::setNodeStringValue(path
, editedNode
.Get(), xml
);
929 case WinToastTemplate::AudioOption::Loop
:
930 hr
= attributes
->GetNamedItem(WinToastStringWrapper(L
"loop").Get(), &editedNode
);
932 hr
= Util::setNodeStringValue(L
"true", editedNode
.Get(), xml
);
935 case WinToastTemplate::AudioOption::Silent
:
936 hr
= attributes
->GetNamedItem(WinToastStringWrapper(L
"silent").Get(), &editedNode
);
938 hr
= Util::setNodeStringValue(L
"true", editedNode
.Get(), xml
);
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
);
955 hr
= nodeList
->get_Length(&length
);
957 ComPtr
<IXmlNode
> actionsNode
;
959 hr
= nodeList
->Item(0, &actionsNode
);
961 hr
= xml
->GetElementsByTagName(WinToastStringWrapper(L
"toast").Get(), &nodeList
);
963 hr
= nodeList
->get_Length(&length
);
965 ComPtr
<IXmlNode
> toastNode
;
966 hr
= nodeList
->Item(0, &toastNode
);
968 ComPtr
<IXmlElement
> toastElement
;
969 hr
= toastNode
.As(&toastElement
);
971 hr
= toastElement
->SetAttribute(WinToastStringWrapper(L
"template").Get(), WinToastStringWrapper(L
"ToastGeneric").Get());
973 hr
= toastElement
->SetAttribute(WinToastStringWrapper(L
"duration").Get(), WinToastStringWrapper(L
"long").Get());
975 ComPtr
<IXmlElement
> actionsElement
;
976 hr
= xml
->CreateElement(WinToastStringWrapper(L
"actions").Get(), &actionsElement
);
978 hr
= actionsElement
.As(&actionsNode
);
980 ComPtr
<IXmlNode
> appendedChild
;
981 hr
= toastNode
->AppendChild(actionsNode
.Get(), &appendedChild
);
990 ComPtr
<IXmlElement
> actionElement
;
991 hr
= xml
->CreateElement(WinToastStringWrapper(L
"action").Get(), &actionElement
);
993 hr
= actionElement
->SetAttribute(WinToastStringWrapper(L
"content").Get(), WinToastStringWrapper(content
).Get());
995 hr
= actionElement
->SetAttribute(WinToastStringWrapper(L
"arguments").Get(), WinToastStringWrapper(arguments
).Get());
997 ComPtr
<IXmlNode
> actionNode
;
998 hr
= actionElement
.As(&actionNode
);
1000 ComPtr
<IXmlNode
> appendedChild
;
1001 hr
= actionsNode
->AppendChild(actionNode
.Get(), &appendedChild
);
1010 void WinToast::setError(_Out_ WinToastError
* error
, _In_ WinToastError value
) {
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
"");
1021 WinToastTemplate::~WinToastTemplate() {
1022 _textFields
.clear();
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
;
1031 void WinToastTemplate::setImagePath(_In_
const std::wstring
& imgPath
) {
1032 _imagePath
= imgPath
;
1035 void WinToastTemplate::setAudioPath(_In_
const std::wstring
& audioPath
) {
1036 _audioPath
= audioPath
;
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"},
1068 const auto iter
= Files
.find(file
);
1069 assert(iter
!= Files
.end());
1070 _audioPath
= iter
->second
;
1073 void WinToastTemplate::setAudioOption(_In_
WinToastTemplate::AudioOption audioOption
) {
1074 _audioOption
= audioOption
;
1077 void WinToastTemplate::setFirstLine(const std::wstring
&text
) {
1078 setTextField(text
, WinToastTemplate::FirstLine
);
1081 void WinToastTemplate::setSecondLine(const std::wstring
&text
) {
1082 setTextField(text
, WinToastTemplate::SecondLine
);
1085 void WinToastTemplate::setThirdLine(const std::wstring
&text
) {
1086 setTextField(text
, WinToastTemplate::ThirdLine
);
1089 void WinToastTemplate::setDuration(_In_ Duration duration
) {
1090 _duration
= duration
;
1093 void WinToastTemplate::setExpiration(_In_ INT64 millisecondsFromNow
) {
1094 _expiration
= millisecondsFromNow
;
1097 void WinToastTemplate::setAttributionText(_In_
const std::wstring
& attributionText
) {
1098 _attributionText
= attributionText
;
1101 void WinToastTemplate::addAction(_In_
const std::wstring
& label
) {
1102 _actions
.push_back(label
);
1105 std::size_t WinToastTemplate::textFieldsCount() const {
1106 return _textFields
.size();
1109 std::size_t WinToastTemplate::actionsCount() const {
1110 return _actions
.size();
1113 bool WinToastTemplate::hasImage() const {
1114 return _type
< WinToastTemplateType::Text01
;
1117 const std::vector
<std::wstring
>& WinToastTemplate::textFields() const {
1121 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
];
1127 const std::wstring
& WinToastTemplate::actionLabel(_In_
std::size_t position
) const {
1128 assert(position
< _actions
.size());
1129 return _actions
[position
];
1132 const std::wstring
& WinToastTemplate::imagePath() const {
1136 const std::wstring
& WinToastTemplate::audioPath() const {
1140 const std::wstring
& WinToastTemplate::attributionText() const {
1141 return _attributionText
;
1144 INT64
WinToastTemplate::expiration() const {
1148 WinToastTemplate::WinToastTemplateType
WinToastTemplate::type() const {
1152 WinToastTemplate::AudioOption
WinToastTemplate::audioOption() const {
1153 return _audioOption
;
1156 WinToastTemplate::Duration
WinToastTemplate::duration() const {