Skip to content

Instantly share code, notes, and snippets.

@valinet
Created December 21, 2020 19:22
Show Gist options
  • Save valinet/3283c79ba35fc8f103c747c8adbb6b23 to your computer and use it in GitHub Desktop.
Save valinet/3283c79ba35fc8f103c747c8adbb6b23 to your computer and use it in GitHub Desktop.
Send a toast notification in Windows 10 using plain C
// Send toast notifications in Windows 10, using Windows Runtime,
// without any language projection, in PLAIN C
// Copyright (c) 2021 Valentin - Gabriel Radu
//
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this softwareand associated documentation files(the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions :
// The above copyright noticeand this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Due to the long struct names and because I don't want to abstractize and
// typedef structs which has the potential to obfuscate the code, I decided to
// stick to 120 character lines for this document, instead of 80
//
// Set project Properties - Configuration Properties - Linker - All Options -
// - Additional Dependencies - runtimeobject.lib
// #pragma comment(lib, "runtimeobject.lib") does not work when compiled
// without default lib for whatever reason
//
// Set project Properties - Configuration Properties - Linker - All Options -
// - SubSystem - Windows or change to Console
// and modify the entry point and the signature of "main"
// the pragma belowis optional
//
// This code is basically the example provided by Microsoft without all the
// bloat around it and written in plain C instead of C++, of course
// https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/DesktopToasts/CPP/DesktopToastsSample.cpp
//
// Also, I have taken out the COM activator part; maybe I will port over that
// in the future, but for my use case, I don't really need it; the
// notifications still persist in Action Center and I fire my app with a
// protocol; this has the advantage that you DO NOT have to install a shortcut
// in the Start menu and can use the shortcut of any application that has a
// shortcut in Start with an AppUserModelId; this is great if you do a plugin
// for an app, so you do not really need a shortcut of your own to clutter the
// Start menu.
// Also, the COM activator is required for buttons on the toast.
// To see the IDs for installed apps, run "Get-StartApps" in PowerShell.
//
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#include <initguid.h>
#include <roapi.h>
#include <Windows.ui.notifications.h>
#define Done(code) ExitProcess(code)
// Choose whether you would like to compile with/without the standard library
#define INCLUDE_DEFAULTLIB
#undef INCLUDE_DEFAULTLIB
#ifdef INCLUDE_DEFAULTLIB
#include <stdio.h>
#define __wcslen wcslen
#else
#pragma comment(linker, "/NODEFAULTLIB")
#pragma comment(linker, "/ENTRY:wWinMain")
void printf(char* b, ...) {}
DWORD __wcslen(WCHAR* pszText)
{
WCHAR* pszCurrent = pszText;
while (pszCurrent[0])
{
pszCurrent++;
}
return pszCurrent - pszText;
}
#endif
// UUIDs obtained from <windows.ui.notifications.h>
//
// ABI.Windows.UI.Notifications.IToastNotificationManagerStatics
// 50ac103f-d235-4598-bbef-98fe4d1a3ad4
DEFINE_GUID(UIID_IToastNotificationManagerStatics,
0x50ac103f,
0xd235, 0x4598, 0xbb, 0xef,
0x98, 0xfe, 0x4d, 0x1a, 0x3a, 0xd4
);
//
// ABI.Windows.Notifications.IToastNotificationFactory
// 04124b20-82c6-4229-b109-fd9ed4662b53
DEFINE_GUID(UIID_IToastNotificationFactory,
0x04124b20,
0x82c6, 0x4229, 0xb1, 0x09,
0xfd, 0x9e, 0xd4, 0x66, 0x2b, 0x53
);
// UUIDs obtained from <windows.data.xml.dom.h>
//
// ABI.Windows.Data.Xml.Dom.IXmlDocument
// f7f3a506-1e87-42d6-bcfb-b8c809fa5494
DEFINE_GUID(UIID_IXmlDocument,
0xf7f3a506,
0x1e87, 0x42d6, 0xbc, 0xfb,
0xb8, 0xc8, 0x09, 0xfa, 0x54, 0x94
);
//
// ABI.Windows.Data.Xml.Dom.IXmlDocumentIO
// 6cd0e74e-ee65-4489-9ebf-ca43e87ba637
DEFINE_GUID(UIID_IXmlDocumentIO,
0x6cd0e74e,
0xee65, 0x4489, 0x9e, 0xbf,
0xca, 0x43, 0xe8, 0x7b, 0xa6, 0x37
);
// This is the AppUserModelId of an application from the Start menu
// If you don't supply a valid entry here, the toast will have no icon
// The ID below is for Mozilla Thunderbird
#define APP_ID L"D78BF5DD33499EC2"
HRESULT CreateXmlDocumentFromString(
const wchar_t* xmlString,
__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument** doc
)
{
HRESULT hr = S_OK;
HSTRING_HEADER header_IXmlDocumentHString;
HSTRING IXmlDocumentHString;
hr = WindowsCreateStringReference(
RuntimeClass_Windows_Data_Xml_Dom_XmlDocument,
(UINT32)__wcslen(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument),
&header_IXmlDocumentHString,
&IXmlDocumentHString
);
if (FAILED(hr))
{
printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
return hr;
}
if (IXmlDocumentHString == NULL)
{
return E_POINTER;
}
IInspectable* pInspectable;
hr = RoActivateInstance(IXmlDocumentHString, &pInspectable);
if (SUCCEEDED(hr))
{
hr = pInspectable->lpVtbl->QueryInterface(
pInspectable,
&UIID_IXmlDocument,
doc
);
pInspectable->lpVtbl->Release(pInspectable);
}
else
{
printf("%s:%d:: RoActivateInstance\n", __FUNCTION__, __LINE__);
return hr;
}
__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO* docIO;
(*doc)->lpVtbl->QueryInterface(
(*doc),
&UIID_IXmlDocumentIO,
&docIO
);
if (FAILED(hr))
{
printf("%s:%d:: QueryInterface\n", __FUNCTION__, __LINE__);
return hr;
}
HSTRING_HEADER header_XmlString;
HSTRING XmlString;
hr = WindowsCreateStringReference(
xmlString,
(UINT32)__wcslen(xmlString),
&header_XmlString,
&XmlString
);
if (FAILED(hr))
{
printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
docIO->lpVtbl->Release(docIO);
return hr;
}
if (XmlString == NULL)
{
return E_POINTER;
}
hr = docIO->lpVtbl->LoadXml(docIO, XmlString);
docIO->lpVtbl->Release(docIO);
return hr;
}
int WINAPI wWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nShowCmd
)
{
#ifdef INCLUDE_DEFAULTLIB
FILE* conout;
AllocConsole();
freopen_s(&conout, "CONOUT$", "w", stdout);
#endif
HRESULT hr = S_OK;
hr = RoInitialize(RO_INIT_MULTITHREADED);
if (FAILED(hr))
{
printf("%s:%d:: RoInitialize\n", __FUNCTION__, __LINE__);
goto exit0;
}
HSTRING_HEADER header_AppIdHString;
HSTRING AppIdHString;
hr = WindowsCreateStringReference(
APP_ID,
(UINT32)__wcslen(APP_ID),
&header_AppIdHString,
&AppIdHString
);
if (FAILED(hr))
{
printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
goto exit1;
}
if (AppIdHString == NULL)
{
hr = E_POINTER;
goto exit1;
}
__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument* inputXml = NULL;
hr = CreateXmlDocumentFromString(
L"<toast activationType=\"protocol\" launch=\"imsprevn://0\" duration=\"long\">\r\n"
L" <visual>\r\n"
L" <binding template=\"ToastGeneric\">\r\n"
L" <text><![CDATA[Hello, world]]></text>\r\n"
L" <text><![CDATA[Click me]]></text>\r\n"
L" <text placement=\"attribution\"><![CDATA[Bottom text]]></text>\r\n"
L" </binding>\r\n"
L" </visual>\r\n"
L" <audio src=\"ms-winsoundevent:Notification.Mail\" loop=\"false\" />\r\n"
L"</toast>\r\n"
, &inputXml
);
if (FAILED(hr))
{
printf("%s:%d:: CreateXmlDocumentFromString\n", __FUNCTION__, __LINE__);
goto exit1;
}
HSTRING_HEADER header_ToastNotificationManagerHString;
HSTRING ToastNotificationManagerHString;
hr = WindowsCreateStringReference(
RuntimeClass_Windows_UI_Notifications_ToastNotificationManager,
(UINT32)__wcslen(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager),
&header_ToastNotificationManagerHString,
&ToastNotificationManagerHString
);
if (FAILED(hr))
{
printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
goto exit2;
}
if (ToastNotificationManagerHString == NULL)
{
printf("%s:%d:: ToastNotificationManagerHString == NULL\n", __FUNCTION__, __LINE__);
hr = E_POINTER;
goto exit2;
}
__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics* toastStatics = NULL;
hr = RoGetActivationFactory(
ToastNotificationManagerHString,
&UIID_IToastNotificationManagerStatics,
(LPVOID*)&toastStatics
);
if (FAILED(hr))
{
printf("%s:%d:: RoGetActivationFactory\n", __FUNCTION__, __LINE__);
goto exit2;
}
__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier* notifier;
hr = toastStatics->lpVtbl->CreateToastNotifierWithId(
toastStatics,
AppIdHString,
&notifier
);
if (FAILED(hr))
{
printf("%s:%d:: CreateToastNotifierWithId\n", __FUNCTION__, __LINE__);
goto exit3;
}
HSTRING_HEADER header_ToastNotificationHString;
HSTRING ToastNotificationHString;
hr = WindowsCreateStringReference(
RuntimeClass_Windows_UI_Notifications_ToastNotification,
(UINT32)__wcslen(RuntimeClass_Windows_UI_Notifications_ToastNotification),
&header_ToastNotificationHString,
&ToastNotificationHString
);
if (FAILED(hr))
{
printf("%s:%d:: WindowsCreateStringReference\n", __FUNCTION__, __LINE__);
goto exit4;
}
if (ToastNotificationHString == NULL)
{
printf("%s:%d:: ToastNotificationHString == NULL\n", __FUNCTION__, __LINE__);
hr = E_POINTER;
goto exit4;
}
__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory* notifFactory = NULL;
hr = RoGetActivationFactory(
ToastNotificationHString,
&UIID_IToastNotificationFactory,
(LPVOID*)&notifFactory
);
if (FAILED(hr))
{
printf("%s:%d:: RoGetActivationFactory\n", __FUNCTION__, __LINE__);
goto exit4;
}
__x_ABI_CWindows_CUI_CNotifications_CIToastNotification* toast = NULL;
hr = notifFactory->lpVtbl->CreateToastNotification(notifFactory, inputXml, &toast);
if (FAILED(hr))
{
printf("%s:%d:: CreateToastNotification\n", __FUNCTION__, __LINE__);
goto exit5;
}
hr = notifier->lpVtbl->Show(notifier, toast);
if (FAILED(hr))
{
printf("%s:%d:: Show\n", __FUNCTION__, __LINE__);
goto exit6;
}
// We have to wait a bit for the COM threads to deliver the notification
// to the system, I think
// Don't know any better, yielding (Sleep(0)) is not enough
Sleep(1);
exit6:
toast->lpVtbl->Release(toast);
exit5:
notifFactory->lpVtbl->Release(notifFactory);
exit4:
notifier->lpVtbl->Release(notifier);
exit3:
toastStatics->lpVtbl->Release(toastStatics);
exit2:
inputXml->lpVtbl->Release(inputXml);
exit1:
RoUninitialize();
exit0:
Done(hr);
}
@Danyfirex
Copy link

Danyfirex commented Jan 13, 2022

Hello @valinet nice code, what VS version did you used to compile this? I'm trying to compile it but I get LNK2001: unresolved external symbol __RTC_InitBase issue.

Now. trying to implement action handler.

@ednt
Copy link

ednt commented May 23, 2022

Have you managed the action stuff in plain C ?

@valinet
Copy link
Author

valinet commented May 23, 2022

@opsJson
Copy link

opsJson commented May 22, 2023

I get a ton of errors when I include the <windows.ui.notifications.h> header, probably because it contains C++ code like namespaces and interfaces.

Is there a way to compile the code without a C++ compiler?

@valinet
Copy link
Author

valinet commented May 22, 2023

Open the windows.ui.notifications.h in a text editor. If you see #ifdefs _cplusplus there, then the header still contains C-style interfaces, so it should compile with the C compiler of MSVC as well (when I wrote this, it did, that's why it is suffixed .c - the code is written in C, not C++ (notice the lpVtbls, for e.g.). If it throws error, then the compiler probably compiles it as C++. Make sure it is suffixed .c, MSVC uses the file extension to decide whether to compile the code as C or C++.

@opsJson
Copy link

opsJson commented May 22, 2023

I'm actually using mingw gcc to compile, not MSVC.

indeed the headers have C-style interfaces, the errors were being generated because gcc does not define any function parameter annotations, as this is a microsoft mechanism

