Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Persistent toast notification in Win32 app with COM server
#include "stdafx.h"
#include "ToastActivator_h.h"
using namespace Microsoft::WRL;
using namespace ABI::Windows::UI::Notifications;
using namespace ABI::Windows::Data::Xml::Dom;
using namespace Windows::Foundation;
class DECLSPEC_UUID("BD8EC9B3-CAEB-465A-B5C0-2ABA5D5839D1") CToastActivator
WrlFinal : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, INotificationActivationCallback, FtmBase>
{
public:
virtual HRESULT STDMETHODCALLTYPE Activate(
_In_ LPCWSTR /*appUserModelId*/,
_In_ LPCWSTR /*invokedArgs*/,
/*_In_reads_(dataCount)*/ const NOTIFICATION_USER_INPUT_DATA* /*data*/,
ULONG /*dataCount*/) override
{
MessageBox(NULL, L"Toast activated", L"Toast Notofication", MB_OK);
}
//IUnknown methods are implemented here
HRESULT __stdcall QueryInterface(
REFIID riid,
void **ppObj)
{
if (riid == IID_IUnknown)
{
*ppObj = static_cast<INotificationActivationCallback*>(this);
AddRef();
return S_OK;
}
if (riid == IID_INotificationActivationCallback)
{
*ppObj = static_cast<INotificationActivationCallback*>(this);
AddRef();
return S_OK;
}
*ppObj = NULL;
return E_NOINTERFACE;
}
ULONG __stdcall AddRef()
{
return InterlockedIncrement(&m_nRefCount);
}
ULONG __stdcall Release()
{
long nRefCount = 0;
nRefCount = InterlockedDecrement(&m_nRefCount);
if (nRefCount == 0) delete this;
return nRefCount;
}
CToastActivator()
{
//
//constructor
//
m_nRefCount = 0;
}
~CToastActivator()
{
}
private:
long m_nRefCount;
};
//
// Main function
int WINAPI wWinMain(_In_ HINSTANCE /* hInstance */, _In_opt_ HINSTANCE /* hPrevInstance */, _In_ LPWSTR /* lpCmdLine */, _In_ int /* nCmdShow */)
{
HRESULT hr = Initialize(RO_INIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
Module<OutOfProc>::GetModule().RegisterObjects();
DesktopToastsApp app;
hr = app.Initialize();
if (SUCCEEDED(hr))
{
app.RunMessageLoop();
}
Uninitialize();
}
return SUCCEEDED(hr);
}
DesktopToastsApp::DesktopToastsApp() : _hwnd(nullptr), _hEdit(nullptr)
{
}
DesktopToastsApp::~DesktopToastsApp()
{
}
// In order to display toasts, a desktop application must have a shortcut on the Start menu.
// Also, an AppUserModelID must be set on that shortcut.
// The shortcut should be created as part of the installer. The following code shows how to create
// a shortcut and assign an AppUserModelID using Windows APIs. You must download and include the
// Windows API Code Pack for Microsoft .NET Framework for this code to function
//
// Included in this project is a wxs file that be used with the WiX toolkit
// to make an installer that creates the necessary shortcut. One or the other should be used.
HRESULT DesktopToastsApp::TryCreateShortcut()
{
wchar_t shortcutPath[MAX_PATH];
DWORD charWritten = GetEnvironmentVariable(L"APPDATA", shortcutPath, MAX_PATH);
HRESULT hr = charWritten > 0 ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
errno_t concatError = wcscat_s(shortcutPath, ARRAYSIZE(shortcutPath), L"\\Microsoft\\Windows\\Start Menu\\Programs\\Desktop Toasts App.lnk");
hr = concatError == 0 ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
DWORD attributes = GetFileAttributes(shortcutPath);
bool fileExists = attributes < 0xFFFFFFF;
if (!fileExists)
{
hr = InstallShortcut(shortcutPath);
}
else
{
hr = S_FALSE;
}
}
}
return hr;
}
// Install the shortcut
HRESULT DesktopToastsApp::InstallShortcut(_In_z_ wchar_t *shortcutPath)
{
wchar_t exePath[MAX_PATH];
DWORD charWritten = GetModuleFileNameEx(GetCurrentProcess(), nullptr, exePath, ARRAYSIZE(exePath));
HRESULT hr = charWritten > 0 ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
ComPtr<IShellLink> shellLink;
hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink));
if (SUCCEEDED(hr))
{
hr = shellLink->SetPath(exePath);
if (SUCCEEDED(hr))
{
hr = shellLink->SetArguments(L"");
if (SUCCEEDED(hr))
{
ComPtr<IPropertyStore> propertyStore;
hr = shellLink.As(&propertyStore);
if (SUCCEEDED(hr))
{
PROPVARIANT appIdPropVar;
hr = InitPropVariantFromString(AppId, &appIdPropVar);
if (SUCCEEDED(hr))
{
hr = propertyStore->SetValue(PKEY_AppUserModel_ID, appIdPropVar);
if (SUCCEEDED(hr))
{
/* hr = propertyStore->Commit();*/
PropVariantClear(&appIdPropVar);
appIdPropVar.vt = VT_CLSID;
appIdPropVar.puuid = const_cast<CLSID*>(&__uuidof(CToastActivator));
hr = propertyStore->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, appIdPropVar);
if (SUCCEEDED(hr))
{
ComPtr<IPersistFile> persistFile;
hr = shellLink.As(&persistFile);
if (SUCCEEDED(hr))
{
hr = persistFile->Save(shortcutPath, TRUE);
}
}
}
PropVariantClear(&appIdPropVar);
}
}
}
}
}
}
return hr;
}
// Prepare the main window
HRESULT DesktopToastsApp::Initialize()
{
HRESULT hr = TryCreateShortcut();
hr = DisplayToast();
return hr;
}
// Standard message loop
void DesktopToastsApp::RunMessageLoop()
{
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Display the toast using classic COM. Note that is also possible to create and display the toast using the new C++ /ZW options (using handles,
// COM wrappers, etc.)
HRESULT DesktopToastsApp::DisplayToast()
{
ComPtr<IToastNotificationManagerStatics> toastStatics;
HRESULT hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager).Get(), &toastStatics);
if (SUCCEEDED(hr))
{
ComPtr<IXmlDocument> toastXml;
hr = CreateToastXml(toastStatics.Get(), &toastXml);
if (SUCCEEDED(hr))
{
hr = CreateToast(toastStatics.Get(), toastXml.Get());
}
}
return hr;
}
// Create the toast XML from a template
HRESULT DesktopToastsApp::CreateToastXml(_In_ IToastNotificationManagerStatics *toastManager, _Outptr_ IXmlDocument** inputXml)
{
// Retrieve the template XML
HRESULT hr = toastManager->GetTemplateContent(ToastTemplateType_ToastImageAndText04, inputXml);
if (SUCCEEDED(hr))
{
wchar_t *imagePath = _wfullpath(nullptr, L"toastImageAndText.png", MAX_PATH);
hr = imagePath != nullptr ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
if (SUCCEEDED(hr))
{
hr = SetImageSrc(imagePath, *inputXml);
if (SUCCEEDED(hr))
{
wchar_t* textValues[] = {
L"Line 1",
L"Line 2",
L"Line 3"
};
UINT32 textLengths[] = {6, 6, 6};
hr = SetTextValues(textValues, 3, textLengths, *inputXml);
}
}
}
return hr;
}
// Set the value of the "src" attribute of the "image" node
HRESULT DesktopToastsApp::SetImageSrc(_In_z_ wchar_t *imagePath, _In_ IXmlDocument *toastXml)
{
wchar_t imageSrc[MAX_PATH] = L"file:///";
HRESULT hr = StringCchCat(imageSrc, ARRAYSIZE(imageSrc), imagePath);
if (SUCCEEDED(hr))
{
ComPtr<IXmlNodeList> nodeList;
hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"image").Get(), &nodeList);
if (SUCCEEDED(hr))
{
ComPtr<IXmlNode> imageNode;
hr = nodeList->Item(0, &imageNode);
if (SUCCEEDED(hr))
{
ComPtr<IXmlNamedNodeMap> attributes;
hr = imageNode->get_Attributes(&attributes);
if (SUCCEEDED(hr))
{
ComPtr<IXmlNode> srcAttribute;
hr = attributes->GetNamedItem(StringReferenceWrapper(L"src").Get(), &srcAttribute);
if (SUCCEEDED(hr))
{
hr = SetNodeValueString(StringReferenceWrapper(imageSrc).Get(), srcAttribute.Get(), toastXml);
}
}
}
}
}
return hr;
}
// Set the values of each of the text nodes
HRESULT DesktopToastsApp::SetTextValues(_In_reads_(textValuesCount) wchar_t **textValues, _In_ UINT32 textValuesCount, _In_reads_(textValuesCount) UINT32 *textValuesLengths, _In_ IXmlDocument *toastXml)
{
HRESULT hr = textValues != nullptr && textValuesCount > 0 ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
ComPtr<IXmlNodeList> nodeList;
hr = toastXml->GetElementsByTagName(StringReferenceWrapper(L"text").Get(), &nodeList);
if (SUCCEEDED(hr))
{
UINT32 nodeListLength;
hr = nodeList->get_Length(&nodeListLength);
if (SUCCEEDED(hr))
{
hr = textValuesCount <= nodeListLength ? S_OK : E_INVALIDARG;
if (SUCCEEDED(hr))
{
for (UINT32 i = 0; i < textValuesCount; i++)
{
ComPtr<IXmlNode> textNode;
hr = nodeList->Item(i, &textNode);
if (SUCCEEDED(hr))
{
hr = SetNodeValueString(StringReferenceWrapper(textValues[i], textValuesLengths[i]).Get(), textNode.Get(), toastXml);
}
}
}
}
}
}
return hr;
}
HRESULT DesktopToastsApp::SetNodeValueString(_In_ HSTRING inputString, _In_ IXmlNode *node, _In_ IXmlDocument *xml)
{
ComPtr<IXmlText> inputText;
HRESULT hr = xml->CreateTextNode(inputString, &inputText);
if (SUCCEEDED(hr))
{
ComPtr<IXmlNode> inputTextNode;
hr = inputText.As(&inputTextNode);
if (SUCCEEDED(hr))
{
ComPtr<IXmlNode> pAppendedChild;
hr = node->AppendChild(inputTextNode.Get(), &pAppendedChild);
}
}
return hr;
}
// Create and display the toast
HRESULT DesktopToastsApp::CreateToast(_In_ IToastNotificationManagerStatics *toastManager, _In_ IXmlDocument *xml)
{
ComPtr<IToastNotifier> notifier;
HRESULT hr = toastManager->CreateToastNotifierWithId(StringReferenceWrapper(AppId).Get(), &notifier);
if (SUCCEEDED(hr))
{
ComPtr<IToastNotificationFactory> factory;
hr = GetActivationFactory(StringReferenceWrapper(RuntimeClass_Windows_UI_Notifications_ToastNotification).Get(), &factory);
if (SUCCEEDED(hr))
{
ComPtr<IToastNotification> toast;
hr = factory->CreateToastNotification(xml, &toast);
if (SUCCEEDED(hr))
{
// Register the event handlers
EventRegistrationToken activatedToken, dismissedToken, failedToken;
ComPtr<ToastEventHandler> eventHandler(new ToastEventHandler(_hwnd, _hEdit));
hr = toast->add_Activated(eventHandler.Get(), &activatedToken);
if (SUCCEEDED(hr))
{
hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken);
if (SUCCEEDED(hr))
{
hr = toast->add_Failed(eventHandler.Get(), &failedToken);
if (SUCCEEDED(hr))
{
hr = notifier->Show(toast.Get());
}
}
}
}
}
}
return hr;
}
// ToastActivator.idl : IDL source for ToastActivator
//
// This file will be processed by the MIDL tool to
// produce the type library (ToastActivator.tlb) and marshalling code.
import "oaidl.idl";
import "ocidl.idl";
typedef struct _NOTIFICATION_USER_INPUT_DATA
{
LPCWSTR Key;
LPCWSTR Value;
} NOTIFICATION_USER_INPUT_DATA;
[
object,
uuid("53E31837-6600-4A81-9395-75CFFE746F94"),
pointer_default(ref)
]
interface INotificationActivationCallback : IUnknown
{
HRESULT Activate(
[in, string] LPCWSTR appUserModelId,
[in, string] LPCWSTR arguments, // arugments from the invoked button
[in, size_is(count), unique] const NOTIFICATION_USER_INPUT_DATA* data, // data from all the input elements in the XML
[in] ULONG count);
};
/* this ALWAYS GENERATED file contains the definitions for the interfaces */
/* File created by MIDL compiler version 8.00.0613 */
/* at Tue Jan 19 08:44:07 2038
*/
/* Compiler settings for ToastActivator.idl:
Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0613
protocol : dce , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
/* @@MIDL_FILE_HEADING( ) */
#pragma warning( disable: 4049 ) /* more than 64k source lines */
/* verify that the <rpcndr.h> version is high enough to compile this file*/
#ifndef __REQUIRED_RPCNDR_H_VERSION__
#define __REQUIRED_RPCNDR_H_VERSION__ 475
#endif
#include "rpc.h"
#include "rpcndr.h"
#ifndef __RPCNDR_H_VERSION__
#error this stub requires an updated version of <rpcndr.h>
#endif /* __RPCNDR_H_VERSION__ */
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/
#ifndef __ToastActivator_h_h__
#define __ToastActivator_h_h__
#if defined(_MSC_VER) && (_MSC_VER >= 1020)
#pragma once
#endif
/* Forward Declarations */
#ifndef __INotificationActivationCallback_FWD_DEFINED__
#define __INotificationActivationCallback_FWD_DEFINED__
typedef interface INotificationActivationCallback INotificationActivationCallback;
#endif /* __INotificationActivationCallback_FWD_DEFINED__ */
/* header files for imported files */
#include "oaidl.h"
#include "ocidl.h"
#ifdef __cplusplus
extern "C"{
#endif
/* interface __MIDL_itf_ToastActivator_0000_0000 */
/* [local] */
typedef struct _NOTIFICATION_USER_INPUT_DATA
{
LPCWSTR Key;
LPCWSTR Value;
} NOTIFICATION_USER_INPUT_DATA;
extern RPC_IF_HANDLE __MIDL_itf_ToastActivator_0000_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_ToastActivator_0000_0000_v0_0_s_ifspec;
#ifndef __INotificationActivationCallback_INTERFACE_DEFINED__
#define __INotificationActivationCallback_INTERFACE_DEFINED__
/* interface INotificationActivationCallback */
/* [ref][uuid][object] */
EXTERN_C const IID IID_INotificationActivationCallback;
#if defined(__cplusplus) && !defined(CINTERFACE)
MIDL_INTERFACE("53E31837-6600-4A81-9395-75CFFE746F94")
INotificationActivationCallback : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE Activate(
/* [string][in] */ LPCWSTR appUserModelId,
/* [string][in] */ LPCWSTR arguments,
/* [unique][size_is][in] */ const NOTIFICATION_USER_INPUT_DATA *data,
/* [in] */ ULONG count) = 0;
};
#else /* C style interface */
typedef struct INotificationActivationCallbackVtbl
{
BEGIN_INTERFACE
HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
INotificationActivationCallback * This,
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
_COM_Outptr_ void **ppvObject);
ULONG ( STDMETHODCALLTYPE *AddRef )(
INotificationActivationCallback * This);
ULONG ( STDMETHODCALLTYPE *Release )(
INotificationActivationCallback * This);
HRESULT ( STDMETHODCALLTYPE *Activate )(
INotificationActivationCallback * This,
/* [string][in] */ LPCWSTR appUserModelId,
/* [string][in] */ LPCWSTR arguments,
/* [unique][size_is][in] */ const NOTIFICATION_USER_INPUT_DATA *data,
/* [in] */ ULONG count);
END_INTERFACE
} INotificationActivationCallbackVtbl;
interface INotificationActivationCallback
{
CONST_VTBL struct INotificationActivationCallbackVtbl *lpVtbl;
};
#ifdef COBJMACROS
#define INotificationActivationCallback_QueryInterface(This,riid,ppvObject) \
( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
#define INotificationActivationCallback_AddRef(This) \
( (This)->lpVtbl -> AddRef(This) )
#define INotificationActivationCallback_Release(This) \
( (This)->lpVtbl -> Release(This) )
#define INotificationActivationCallback_Activate(This,appUserModelId,arguments,data,count) \
( (This)->lpVtbl -> Activate(This,appUserModelId,arguments,data,count) )
#endif /* COBJMACROS */
#endif /* C style interface */
#endif /* __INotificationActivationCallback_INTERFACE_DEFINED__ */
/* Additional Prototypes for ALL interfaces */
/* end of Additional Prototypes */
#ifdef __cplusplus
}
#endif
#endif
/* this ALWAYS GENERATED file contains the IIDs and CLSIDs */
/* link this file in with the server and any clients */
/* File created by MIDL compiler version 8.00.0613 */
/* at Tue Jan 19 08:44:07 2038
*/
/* Compiler settings for ToastActivator.idl:
Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.00.0613
protocol : dce , ms_ext, c_ext, robust
error checks: allocation ref bounds_check enum stub_data
VC __declspec() decoration level:
__declspec(uuid()), __declspec(selectany), __declspec(novtable)
DECLSPEC_UUID(), MIDL_INTERFACE()
*/
/* @@MIDL_FILE_HEADING( ) */
#pragma warning( disable: 4049 ) /* more than 64k source lines */
#ifdef __cplusplus
extern "C"{
#endif
#include <rpc.h>
#include <rpcndr.h>
#ifdef _MIDL_USE_GUIDDEF_
#ifndef INITGUID
#define INITGUID
#include <guiddef.h>
#undef INITGUID
#else
#include <guiddef.h>
#endif
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8)
#else // !_MIDL_USE_GUIDDEF_
#ifndef __IID_DEFINED__
#define __IID_DEFINED__
typedef struct _IID
{
unsigned long x;
unsigned short s1;
unsigned short s2;
unsigned char c[8];
} IID;
#endif // __IID_DEFINED__
#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED
#define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \
const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
#endif !_MIDL_USE_GUIDDEF_
MIDL_DEFINE_GUID(IID, IID_INotificationActivationCallback,0x53E31837,0x6600,0x4A81,0x93,0x95,0x75,0xCF,0xFE,0x74,0x6F,0x94);
#undef MIDL_DEFINE_GUID
#ifdef __cplusplus
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment