-
-
Save valinet/3283c79ba35fc8f103c747c8adbb6b23 to your computer and use it in GitHub Desktop.
// 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, | |
¬ifier | |
); | |
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*)¬ifFactory | |
); | |
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); | |
} |
Have you managed the action stuff in plain C ?
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?
Open the windows.ui.notifications.h
in a text editor. If you see #ifdef
s _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 lpVtbl
s, 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++.
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
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.
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.