Skip to content

Instantly share code, notes, and snippets.

@mstroeck
Forked from CalvinLinTrend/DesktopToastsSample.cpp
Last active June 30, 2023 07:11
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mstroeck/e72e4e4dd6c326c11282 to your computer and use it in GitHub Desktop.
Save mstroeck/e72e4e4dd6c326c11282 to your computer and use it in GitHub Desktop.
Toast Notification in Win32 app with COM server
#include "stdafx.h"
#include <wrl/module.h>
#include "ToastNotificationActivationCallback.h"
#include "DesktopToastsSample.h"
// This is taken from: http://blogs.msdn.com/b/tiles_and_toasts/archive/2015/10/16/quickstart-handling-toast-activations-from-win32-apps-in-windows-10.aspx
DWORD g_allLocks;
// Main function
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
std::wstring strCmdLine = lpCmdLine;
OutputDebugString(strCmdLine.c_str());
HRESULT hr = Initialize(RO_INIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
if (strCmdLine.compare(L"-Embedding") == 0)
{
OutputDebugString(L"Register Objects");
hr = Module<OutOfProc>::GetModule().RegisterObjects();
MessageBox(NULL, L"Toast activated", L"Toast Notofication", MB_OK);
hr = Module<OutOfProc>::GetModule().UnregisterObjects();
Uninitialize();
return 0;
}
if (SUCCEEDED(hr))
{
OutputDebugString(L"Initial DesktopToastsApp");
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\\Trend Micro\\Trend Micro Titanium.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();*/
std::wstring test = L"23A5B06E-20BB-4E7E-A0AC-6982ED6A6041";
UUID idTest;
UuidFromString((RPC_WSTR)test.c_str(), &idTest);
PropVariantClear(&appIdPropVar);
appIdPropVar.vt = VT_CLSID;
//appIdPropVar.puuid = const_cast<CLSID*>(&__uuidof(CToastActivator));
appIdPropVar.puuid = const_cast<CLSID*>(&idTest);
hr = propertyStore->SetValue(PKEY_AppUserModel_ToastActivatorCLSID, appIdPropVar);
if (SUCCEEDED(hr))
{
propertyStore->Commit();
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();
//HRESULT hr = S_OK;
if (SUCCEEDED(hr))
{
WNDCLASSEX wcex;
ATOM atom;
// Register window class
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DesktopToastsApp::WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof(LONG_PTR);
wcex.hInstance = HINST_THISCOMPONENT;
wcex.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = nullptr;
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = L"DesktopToastsApp";
wcex.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
atom = RegisterClassEx(&wcex);
hr = atom ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
// Create window
_hwnd = CreateWindow(
L"DesktopToastsApp",
L"Desktop Toasts Demo App",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
350,
200,
nullptr,
nullptr,
HINST_THISCOMPONENT,
this
);
hr = _hwnd ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
CreateWindow(
L"BUTTON",
L"View Text Toast",
BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,
0,
0,
150,
25,
_hwnd,
reinterpret_cast<HMENU>(HM_TEXTBUTTON),
HINST_THISCOMPONENT,
nullptr
);
_hEdit = CreateWindow(
L"EDIT",
L"Whatever action you take on the displayed toast will be shown here.",
ES_LEFT | ES_MULTILINE | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER,
0,
30,
300,
50,
_hwnd, nullptr,
HINST_THISCOMPONENT,
nullptr
);
ShowWindow(_hwnd, SW_SHOWNORMAL);
UpdateWindow(_hwnd);
}
}
}
else
{
MessageBox(nullptr, L"Failed to install shortcut. Try running this application as administrator.", L"Failed launch", MB_OK | MB_ICONERROR);
}
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)
{
HStringReference toastXML(
L"<toast scenario=\"reminder\">"
L" <visual>"
L" <binding template=\"ToastGeneric\">"
L" <text>A standard toast.</text>"
L" </binding>"
L" </visual>"
L"</toast>");
ComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocumentIO> xmlDocument;
HRESULT hr = Windows::Foundation::ActivateInstance(StringReferenceWrapper(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument).Get(), &xmlDocument);
if (SUCCEEDED(hr))
{
hr = xmlDocument->LoadXml(toastXML.Get());
if (SUCCEEDED(hr))
{
hr = xmlDocument.CopyTo(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;
}
HRESULT DesktopToastsApp::TextButtonClicked()
{
return DisplayToast();
}
// Standard window procedure
LRESULT CALLBACK DesktopToastsApp::WndProc(_In_ HWND hwnd, _In_ UINT32 message, _In_ WPARAM wParam, _In_ LPARAM lParam)
{
if (message == WM_CREATE)
{
LPCREATESTRUCT pcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
DesktopToastsApp *app = reinterpret_cast<DesktopToastsApp *>(pcs->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) app);
return 1;
}
DesktopToastsApp *pApp = reinterpret_cast<DesktopToastsApp *>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (pApp)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
switch (wmId)
{
case HM_TEXTBUTTON:
pApp->TextButtonClicked();
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
}
return 0;
case WM_DESTROY:
{
PostQuitMessage(0);
}
return 1;
}
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
#include <NotificationActivationCallback.h>
#include <wrl/wrappers/corewrappers.h>
#include <wrl/module.h>
#include <xstring>
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::UI::Notifications;
using namespace ABI::Windows::Data::Xml::Dom;
using namespace Windows::Foundation;
class DECLSPEC_UUID("23A5B06E-20BB-4E7E-A0AC-6982ED6A6041") 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
{
//OutputDebugString(L"Test");
MessageBox(NULL, L"It's inside CToastActivator", L"Toast Notofication", MB_OK);
//return DesktopToastsApp::GetInstance().SetMessage(L"NotificationActivator - The user clicked on the toast.");
return S_OK;
}
//IUnknown methods are implemented here
HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID riid,
void **ppObj)
{
OutputDebugString(L"QueryInterface");
if (riid == IID_IUnknown)
{
OutputDebugString(L"IID_IUnknown");
*ppObj = static_cast<INotificationActivationCallback*>(this);
AddRef();
return S_OK;
}
if (riid == IID_INotificationActivationCallback)
{
OutputDebugString(L"IID_INotificationActivationCallback");
*ppObj = static_cast<INotificationActivationCallback*>(this);
AddRef();
return S_OK;
}
*ppObj = NULL;
return E_NOINTERFACE;
}
ULONG STDMETHODCALLTYPE AddRef()
{
return InterlockedIncrement(&m_nRefCount);
}
ULONG STDMETHODCALLTYPE 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;
};
CoCreatableClass(CToastActivator);
@paulushub
Copy link

There is MS sample in the SDK for this stuff.
DesktopToasts
DesktopToasts

That said, why are you creating shortcut to Trend Micro? (DesktopToastsSample.cpp - Line 72)

L"\\Microsoft\\Windows\\Start Menu\\Programs\\Trend Micro\\Trend Micro Titanium.lnk"

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