I just got all the #defines from this site and now it's compilling!

however, I tried to run it here, and nothing happens =/

command I used

gcc main.c -o main -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.22000.0\winrt" -L"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x64" -lruntimeobject -w

@opsJson
Copy link

opsJson commented May 22, 2023

now it worked, I forgot that I turned off Windows notifications myself LOL

interestingly, it only works if I compile in 64bits and give the 32bits libraries directory.

if I compile 32 bits, with directory 32 or 64, it doesn't work.
if I compile 64 bits, with 64 bits directory, it doesn't work.
if I compile 64 bits, with 32 bits directory, then it works.

command that worked:

gcc main.c -o main -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.22000.0\winrt" -L"C:\Program Files (x86)\Windows Kits\10\Lib \10.0.22000.0\um\x86" -lruntimeobject -w -g

Warnings:

C:/Program Files (x86)/Dev-Cpp/MinGW64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.2/../../../../x86_64-w64- mingw32/bin/ld.exe: skipping incompatible C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86/runtimeobject.lib when searching for -lruntimeobject
C:/Program Files (x86)/Dev-Cpp/MinGW64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.2/../../../../x86_64-w64- mingw32/bin/ld.exe: skipping incompatible C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86/advapi32.lib when searching for -ladvapi32
C:/Program Files (x86)/Dev-Cpp/MinGW64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.2/../../../../x86_64-w64- mingw32/bin/ld.exe: skipping incompatible C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86/shell32.lib when searching for -lshell32
C:/Program Files (x86)/Dev-Cpp/MinGW64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.2/../../../../x86_64-w64- mingw32/bin/ld.exe: skipping incompatible C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86/user32.lib when searching for -luser32
C:/Program Files (x86)/Dev-Cpp/MinGW64/bin/../lib/gcc/x86_64-w64-mingw32/4.9.2/../../../../x86_64-w64- mingw32/bin/ld.exe: skipping incompatible C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x86/kernel32.lib when searching for -lkernel32

I get these warnings because I'm compiling without the "-m32" and looking in the 32-bit libraries folder, but somehow it compiles and works.

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