- 
      
 - 
        
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 #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++.
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 -wnow 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 -gWarnings:
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.