Skip to content

Instantly share code, notes, and snippets.

@kirancheraku
Created December 2, 2015 13:17
Show Gist options
  • Save kirancheraku/512f658da1847044a7b6 to your computer and use it in GitHub Desktop.
Save kirancheraku/512f658da1847044a7b6 to your computer and use it in GitHub Desktop.
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
@kirancheraku
Copy link
Author

The posted sample files contain the COM server implementation for persistent toast notifications wuth Win32 desktop app.
The Toast activation is not working with this implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment