Quickstart: Handling toast activations from Win32 apps in Windows 10


Summary

In Windows 10, we made sure Win32 apps can use the new toast notification features, meaning developers can make their notifications appear in Action Center and utilize interactive toast notifications.

Since Windows 8.1, Win32 apps are able to generate toast notifications using the existing WinRT ToastNotificationManager API just like a modern app. Handling activation from toast notifications, however, was a different story. Modern apps can be notified about user’s interaction with the toast through the Activated event (when the app is running) or via app activation (when the app is not running). However, Win32 apps don’t have the same activation model and cannot be activated like modern apps.

In Windows 10, we wanted Win32 apps to be activated at any time from a toast, especially when the user clicks on the toast notification from inside Action Center, now that notifications can stay in Action Center for days until the user sees them. So instead of requiring Win32 apps to use an event handler for activation (IToastNotification::add_Activated) that only works while the app is running, we now allow Win32 apps to use a COM server that can be invoked when users interact with the toast at any time. 

Prerequisite

A shortcut to your app is required to be installed to the Start Screen for Win32 app to send and handle toast notifications. This shortcut needs to contain 2 pieces of information:

  1. A System.AppUserModel.ID – this is used to provide an identity of the app to the notification platform for registration as a notification endpoint. This is the same prerequisite for Win32 apps to send toast notifications to Windows 8 and 8.1.

  2. A CLSID that points to your COM component that implements INotificationActivationCallBack.

How to create the shortcut

You can create the shortcut statically using WXS or dynamically with C++ as seen below…

<Shortcut Id="ApplicationStartMenuShortcut"
          Name="Desktop Toasts Sample"
          Description="Sends basic toasts"
          Target="[AppRootDirectory]DesktopToastsSample.exe"
          WorkingDirectory="AppRootDirectory">
  <!-- Set the AppID in order to get toasts to work -->
  <ShortcutProperty Key="System.AppUserModel.ID"
                    Value="Microsoft.Samples.DesktopToasts" />
  <!-- Set the ToastActivatorCLSID in order to get notifications working in Action Center -->
  <ShortcutProperty Key="System.AppUserModel.ToastActivatorCLSID"
                    Value="{23A5B06E-20BB-4E7E-A0AC-6982ED6A6041}" />
</Shortcut>
// Install the shortcut for the current running process
HRESULT DesktopToastsApp::InstallShortcut(_In_ PCWSTR 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))
            {
                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))
                        {
                            PropVariantClear(&appIdPropVar);
                            appIdPropVar.vt = VT_CLSID;
                            appIdPropVar.puuid = const_cast<CLSID*>(&__uuidof(NotificationActivator));
                            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;
}

Registering COM activator from Action Center to call your Win32 app

COM Interface to implement:

A Win32 component that participates with Action Center will need to create a COM component that exposes the following interface:

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); 
};

Here is how to tell COM how to start your app when needed:

reg add “HKEY_CURRENT_USER\SOFTWARE\Classes\CLSID\{23A5B06E-20BB-4E7E-A0AC-6982ED6A6041}\LocalServer32” /d C:\Users\micgou\Desktop\DesktopToastsSample.exe 

When your app starts, register your COM object:

Module::GetModule().RegisterObjects();

Sending and handling standard toast notifications

Developer implements INotificationActivationCallback to just respond to non-interactive standard toasts by bringing their app to the foreground.  If the app wasn’t running, the app will be launched by COM via its local server registration.

The app uses the WinRT toast API to create the toast.

HStringReference toastXML(
    L"<toast>"
    L" <visual>"
    L" <binding template=\"ToastGeneric\">"
    L" <text>A standard toast.</text>"
    L" </binding>"
    L" </visual>"
    L"</toast>");

...

hr = DocumentIO->LoadXml(toastXML.Get());

...

ComPtr<IToastNotification> toast;
hr = factory->CreateToastNotification(xml, &toast);

...

hr = notifier->Show(toast.Get());

class DECLSPEC_UUID("23A5B06E-20BB-4E7E-A0AC-6982ED6A6041") NotificationActivator
    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
    {
        return DesktopToastsApp::GetInstance()->SetMessage(L"NotificationActivator - The user clicked on the toast.");
    }
};

Sending and handling interactive toast notifications

Developer implements INotificationActivationCallback to just respond to interactive toasts without any additional UI. The work just happens in the background. The UI could be waiting for the callback to return by showing indeterminate progress UI, so the callback should return at the appropriate time to indicate that the action is complete.

[Note] You need to register a non-UI process as the COM server if you want to respond to the interactive toast without showing UI, such as quick replying to a message without bringing up your app to the foreground. 

HStringReference toastXML(
    L"<toast>"
    L" <visual>"
    L" <binding template='ToastGeneric'>"
    L" <text>Press Reply</text>"
    L" </binding>"
    L" </visual>"
    L" <actions>"
    L" <action content='reply'"
    L" arguments='replyToComment'"
    L" activationKind='Background'/>"
    L" </actions>"
    L"</toast>");

...

hr = DocumentIO->LoadXml(toastXML.Get());

...

ComPtr<IToastNotification> toast;
hr = factory->CreateToastNotification(xml, &toast);

...

hr = notifier->Show(toast.Get());



HRESULT NotificationActivator::Activate(PCWSTR /*appUserModelId*/, PCWSTR invokedArgs,
    const NOTIFICATION_USER_INPUT_DATA* data, ULONG count)
{
    if (invokedArgs == nullptr)
    {
      // Start my app or just do nothing because COM started the app already.
    }
    else if (::wcscmp(invokedArgs, L"replyToComment") == 0)
    {
        ASSERT(count == 1);
        ASSERT(::wcscmp(data[0].Key, L"replyToComment") == 0);
               
        DoWorkToReply(data[0].Data);
    }

    return S_OK;
}

Resources

Comments (50)

  1. Kiran says:

    As per this article, After Adding the COM server to my Win32 app, the toast is being persisted in Action center. But on tapping the toast either my App's or COM server's (NotificationActivator) Activate method is not being triggered.

    I am using DOM XML API to display the toast as per the Win32 C++ sample:

    code.msdn.microsoft.com/…/sending-toast-notifications-71e230a2

    I am not using any WinRT API in my Win32 app to display the toast notification.

    To register COm object when my App starts, I tried to call RegisterObjects() method as described in this article.

    But there is no RegisterObjects() method provided by MicroSoft::WRL::Module class.

    Instead I have used Module<InProc>::GetModule().RegisterCOMObject(…) method to register the COM object.

    Please suggest if something i have missed.

  2. Kiran says:

    After adding the COM server to my Win32 app, the toast is persisted in Action center.

    But My Win32 application is being launched for every few minutes even though there is no user interaction with the toast in Action center. Is it expected behaviour?

  3. Kiran says:

    Any update on my previous queries? Please let me know if you need any details from me.

  4. andrewbares7 says:

    @Kiran – I've told Lei to follow up, he'll look into this when he has a chance.

  5. Kiran says:

    @Lei: Could you Please confirm whether it is usage problem or a bug in APIs?

  6. andrewbares7 says:

    Kiran, can you provide the actual code of your COM server, and actual code snippets of how you're registering the COM server? Lei's working on asking whether anyone knows of issues like you mentioned (we are only experts at the normal WinRT API's but we'll attempt to help). It would help us solve your problem if we could see your actual code. You can post code on GitHub GIST and link it here.

  7. Lei says:

    Is there actual code available in GitHub?  I modified DesktopToastsSample project, but could not make it works.

  8. Kiran says:

    @andrewbares7:

    As I am unable to upload the complete zip file of my COM Server sample, I have posted few files of COM server and Desktop toast app implementation.

    The GitHub GIST link for the same is: gist.github.com/…/512f658da1847044a7b6

    Please suggest if anything missed.

  9. Kiran says:

    @andrewbares7:  Any update on my last post?

  10. Kiran says:

    This issue has become a blocker for my project work.

    Today I have observed following behaviour with WRL COM based toast notifications from Desktop Application:

    1. When I dismiss the Toast notification (The user dismissed the toast notification) from Notification area, the toast notification is pushed into Action center and it is persisted there. But the Activate event handler is not triggered when we tap on the notification inside Action center even though my app is still running.

    This behaviour is seen without adding any COM server to my desktop app.

    2. When the Toast is dismissed after time out (TimedOut) then the toast is not persisting in Action center.

    Atleast it should be persisted in Action center untill the app is running in backgroud.

    Why it is behaving differently in each dismissal case?

  11. Kylan says:

    Hello, what is the type of variables DocumentIO and factory?

  12. @Kiran says:

    Thanks for your sample code!  By doing some tests, DesktopToastsSample.exe could be launched after clicking the toast notification, but the exported interface Activate seems not called.  Do you have the same issue?  Is there anything I missed?

  13. Kiran says:

    Yes. I am having the same issue that you observed.

  14. Calvin Lin says:

    Here is my new finding:

    1. After tracing the code in debug mode, Module<OutOfProc>::GetModule().RegisterObjects() will not register the COM object CToastActivator.  One more code line, CoCreatableClass(CToastActivator);, is needed.

    2. After adding the line of code, CToastActivator ::Activate was still not called.  After checking system event log, here is the error :

    The server {23A5B06E-20BB-4E7E-A0AC-6982ED6A6041} did not register with DCOM within the required timeout.

    Is there any idea about the error of timeout?

  15. Calvin Lin says:

    Hi, Kiran

    I fixed the issue and make DesktopToastsSample.exe works.  The issue was caused by without calling Module<OutOfProc>::GetModule().UnregisterObjects();.

    I post my source code in gist.github.com/…/8701312744f94bcd8701

  16. Calvin Lin says:

    Hi, Kiran

    Sorry, I forgot to mention that if you using Windows 10 SDK, the idl for interface INotificationActivationCallback is not necessary.  The interface is available in %Win10SDK_Path%IncludeumNotificationActivationCallback.h.

  17. Kiran says:

    @Calvin Lin: Thanks for sharing sample code.

    From your sample,

    hr = xmlDocument->LoadXml(toastXML.Get()); is always returning "XML_E_BADSTARTNAMECHAR" (0xC00CE504) error code for me. What could be the problem?

  18. Calvin Lin says:

    @Kiran

    Do you compile the sample program with Win 10 SDK?  The format of the toast in my DesktopToastsSample is for adaptive toast which is new of Windows 10.  I compiled the sample program with Win 10 SDK.  Maybe it could be the root cause.  Or You could try the original sample code to launch toast.

  19. Kiran says:

    @Calvin Lin, I tried with the original sample code to launch the toast. And I made the changes to the sample code as per your sample except the CreateToastXML method.

    Still  on tapping the toast, CToastActivator::Activate is not called for me.

  20. Calvin Lin says:

    @Kiran, By testing my sample, I found the COM server was not stable.  Sometimes, the COM server got crashed when action center tried to call CToastActivator::Activate.  I still try to find out the root cause.  You could check application event log and system event log.  Here are the steps how I verified the problem:

    1. Trigger a toast by DesktopToastsSample.exe

       – Verify whether the title of toast content in Action Center is APP ID or shortcut link name

       – If the title display APP ID, that means the created short is not correct

    2. Check task manager whether DesktopToastsSample.exe was launched after clicking the toast

       – If the register key for the COM server was not created correctly, DesktopToastsSample.exe will not be launched

    3. If there was not response after clicking the toast, I will check Windows event logs.

    I added some logs by OutputDebugString and used DbgView to check the debug logs to trace the problem too.

  21. kiran says:

    @Calvin Lin, I found the root cause for the app crash.

    The app crash is happening inside the ntdll.dll (heap corruption). It is because of the below line inside Release() method of CToastActivator:

    if (nRefCount == 0) delete this;

    After removing the above line, The CToastActivate::Activate() is called for me when tapping the toast notification.  

    Thanks a lot for your support in resolving this issue.

  22. Kiran says:

    @Calvin Lin, The  CToastActivate::Activate() is called when I tap on the Toast notification. But it is not called when I click on the foreground  action button (Adaptive toast) on the Toast notification. Is there any other Activate override function to handle the actions?

    And When i try to Activate the instance of ToastNotificationHistory as:

    ComPtr<ABI::Windows::UI::Notifications::IToastNotificationHistory> history;

    HRESULT hr = Windows::Foundation::ActivateInstance(StringReferenceWrapper(RunTimeClass_Windows_UI_notifications_ToastNotificationHistory).Get(), &history);

    It is returning HRESULT error as "REGDB_E_CLASSNOTREG".  How can i use ToastNotificationHistory in desktop app?

  23. Michael says:

    I have really hard time understanding why this toast system had to be made so complex.

    I mean look at the simplicity of:

    int WINAPI MessageBox(

     _In_opt_ HWND    hWnd,

     _In_opt_ LPCTSTR lpText,

     _In_opt_ LPCTSTR lpCaption,

     _In_     UINT    uType

    );

    compared to this toast monstrosity. Over-engineered APIs like this are the reason for software bloat.

    Also what's with the application shortcut? Why should a UI message have anything to do with a shortcut file?

    Why couldn't there be just a registry entry somewhere where to append your application identifier and CLSID?

    Could you even please bother to provide some rich code examples how to initialize and run the COM server within a dynamic link library or an executable? These small code snippets are hard to understand.

  24. Elena says:

    Could you please upload the entire code for this? Thanks.

  25. Elena says:

    Is the SupressPopup functionality available in C++?

    Also, could you please give working example of put_ExpirationTime? I can't figure out the parameter type for this function.

  26. Tony says:

    Can someone post a complete, working code sample somewhere.  Thanks

  27. Martin says:

    So if I understood correctly, nobody can make it work in c++?

    We're stuck with .NET/cs?

  28. Tony says:

    Can someone please post an entire Visual Studio solution?  Even if it doesn't work, it'd be better than this.

    I can't get these bits and pieces from here, github, and pastebin to compile.

    Thanks

  29. Tony says:

    Martin, you have a .NET/C# solution that works?  Please post it. Preferably a complete, ready to compile, sample.

  30. Martin says:

    No I don't. I thought there was, but apparently there isn't …

    There's only universal app working examples. But that's very restrictive…

  31. Martin says:

    BTW, commenting

    "if (nRefCount == 0) delete this;"

    is stupid. Who removes deletes?

    Maybe changing

    m_nRefCount = 0;

    to

    m_nRefCount = 1;

    would be better?

  32. Bill says:

    I'm getting an assertion failure on this:

    Module<OutOfProc>::GetModule().RegisterObjects();

    more precisely:

    Module<OutOfProc>::GetModule()

    I'm working with Calvin Lin's code sample.

    Any Ideas?

  33. Bilal says:

    Hello , who do that on c# , and thank you

  34. Elena says:

    The Activated event is missing UserInput, while INotificationActivationCallback::Activate method does have the user input data, but only works with string args defined in the notification's XML. In real life, notifications are fired by various modules which attach complex objects to the notification, that they expect the be used once the user clicks the toast. This is impossible to do with just string args. It would have been possible with UserInput in the Activated event, but it's missing. The INotificationActivationCallback::Activate method, with all its ActionCenter and dead application features is useless for the everyday purpose when we just need to implement functionality on a live toast.

    Also, why is there an IToastNotificationActionTriggerDetail interface in 10.0.10586.0 sdk and no documentation available online for Win32 apps? Why doesn't the IInspectable argument of Activated implement this interface?

  35. leixu2046 says:

    Hi guys, sincerely apologize for missing a full sample and ignoring some of the comments previously – the feature was shipped by a different team and due to preparation for some work in the upcoming Windows update and Build 2016, publishing the sample was delayed. The sample that works e2e is now published on GitHub here:
    https://github.com/WindowsNotifications/desktop-toasts

    Please follow up with questions and thanks again.

    1. Elena says:

      Thank you! The code in RegisterActivator()/UnregisterActivator() fixed a very troubling bug!

    2. Elena says:

      Did you forget to include notificationactivationcallback.h?

      Also, regarding your comment “When the user clicks or taps on the toast, the registered COM object is activated, and the Activated event is raised. There is no guarantee which will happen first. If the COM object is activated first, then this message may not show.” From my experiments, I noticed that INotificationActivationCallback::Activate always occurs before the Activated handler (added via add_Activated). Activated gives much more possibilities than INotificationActivationCallback::Activate, but lacks the UserInput. Do you think that we will have a guarantee in the future about how these two handlers will be called?

    3. Coder143 says:

      Thanks for the sample, it helps a lot, but there seems to be a file missing in the cpp example (#include “notificationactivationcallback.h”)

    4. Mark Femal says:

      This blog post was absolutely _critical_ to extend and understand how to make a winforms legacy application can use the action center and toast notifications locally on a PC so thank you. However, I have some basic questions:

      1. Is it possible to use the method here (which I have working) to extend to push notifications via WNS or do you need to rewrite the whole application (to UWP) or wait for Project Centennial (my company does not favor either of those methods). Not having a path to use WNS for legacy applications is inconvenient.

      2. The methods noted here, don’t seem to extend to tile updates we try to do locally, just toast notifications and action center? At least its not working for me… (this again appears to be tied to the manifest definitions a full UWP app has for tiles)

      1. leixu2046 says:

        Thank you Mark, unfortunately, you will need UWP or Centennial to use WNS push service as well as adding live tiles. Could you elaborate on why your company doesn’t favor the Centennial approach?

  36. Elena says:

    When the number of notifications in Action Center exceeds 20, I get an extra notification saying “More notifications”. If I click on it, my app opens and all the other 20 toasts from Action Center disappear. I wasn’t able to find any documentation on this. Can you please tell me if this is expected behavior?

    1. leixu2046 says:

      Hi Elena,

      This is expected behavior. We automatically add a generic notification that says “more notification” when you have more than 20 notifications. It pretty much sends a message to the user that “there is a more notifications you missed than we can display here so please go into the app and find out what you missed”. Can you tell me if you expect to see this happen a lot for users using your app?

      1. Elena says:

        In the latest Windows 10 release, that nasty notification disappeared and now the toasts are FIFO in Action Center – the old ones disappear as the new ones replace them one by one. To answer your question – my app is fine with 20 notifications, there’s no need for more.

  37. John Preston says:

    In a messaging app each time a new message comes in chat I show toast notification and hold pointers to all IToastNotifications shown.
    When the user clicks the notification the chat is opened. He also can open it by himself. Also this chat can be marked as read from a different device.

    If any of those three events occur all the notifications from that chat are removed using IToastNotifier::Hide method. So I have some questions:

    1. If the app gets relaunched there is no way to enumerate the notifications for hiding all of them related to a specific chat – user should have to deal with all the notifications that were left in the action center manually? Will they at least hide from the action center using IToastNotifier::Hide before the app was closed, but after the notification went from screen to the action center?

    2. Starting with Windows 10 anniversary update I get reports and logs, that IToastNotifier::Hide sometimes takes up to two minutes (!) before returning. Also I got one debug dump where a call to IToastNotifier::Hide resulted in an inside call to Activated event handler of some (other or the one that I was hiding, I’m not sure) IToastNotification.

    Is this a valid and correct way to work with the notifications? Why IToastNotifier::Hide takes sometimes so huge amount of time (the app freezes) and sometimes calls the Activated and maybe other handlers inside itself? What is the right way to deal with it? Is there a way to hide the notifications from my app that went to the action center already?

    1. Shirouzu says:

      >Starting with Windows 10 anniversary update I get reports and logs, that IToastNotifier::Hide sometimes takes up to two minutes (!) before returning.

      The same problems have occurred in multiple Win10 anniv PCs that use my software.

      My software name is “IP Messenger for Win” that is open source software.
      And my user send to me a detail-log and it indicates…

      1. create IToastNotifier
      2. call IToastNotifier::Show()
      (1sec – 5sec later)
      3. call IToastNotifier::Hide()
      4. release IToastNotifier

      It is okay to run this combination several times.
      But it runs many times(10-20times) with few timing gap, IToastNotifier::Hide() spends just 120sec.
      If it occurs, Hide() always spends 120sec.

      1. Shirouzu says:

        I confirmed the Hide() freeze problem can be avoided by using multi-thread & COM(MTA=COINIT_MULTITHREADED).
        (I think it is a Win10 Anniv. bug, so the following way is only a workaround.)

        If you want to create a new toast,
        1. create a new thread and CoInitialize(NULL, COINIT_MULTITHREADED);
        2. create a toast and relative object(factory/event etc) in this toast thread.
        (1 new toast = 1 new thread…it is a very rich way. I don’t like it, but I think there is no other way to avoid it…)

        If you want to hide this toast in main/other thread,
        3. send/notify a hide-request to this toast thread. (in some way)
        4. Call Hide() in this toast thread.

        Note.
        5. In MTA mode, Click or Timeout event are issued by NOT this toat(and main) thread. (It is WinRT/COM internal thread?)

        Bye the way, strangely, using multi-thread with MTA, it seems Hide() becomes not to freeze in this toast thread (not only main/other thread).
        So I doubt this problem is relative to WinRT/COM(STA=COINIT_APARTMENTTHREADED) synchronize mechanism.

        1. John Preston says:

          Still no other workaround found? My users are still complaining, I had to disable Windows toast notifications by default in my software (use custom self made notifications by default).

          I’m afraid to spawn a thread for each notification, if they start to leak because for some reason none of the callbacks get called or smth like that it will be very bad.

          leixu2046 perhaps you do have some thoughts / reports / information? This is a rare (I cannot reproduce that myself) but regularly seen and very bad behaviour, I have several issues on github.

  38. Maria Webster says:

    I see that up on GitHub, there is a fully working version of this in C# but the C++ DesktopToastsSample still uses the old Template model. Is there a FULL working version in C++ anywhere?
    Also, I would prefer not to have to hook into ActionCenter. All I really want to do is to use the new GenericTemplate TemplateType to create a more specific type of toast. The only action I need users to do is to click on the toast to acknowledge it.

  39. Andrew Barns says:

    Who’s giving these posts 5 star ratings? This doesn’t even work.

  40. Elena says:

    Hi,

    I’d like to know if there’s a way for my app to be notified when the user sets the Notifications (either Global or per app) to Off from Windows Settings .

    Thanks!

Skip to main content