Windows Toastnotification without PowerShell

Just starting out? Need help? Post your questions and find answers here.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Windows Toastnotification without PowerShell

Post by infratec »

Hi,

since it is not always allowed to execute PS scripts, I'm searching for a way to directly access the windows ToastNotification Class.
But since I'm not familar with implementing Interfaces to OOP stuff, I'm a bit lost.

Code: Select all

$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($toastXML)
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("BlaBla").Show($toast)
Any help is appreciated.
breeze4me
Enthusiast
Enthusiast
Posts: 511
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Windows Toastnotification without PowerShell

Post by breeze4me »

Try this.
https://gist.github.com/valinet/3283c79 ... c8adbb6b23

Code: Select all

; https://gist.github.com/valinet/3283c79ba35fc8f103c747c8adbb6b23

Structure HSTRING
  unused.i
EndStructure

Structure HSTRING_HEADER
  StructureUnion
    *Reserved1
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      Reserved2.c[24]
    CompilerElse
      Reserved2.c[20]
    CompilerEndIf
  EndStructureUnion
EndStructure


;winrt\inspectable.h
Interface IInspectable Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
EndInterface

;winrt\Windows.Data.Xml.Dom.h
Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
  get_Doctype(*value)
  get_Implementation(*value)
  get_DocumentElement(*value)
  CreateElement(tagName, *newElement)
  CreateDocumentFragment(*newDocumentFragment)
  CreateTextNode(hStringData, *newTextNode)
  CreateComment(hStringData, *newComment)
  CreateProcessingInstruction(target, hStringData, *newProcessingInstruction)
  CreateAttribute(name, *newAttribute)
  CreateEntityReference(name, *newEntityReference)
  GetElementsByTagName(tagName, *elements)
  CreateCDataSection(hStringData, *newCDataSection)
  get_DocumentUri(*value)
  CreateAttributeNS(*namespaceUri, qualifiedName, *newAttribute)
  CreateElementNS(*namespaceUri, qualifiedName, *newElement)
  GetElementById(elementId, *element)
  ImportNode(*node, deep, *newNode)
EndInterface

;winrt\Windows.Data.Xml.Dom.h
Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
  LoadXML(xml)
  LoadXmlWithSettings(xml, *loadSettings)
  SaveToFileAsync(*file, *asyncInfo)
EndInterface

;winrt\windows.ui.notifications.h
Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
  CreateToastNotifier(*result)
  CreateToastNotifierWithId(applicationId, *result)
  GetTemplateContent(type, *result)
EndInterface

;winrt\windows.ui.notifications.h
Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
  Show(*notification)
  Hide(*notification)
  get_Setting(*value)
  AddToSchedule(*scheduledToast)
  RemoveFromSchedule(*scheduledToast)
  GetScheduledToastNotifications(*result)
EndInterface

;winrt\windows.ui.notifications.h
Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
  CreateToastNotification(*content, *value)
EndInterface

;winrt\windows.ui.notifications.h
Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotification Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
  get_Content(*value)
  put_ExpirationTime(*value)
  get_ExpirationTime(*value)
  add_Dismissed(*handler, *token)
  remove_Dismissed(token)
  add_Activated(*handler, *token)
  remove_Activated(token)
  add_Failed(*handler, *token)
  remove_Failed(token)
EndInterface

#RO_INIT_MULTITHREADED = 1

#RuntimeClass_Windows_Data_Xml_Dom_XmlDocument = "Windows.Data.Xml.Dom.XmlDocument"
#RuntimeClass_Windows_UI_Notifications_ToastNotificationManager = "Windows.UI.Notifications.ToastNotificationManager"
#RuntimeClass_Windows_UI_Notifications_ToastNotification = "Windows.UI.Notifications.ToastNotification"


Prototype ptRoInitialize(initType)
Global RoInitialize.ptRoInitialize

Prototype ptRoUninitialize()
Global RoUninitialize.ptRoUninitialize

Prototype ptRoGetActivationFactory(activatableClassId, iid, *factory)
Global RoGetActivationFactory.ptRoGetActivationFactory

Prototype ptWindowsCreateStringReference(sourceString.s, length.l, *hstringHeader, *string)
Global WindowsCreateStringReference.ptWindowsCreateStringReference

Prototype ptRoActivateInstance(activatableClassId, *instance)
Global RoActivateInstance.ptRoActivateInstance


If OSVersion() < #PB_OS_Windows_10
  Debug "Windows 10 or later is required."
  End
EndIf

Global LibComBase = OpenLibrary(#PB_Any, "combase.dll")
If LibComBase
  RoInitialize = GetFunction(LibComBase, "RoInitialize")
  RoUninitialize = GetFunction(LibComBase, "RoUninitialize")
  WindowsCreateStringReference = GetFunction(LibComBase, "WindowsCreateStringReference")
  RoActivateInstance = GetFunction(LibComBase, "RoActivateInstance")
  RoGetActivationFactory = GetFunction(LibComBase, "RoGetActivationFactory")
EndIf


;for test on Windows 10.
#APP_ID = "Microsoft.InternetExplorer.Default"


Procedure CreateXmlDocumentFromString(xmlString.s, *idoc.Integer)
  Protected Result = #E_FAIL
  Protected *doc.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument
  Protected *docIO.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO
  Protected *pInspectable.IInspectable
  Protected header_IXmlDocumentHString.HSTRING_HEADER, *IXmlDocumentHString.HSTRING
  Protected header_XmlString.HSTRING_HEADER, *XmlString.HSTRING
  
  Result = WindowsCreateStringReference(#RuntimeClass_Windows_Data_Xml_Dom_XmlDocument, Len(#RuntimeClass_Windows_Data_Xml_Dom_XmlDocument), @header_IXmlDocumentHString, @*IXmlDocumentHString)
  If Result <> #S_OK : ProcedureReturn Result :	EndIf
  If *IXmlDocumentHString = 0 : ProcedureReturn #E_POINTER : EndIf
  
  Result = RoActivateInstance(*IXmlDocumentHString, @*pInspectable)
  If Result <> #S_OK : ProcedureReturn Result : EndIf
  If *pInspectable = 0 : ProcedureReturn #E_NOINTERFACE : EndIf
  
  Result = *pInspectable\QueryInterface(?UIID_IXmlDocument, @*doc)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *doc = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = *doc\QueryInterface(?UIID_IXmlDocumentIO, @*docIO)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *docIO = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = WindowsCreateStringReference(xmlString, Len(xmlString), @header_XmlString, @*XmlString)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *XmlString = 0 : Result = #E_POINTER : Goto _Proc_Exit : EndIf
  
  Result = *docIO\LoadXML(*XmlString)
  
  _Proc_Exit:
  If Result = #S_OK
    *idoc\i = *doc
  Else
    If *doc : *doc\Release() : EndIf
  EndIf
  
  If *docIO : *docIO\Release() : EndIf
  If *pInspectable : *pInspectable\Release() : EndIf
  
  ProcedureReturn Result
EndProcedure


Procedure ShowMsg(xml.s)
  Protected header_AppIdHString.HSTRING_HEADER, *AppIdHString.HSTRING
  Protected header_ToastNotificationManagerHString.HSTRING_HEADER, *ToastNotificationManagerHString.HSTRING
  Protected header_ToastNotificationHString.HSTRING_HEADER, *ToastNotificationHString.HSTRING
  Protected *inputXml.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument
  Protected *toastStatics.__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics
  Protected *notifier.__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier
  Protected *notifFactory.__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory
  Protected *toast.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification
  
  If RoInitialize(#RO_INIT_MULTITHREADED) <> #S_OK : ProcedureReturn #E_FAIL : EndIf
  
  If WindowsCreateStringReference(#APP_ID, Len(#APP_ID), @header_AppIdHString, @*AppIdHString) <> #S_OK : Goto _Proc_Exit : EndIf
  If *AppIdHString = 0 : Goto _Proc_Exit : EndIf
  
  If CreateXmlDocumentFromString(xml, @*inputXml) <> #S_OK : Goto _Proc_Exit : EndIf
  If *inputXml = 0 : Goto _Proc_Exit : EndIf
  
  If WindowsCreateStringReference(#RuntimeClass_Windows_UI_Notifications_ToastNotificationManager, Len(#RuntimeClass_Windows_UI_Notifications_ToastNotificationManager),
                                  @header_ToastNotificationManagerHString, @*ToastNotificationManagerHString) <> #S_OK : Goto _Proc_Exit : EndIf
  If *ToastNotificationManagerHString = 0 : Goto _Proc_Exit : EndIf
  If RoGetActivationFactory(*ToastNotificationManagerHString, ?UIID_IToastNotificationManagerStatics, @*toastStatics) <> #S_OK : Goto _Proc_Exit : EndIf
  If *toastStatics = 0 : Goto _Proc_Exit : EndIf
  If *toastStatics\CreateToastNotifierWithId(*AppIdHString, @*notifier) <> #S_OK : Goto _Proc_Exit : EndIf
  If WindowsCreateStringReference(#RuntimeClass_Windows_UI_Notifications_ToastNotification, Len(#RuntimeClass_Windows_UI_Notifications_ToastNotification),
                                  @header_ToastNotificationHString, @*ToastNotificationHString) <> #S_OK : Goto _Proc_Exit : EndIf
  If *ToastNotificationHString = 0 : Goto _Proc_Exit : EndIf
  If RoGetActivationFactory(*ToastNotificationHString, ?UIID_IToastNotificationFactory, @*notifFactory) <> #S_OK : Goto _Proc_Exit : EndIf
  If *notifFactory = 0 : Goto _Proc_Exit : EndIf
  If *notifFactory\CreateToastNotification(*inputXml, @*toast) <> #S_OK : Goto _Proc_Exit : EndIf
  If *toast = 0 : Goto _Proc_Exit : EndIf
  
  Result = *notifier\Show(*toast)
  
  ;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)
  
  _Proc_Exit:
  If *toast : *toast\Release() : EndIf
  If *notifFactory : *notifFactory\Release() : EndIf
  If *notifier : *notifier\Release() : EndIf
  If *toastStatics : *toastStatics\Release() : EndIf
  If *inputXml : *inputXml\Release() : EndIf
  
  RoUninitialize()
  
  ProcedureReturn Result
EndProcedure

xml.s = ~"<toast activationType=\"protocol\" launch=\"imsprevn://0\" duration=\"long\">\r\n" +
        ~"	<visual>\r\n" +
        ~"		<binding template=\"ToastGeneric\">\r\n" +
        ~"			<text><![CDATA[Hello, world]]></text>\r\n" +
        ~"			<text><![CDATA[Click me]]></text>\r\n" +
        ~"			<text placement=\"attribution\"><![CDATA[Bottom text]]></text>\r\n" +
        ~"		</binding>\r\n" +
        ~"	</visual>\r\n" +
        ~"	<audio src=\"ms-winsoundevent:Notification.Mail\" loop=\"false\" />\r\n" +
        ~"</toast>\r\n"



; Toast notification must be enabled to receive the notification message.
ShowMsg(xml)


If LibComBase : CloseLibrary(LibComBase) : EndIf



DataSection
  ;UUIDs obtained from <windows.ui.notifications.h>
  ;ABI.Windows.UI.Notifications.IToastNotificationManagerStatics
  ;50ac103f-d235-4598-bbef-98fe4d1a3ad4
  UIID_IToastNotificationManagerStatics:
  Data.l $50ac103f
  Data.w $d235, $4598
  Data.b $bb, $ef, $98, $fe, $4d, $1a, $3a, $d4
  
  ;ABI.Windows.Notifications.IToastNotificationFactory
  ;04124b20-82c6-4229-b109-fd9ed4662b53
  UIID_IToastNotificationFactory:
  Data.l $04124b20
  Data.w $82c6, $4229
  Data.b $b1, $09, $fd, $9e, $d4, $66, $2b, $53
  
  ;UUIDs obtained from <windows.data.xml.dom.h>
  ;ABI.Windows.Data.Xml.Dom.IXmlDocument
  ;f7f3a506-1e87-42d6-bcfb-b8c809fa5494
  UIID_IXmlDocument:
  Data.l $f7f3a506
  Data.w $1e87, $42d6
  Data.b $bc, $fb, $b8, $c8, $09, $fa, $54, $94
  
  ;ABI.Windows.Data.Xml.Dom.IXmlDocumentIO
  ;6cd0e74e-ee65-4489-9ebf-ca43e87ba637
  UIID_IXmlDocumentIO:
  Data.l $6cd0e74e
  Data.w $ee65, $4489
  Data.b $9e, $bf, $ca, $43, $e8, $7b, $a6, $37
EndDataSection
User avatar
jacdelad
Addict
Addict
Posts: 1431
Joined: Wed Feb 03, 2021 12:46 pm
Location: Planet Riesa
Contact:

Re: Windows Toastnotification without PowerShell

Post by jacdelad »

This is awesome and far above my abilities!
PureBasic 6.04/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/130TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Windows Toastnotification without PowerShell

Post by infratec »

@breeze4me
many thanks for this code.

I also checked the C code on github, but unfortunately the actions are not implemented.
and I need to get events from buttons.
breeze4me
Enthusiast
Enthusiast
Posts: 511
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Windows Toastnotification without PowerShell

Post by breeze4me »

Handling strings in threads seems to be very unstable, so a mutex is used.
The code below seems to work well, but more tests are required.

Edit: The code slightly modified.

Code: Select all

EnableExplicit

; Sending toast notifications from desktop apps
; https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh779727(v=win.10)
; 
; Generally, sending a toast notification from a desktop app is the same as sending it from a Windows Store app. However, you should be aware of these differences and requirements:
;
; - The app must have a shortcut installed (though not necessarily pinned) to the Start screen or in the Apps view
; - The shortcut must have an AppUserModelID
; - Desktop apps cannot schedule a toast
; - All toasts raised by desktop apps are the same system-defined color
; - Desktop apps cannot use web images
; - Desktop app notifications will not appear on the lock screen


; How to enable desktop toast notifications through an AppUserModelID
; https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/hh802762(v=vs.85)

; Application User Model IDs (AppUserModelIDs)
; https://docs.microsoft.com/en-us/windows/win32/shell/appids

;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.
#App_ID = "MyCompany.TestApp.TestSubProduct"

; Unique CLSID of your app.
#App_CLSID = "{394391cf-093b-4cb1-895b-5505ee760cc2}"

;Set a name to be used as the shortcut file(.lnk) name and the toast notification title.
#App_Name = "My Toast Notification Test App"


;- For debugging.
#DebugMode = 1

#EditorGadget = 2

#App_DebugEvent_NewDebugString = #PB_Event_FirstCustomValue
#App_DebugEventType_Value = #PB_EventType_FirstCustomValue

CompilerIf #PB_Compiler_Debugger
  ;CompilerError "Turn OFF debugger.   "
CompilerEndIf

CompilerIf Not #PB_Compiler_Thread
  CompilerError "Turn ON thread-safe.   "
CompilerEndIf

CompilerIf #DebugMode
  Global MutexStringSync
  
  CompilerIf Not Defined(EditorGadget, #PB_Constant)
    CompilerError "#EditorGadget NOT defined.   "
  CompilerEndIf
  
  Macro mDebugOutput(String)
    CompilerIf Not Defined(___string_buffer___, #PB_Variable)
      Define ___string_buffer___
    CompilerEndIf
    
    LockMutex(MutexStringSync)
    ___string_buffer___ = AllocateMemory(StringByteLength(String) + SizeOf(Character))
    PokeS(___string_buffer___, String)
    PostEvent(#App_DebugEvent_NewDebugString, 0, 0, #App_DebugEventType_Value, ___string_buffer___)
    ___string_buffer___ = 0
    UnlockMutex(MutexStringSync)
  EndMacro
  
  Procedure ShowDebugString()
    Protected String.s, *buffer = EventData()
    If *buffer
      LockMutex(MutexStringSync)
      String = PeekS(*buffer)
      If String
        If IsGadget(#EditorGadget)
          AddGadgetItem(#EditorGadget, -1, FormatDate("%hh:%ii:%ss", Date()) + " |    " + String)
          PostMessage_(GadgetID(#EditorGadget), #EM_SCROLL, #SB_PAGEDOWN, 0)
        EndIf
      EndIf
      FreeMemory(*buffer)
      UnlockMutex(MutexStringSync)
    EndIf
  EndProcedure
  
CompilerElse
  Macro mDebugOutput(String)
  EndMacro
  
  Macro ShowDebugString()
  EndMacro
CompilerEndIf

;- Constants.
#SHCNE_ASSOCCHANGED = $8000000
#SHCNF_IDLIST = 0

#SHCNE_ALLEVENTS = $7FFFFFFF
#SHCNF_FLUSH = $1000
#SHREGSET_FORCE_HKCU = 2

#REGCLS_SINGLEUSE = 0       ;class object only generates one instance
#REGCLS_MULTIPLEUSE = 1     ;same class object genereates multiple inst. and local automatically goes into inproc tbl.
#REGCLS_MULTI_SEPARATE = 2  ;multiple use, but separate control over each context.
#REGCLS_SUSPENDED = 4       ;register is as suspended, will be activated when app calls CoResumeClassObjects
#REGCLS_SURROGATE = 8       ;must be used when a surrogate process is registering a class object that will be loaded in the surrogate

#CLSCTX_INPROC_SERVER	= $1
#CLSCTX_INPROC_HANDLER	= $2
#CLSCTX_LOCAL_SERVER	= $4
#CLSCTX_INPROC_SERVER16	= $8
#CLSCTX_REMOTE_SERVER	= $10
#CLSCTX_INPROC_HANDLER16	= $20
#CLSCTX_RESERVED1	= $40
#CLSCTX_RESERVED2	= $80
#CLSCTX_RESERVED3	= $100
#CLSCTX_RESERVED4	= $200
#CLSCTX_NO_CODE_DOWNLOAD	= $400
#CLSCTX_RESERVED5	= $800
#CLSCTX_NO_CUSTOM_MARSHAL	= $1000
#CLSCTX_ENABLE_CODE_DOWNLOAD	= $2000
#CLSCTX_NO_FAILURE_LOG	= $4000
#CLSCTX_DISABLE_AAA	= $8000
#CLSCTX_ENABLE_AAA	= $10000
#CLSCTX_FROM_DEFAULT_CONTEXT	= $20000
#CLSCTX_ACTIVATE_X86_SERVER	= $40000
#CLSCTX_ACTIVATE_32_BIT_SERVER	= #CLSCTX_ACTIVATE_X86_SERVER
#CLSCTX_ACTIVATE_64_BIT_SERVER	= $80000
#CLSCTX_ENABLE_CLOAKING	= $100000
#CLSCTX_APPCONTAINER	= $400000
#CLSCTX_ACTIVATE_AAA_AS_IU	= $800000
#CLSCTX_RESERVED6	= $1000000
#CLSCTX_ACTIVATE_ARM32_SERVER	= $2000000
#CLSCTX_PS_DLL	= $80000000

#RO_INIT_MULTITHREADED = 1

#RuntimeClass_Windows_Data_Xml_Dom_XmlDocument = "Windows.Data.Xml.Dom.XmlDocument"
#RuntimeClass_Windows_UI_Notifications_ToastNotificationManager = "Windows.UI.Notifications.ToastNotificationManager"
#RuntimeClass_Windows_UI_Notifications_ToastNotification = "Windows.UI.Notifications.ToastNotification"

Enumeration ;__x_ABI_CWindows_CUI_CNotifications_CToastDismissalReason
  #ToastDismissalReason_UserCanceled
  #ToastDismissalReason_ApplicationHidden
  #ToastDismissalReason_TimedOut
EndEnumeration

;__x_ABI_CWindows_CFoundation_CPropertyType
#PropertyType_Empty = 0
#PropertyType_UInt8 = 1
#PropertyType_Int16 = 2
#PropertyType_UInt16 = 3
#PropertyType_Int32 = 4
#PropertyType_UInt32 = 5
#PropertyType_Int64 = 6
#PropertyType_UInt64 = 7
#PropertyType_Single = 8
#PropertyType_Double = 9
#PropertyType_Char16 = 10
#PropertyType_Boolean = 11
#PropertyType_String = 12
#PropertyType_Inspectable = 13
#PropertyType_DateTime = 14
#PropertyType_TimeSpan = 15
#PropertyType_Guid = 16
#PropertyType_Point = 17
#PropertyType_Size = 18
#PropertyType_Rect = 19
#PropertyType_OtherType = 20
#PropertyType_UInt8Array = 1025
#PropertyType_Int16Array = 1026
#PropertyType_UInt16Array = 1027
#PropertyType_Int32Array = 1028
#PropertyType_UInt32Array = 1029
#PropertyType_Int64Array = 1030
#PropertyType_UInt64Array = 1031
#PropertyType_SingleArray = 1032
#PropertyType_DoubleArray = 1033
#PropertyType_Char16Array = 1034
#PropertyType_BooleanArray = 1035
#PropertyType_StringArray = 1036
#PropertyType_InspectableArray = 1037
#PropertyType_DateTimeArray = 1038
#PropertyType_TimeSpanArray = 1039
#PropertyType_GuidArray = 1040
#PropertyType_PointArray = 1041
#PropertyType_SizeArray = 1042
#PropertyType_RectArray = 1043
#PropertyType_OtherTypeArray = 1044

;- Interfaces.
;{
;winrt\inspectable.h
Interface IInspectable Extends IUnknown
  GetIids(*iidCount, *iids)
  GetRuntimeClassName(*className)
  GetTrustLevel(*trustLevel)
EndInterface

;winrt\Windows.Data.Xml.Dom.h
Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument Extends IInspectable
  get_Doctype(*value)
  get_Implementation(*value)
  get_DocumentElement(*value)
  CreateElement(tagName, *newElement)
  CreateDocumentFragment(*newDocumentFragment)
  CreateTextNode(hStringData, *newTextNode)
  CreateComment(hStringData, *newComment)
  CreateProcessingInstruction(target, hStringData, *newProcessingInstruction)
  CreateAttribute(name, *newAttribute)
  CreateEntityReference(name, *newEntityReference)
  GetElementsByTagName(tagName, *elements)
  CreateCDataSection(hStringData, *newCDataSection)
  get_DocumentUri(*value)
  CreateAttributeNS(*namespaceUri, qualifiedName, *newAttribute)
  CreateElementNS(*namespaceUri, qualifiedName, *newElement)
  GetElementById(elementId, *element)
  ImportNode(*node, deep, *newNode)
EndInterface

Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlElement Extends IInspectable
  get_TagName(*value)
  GetAttribute(attributeName, *attributeValue)
  SetAttribute(attributeName, attributeValue)
  RemoveAttribute(attributeName)
  GetAttributeNode(attributeName, *attributeNode)
  SetAttributeNode(*newAttribute, *previousAttribute)
  RemoveAttributeNode(*attributeNode, *removedAttribute)
  GetElementsByTagName(tagName, *elements)
  SetAttributeNS(*namespaceUri, qualifiedName, value)
  GetAttributeNS(*namespaceUri, localName, *value)
  RemoveAttributeNS(*namespaceUri, localName)
  SetAttributeNodeNS(*newAttribute, *previousAttribute)
  GetAttributeNodeNS(*namespaceUri, localName, *previousAttribute)
EndInterface

Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlNodeList Extends IInspectable
  get_Length(*value)
  Item(index, *node)
EndInterface

Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlNode Extends IInspectable
  get_NodeValue(*value)
  put_NodeValue(*value)
  get_NodeType(*value)
  get_NodeName(*value)
  get_ParentNode(*value)
  get_ChildNodes(*value)
  get_FirstChild(*value)
  get_LastChild(*value)
  get_PreviousSibling(*value)
  get_NextSibling(*value)
  get_Attributes(*value)
  HasChildNodes(*value)
  get_OwnerDocument(*value)
  InsertBefore(*newChild, *referenceChild, *insertedChild)
  ReplaceChild(*newChild, *referenceChild, *previousChild)
  RemoveChild(*childNode, *removedChild)
  AppendChild(*newChild, *appendedChild)
  CloneNode(deep, *newNode)
  get_NamespaceUri(*value)
  get_LocalName(*value)
  get_Prefix(*value)
  Normalize()
  put_Prefix(*value)
EndInterface

Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO Extends IInspectable
  LoadXML(xml)
  LoadXmlWithSettings(xml, *loadSettings)
  SaveToFileAsync(*file, *asyncInfo)
EndInterface

Macro __x_ABI_CWindows_CFoundation_CCollections_CIPropertySet
  IInspectable
EndMacro

;winrt\windows.foundation.h
Interface __FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable Extends IInspectable
  First(*result)
EndInterface

Interface __FIIterator_1___FIKeyValuePair_2_HSTRING_IInspectable Extends IInspectable
  get_Current(*result)
  get_HasCurrent(*result)
  MoveNext(*result)
  GetMany(itemsLength, *items, *result)
EndInterface

Interface __FIKeyValuePair_2_HSTRING_IInspectable Extends IInspectable
  get_Key(*result)
  get_Value(*result)
EndInterface

Interface __x_ABI_CWindows_CFoundation_CIPropertyValue Extends IInspectable
  get_Type(*value)
  get_IsNumericScalar(*value)
  GetUInt8(*value)
  GetInt16(*value)
  GetUInt16(*value)
  GetInt32(*value)
  GetUInt32(*value)
  GetInt64(*value)
  GetUInt64(*value)
  GetSingle(*value)
  GetDouble(*value)
  GetChar16(*value)
  GetBoolean(*value)
  GetString(*value)
  GetGuid(*value)
  GetDateTime(*value)
  GetTimeSpan(*value)
  GetPoint(*value)
  GetSize(*value)
  GetRect(*value)
  GetUInt8Array(*valueLength, *value)
  GetInt16Array(*valueLength, *value)
  GetUInt16Array(*valueLength, *value)
  GetInt32Array(*valueLength, *value)
  GetUInt32Array(*valueLength, *value)
  GetInt64Array(*valueLength, *value)
  GetUInt64Array(*valueLength, *value)
  GetSingleArray(*valueLength, *value)
  GetDoubleArray(*valueLength, *value)
  GetChar16Array(*valueLength, *value)
  GetBooleanArray(*valueLength, *value)
  GetStringArray(*valueLength, *value)
  GetInspectableArray(*valueLength, *value)
  GetGuidArray(*valueLength, *value)
  GetDateTimeArray(*valueLength, *value)
  GetTimeSpanArray(*valueLength, *value)
  GetPointArray(*valueLength, *value)
  GetSizeArray(*valueLength, *value)
  GetRectArray(*valueLength, *value)
EndInterface

;winrt\windows.ui.notifications.h
Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics Extends IInspectable
  CreateToastNotifier(*result)
  CreateToastNotifierWithId(applicationId, *result)
  GetTemplateContent(type, *result)
EndInterface

Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier Extends IInspectable
  Show(*notification)
  Hide(*notification)
  get_Setting(*value)
  AddToSchedule(*scheduledToast)
  RemoveFromSchedule(*scheduledToast)
  GetScheduledToastNotifications(*result)
EndInterface

Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory Extends IInspectable
  CreateToastNotification(*content, *value)
EndInterface

Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotification Extends IInspectable
  get_Content(*value)
  put_ExpirationTime(*value)
  get_ExpirationTime(*value)
  add_Dismissed(*handler, *token)
  remove_Dismissed(token.q)
  add_Activated(*handler, *token)
  remove_Activated(token.q)
  add_Failed(*handler, *token)
  remove_Failed(token.q)
EndInterface

; Interface __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectable Extends IUnknown
;   Invoke(*sender, *args)
; EndInterface
; Interface __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgs Extends IUnknown
;   Invoke(*sender, *args)
; EndInterface
; Interface __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastFailedEventArgs Extends IUnknown
;   Invoke(*sender, *args)
; EndInterface

Interface ITypedEventHandler_2_ToastEventArgs Extends IUnknown
  Invoke(*sender, *args)
EndInterface

;um\NotificationActivationCallback.h
Interface INotificationActivationCallback Extends IUnknown
  Activate(*appUserModelId, *invokedArgs, *data, count)
EndInterface

;um\propsys.h
Interface IPropertyStore Extends IUnknown
  GetCount(*cProps)
  GetAt(iProp, *pkey)
  GetValue(key, *pv)
  SetValue(key, propvar)
  Commit()
EndInterface

;um\objidl.h
Interface IPersistFileW Extends IUnknown
  GetClassID(*pClassID)
  IsDirty()
  Load(pszFileName.s, dwMode)
  Save(pszFileName.s, fRemember)
  SaveCompleted(pszFileName.s)
  GetCurFile(*ppszFileName)
EndInterface

;um\ShObjIdl_core.h
Interface IShellLink Extends IUnknown   ;IShellLinkW
  GetPath(*pszFile, cch, *pfd, fFlags)
  GetIDList(*ppidl)
  SetIDList(pidl)
  GetDescription(*pszName, cch)
  SetDescription(pszName.s)
  GetWorkingDirectory(*pszDir, cch)
  SetWorkingDirectory(pszDir.s)
  GetArguments(*pszArgs, cch)
  SetArguments(pszArgs.s)
  GetHotkey(*pwHotkey)
  SetHotkey(wHotkey)
  GetShowCmd(*piShowCmd)
  SetShowCmd(iShowCmd)
  GetIconLocation(*pszIconPath, cch, *piIcon)
  SetIconLocation(pszIconPath.s, iIcon)
  SetRelativePath(pszPathRel.s, dwReserved)
  Resolve(hwnd, fFlags)
  SetPath(pszFile.s)
EndInterface

;winrt\windows.ui.notifications.h
Interface __x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs2 Extends IInspectable
  get_UserInput(*value)
EndInterface

;winrt\windows.ui.notifications.h
Interface __x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs Extends IInspectable
  get_Arguments(*value)
EndInterface

Interface __x_ABI_CWindows_CUI_CNotifications_CIToastDismissedEventArgs Extends IInspectable
  get_Reason(*value)
EndInterface

Interface __x_ABI_CWindows_CUI_CNotifications_CIToastFailedEventArgs Extends IInspectable
  get_ErrorCode(*value)
EndInterface

;}

;- Structures.

; https://www.purebasic.fr/english/viewtopic.php?p=489813&#p489813
Structure PROPVARIANT_CLIPDATA
  cbSize.l
  ulClipFmt.l
  *pClipData.BYTE
EndStructure

Structure PROPVARIANT_BSTRBLOB
  cbSize.l
  *pData.BYTE
EndStructure

Structure PROPVARIANT_BLOB
  cbSize.l
  *pBlobData.BYTE
EndStructure

Structure PROPVARIANT_VERSIONEDSTREAM
  guidVersion.GUID
  *pStream.IStream
EndStructure

Structure PROPVARIANT_CAC
  cElems.l
  *pElems.BYTE
EndStructure

Structure PROPVARIANT_CAUB
  cElems.l
  *pElems.BYTE
EndStructure

Structure PROPVARIANT_CAI
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CAUI
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CAL
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAUL
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAFLT
  cElems.l
  *pElems.FLOAT
EndStructure

Structure PROPVARIANT_CADBL
  cElems.l
  *pElems.DOUBLE
EndStructure

Structure PROPVARIANT_CACY
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CADATE
  cElems.l
  *pElems.DOUBLE
EndStructure

Structure PROPVARIANT_CABSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CABSTRBLOB
  cElems.l
  *pElems.PROPVARIANT_BSTRBLOB
EndStructure

Structure PROPVARIANT_CABOOL
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CASCODE
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAPROPVARIANT
  cElems.l
  *pElems.PROPVARIANT
EndStructure

Structure PROPVARIANT_CAH
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CAUH
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CALPSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CALPWSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CAFILETIME
  cElems.l
  *pElems.FILETIME
EndStructure

Structure PROPVARIANT_CACLIPDATA
  cElems.l
  *pElems.PROPVARIANT_CLIPDATA
EndStructure

Structure PROPVARIANT_CACLSID
  cElems.l
  *pElems.CLSID
EndStructure

; https://docs.microsoft.com/en-us/windows/win32/api/propidl/ns-propidl-propvariant
Structure PROPVARIANT Align #PB_Structure_AlignC   ;x64 24 bytes // x86 16 bytes.
  vt.w
  wReserved1.w
  wReserved2.w
  wReserved3.w
  StructureUnion
    cVal.b
    bVal.b
    iVal.w
    uiVal.w
    lVal.l
    ulVal.l
    intVal.l
    uintVal.l
    hVal.q
    uhVal.q
    fltVal.f
    dblVal.d
    boolVal.w
    scode.l
    cyVal.q
    date.d
    filetime.FILETIME
    *puuid.CLSID
    *pclipdata.PROPVARIANT_CLIPDATA
    bstrVal.i
    bstrblobVal.PROPVARIANT_BSTRBLOB
    blob.PROPVARIANT_BLOB
    *pszVal
    *pwszVal
    *punkVal.IUnknown
    *pdispVal.IDispatch
    *pStream.IStream
    *pStorage.IStorage
    *pVersionedStream.PROPVARIANT_VERSIONEDSTREAM
    *parray.SAFEARRAY
    cac.PROPVARIANT_CAC
    caub.PROPVARIANT_CAUB
    cai.PROPVARIANT_CAI
    caui.PROPVARIANT_CAUI
    cal.PROPVARIANT_CAL
    caul.PROPVARIANT_CAUL
    cah.PROPVARIANT_CAH
    cauh.PROPVARIANT_CAUH
    caflt.PROPVARIANT_CAFLT
    cadbl.PROPVARIANT_CADBL
    cabool.PROPVARIANT_CABOOL
    cascode.PROPVARIANT_CASCODE
    cacy.PROPVARIANT_CACY
    cadate.PROPVARIANT_CADATE
    cafiletime.PROPVARIANT_CAFILETIME
    cauuid.PROPVARIANT_CACLSID
    caclipdata.PROPVARIANT_CACLIPDATA
    cabstr.PROPVARIANT_CABSTR
    cabstrblob.PROPVARIANT_CABSTRBLOB
    calpstr.PROPVARIANT_CALPSTR
    calpwstr.PROPVARIANT_CALPWSTR
    capropvar.PROPVARIANT_CAPROPVARIANT
    *pcVal.BYTE
    *pbVal.BYTE
    *piVal.WORD
    *puiVal.WORD
    *plVal.LONG
    *pulVal.LONG
    *pintVal.LONG
    *puintVal.LONG
    *pfltVal.FLOAT
    *pdblVal.DOUBLE
    *pboolVal.WORD
    *pdecVal.VARIANT_DECIMAL
    *pscode.LONG
    *pcyVal.QUAD
    *pdate.DOUBLE
    *pbstrVal.INTEGER
    *ppunkVal.INTEGER
    *ppdispVal.INTEGER
    *pparray.INTEGER
    *pvarVal.PROPVARIANT
  EndStructureUnion
EndStructure

;winrt\hstring.h
Structure HSTRING
  unused.i
EndStructure

Structure HSTRING_HEADER
  StructureUnion
    *Reserved1
    CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
      Reserved2.c[24]
    CompilerElse
      Reserved2.c[20]
    CompilerEndIf
  EndStructureUnion
EndStructure

;winrt\EventToken.h
Structure EventRegistrationToken
  value.q
EndStructure

;um\NotificationActivationCallback.h
Structure NOTIFICATION_USER_INPUT_DATA
  *pKey
  *pValue
EndStructure

;shared\wtypes.h
Structure PROPERTYKEY
  fmtid.GUID
  pid.l
EndStructure

Structure STRUC_IUnknownBase
  *pVtbl
  lRefCount.l
  lDismissCount.l
  *pActivatedHandler.STRUC_IUnknownBase
  *pDismissedHandler.STRUC_IUnknownBase
  *pFailedHandler.STRUC_IUnknownBase
  hEventExitThread.i
EndStructure

Structure STRUC_IUnknownVtbl Extends STRUC_IUnknownBase
  *pQueryInterface
  *pAddRef
  *pRelease
EndStructure

Structure STRUC_IClassFactoryVtbl Extends STRUC_IUnknownVtbl
  *pCreateInstance
  *pLockServer
EndStructure

Structure STRUC_INotificationActivationCallbackVtbl Extends STRUC_IUnknownVtbl
  *pActivate
EndStructure

Structure STRUC_ToastEventHandler Extends STRUC_IUnknownVtbl
  *pInvoke
EndStructure

;- Global variables.
Global MutexStringSync
Global hEventAllThreadExit
Global LibComBase
Global *NotificationActivationCallback.STRUC_INotificationActivationCallbackVtbl
Global *ClassFactory.STRUC_IClassFactoryVtbl

;- Prototypes.
Prototype ptRoInitialize(initType)
Global RoInitialize.ptRoInitialize

Prototype ptRoUninitialize()
Global RoUninitialize.ptRoUninitialize

Prototype ptRoGetActivationFactory(activatableClassId, iid, *factory)
Global RoGetActivationFactory.ptRoGetActivationFactory

Prototype ptWindowsCreateStringReference(sourceString.s, length.l, *hstringHeader, *string)
Global WindowsCreateStringReference.ptWindowsCreateStringReference

Prototype ptRoActivateInstance(activatableClassId, *instance)
Global RoActivateInstance.ptRoActivateInstance

Prototype ptWindowsGetStringRawBuffer(string, *length)
Global WindowsGetStringRawBuffer.ptWindowsGetStringRawBuffer

Prototype ptWindowsDeleteString(string)
Global WindowsDeleteString.ptWindowsDeleteString

Procedure InitLibraries()
  LibComBase = OpenLibrary(#PB_Any, "combase.dll")
  If LibComBase = 0 : ProcedureReturn 0 : EndIf
  
  RoInitialize = GetFunction(LibComBase, "RoInitialize")
  RoUninitialize = GetFunction(LibComBase, "RoUninitialize")
  WindowsCreateStringReference = GetFunction(LibComBase, "WindowsCreateStringReference")
  RoActivateInstance = GetFunction(LibComBase, "RoActivateInstance")
  RoGetActivationFactory = GetFunction(LibComBase, "RoGetActivationFactory")
  WindowsGetStringRawBuffer = GetFunction(LibComBase, "WindowsGetStringRawBuffer")
  WindowsDeleteString = GetFunction(LibComBase, "WindowsDeleteString")
  
  If RoInitialize = 0 Or
     RoUninitialize = 0 Or
     WindowsCreateStringReference = 0 Or
     RoActivateInstance = 0 Or
     RoGetActivationFactory = 0 Or
     WindowsGetStringRawBuffer = 0 Or
     WindowsDeleteString = 0
    
    ProcedureReturn 0
  EndIf
  ProcedureReturn 1
EndProcedure

Procedure CloseLibraries()
  If LibComBase : CloseLibrary(LibComBase) : EndIf
EndProcedure

Procedure.s GetStringFromHString(HString)
  Protected Result.s, *buffer, iLen
  If HString
    *buffer = WindowsGetStringRawBuffer(HString, @iLen)
    If *buffer
      Result = PeekS(*buffer, iLen)
    EndIf
    WindowsDeleteString(HString)
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure.l IUnknown_AddRef(*this.STRUC_IUnknownBase)
  *this\lRefCount + 1
  ProcedureReturn *this\lRefCount
EndProcedure

Procedure.l IUnknown_Release(*this.STRUC_IUnknownBase)
  *this\lRefCount - 1
  ;No need to call FreeMemory.
  ProcedureReturn *this\lRefCount
EndProcedure

Procedure.l INotificationActivationCallback_QueryInterface(*this.STRUC_IUnknownBase, *riid.IID, *ppvObject.Integer)
  If *ppvObject And *riid
    If CompareMemory(*riid, ?IID_IUnknown, SizeOf(IID)) Or CompareMemory(*riid, ?IID_INotificationActivationCallback, SizeOf(IID))
      *this\lRefCount + 1
      *ppvObject\i = *this
      
    Else
      *ppvObject\i = 0
      ProcedureReturn #E_NOINTERFACE
    EndIf
  Else
    ProcedureReturn #E_POINTER
  EndIf
  
  ProcedureReturn #S_OK
EndProcedure

Procedure.l INotificationActivationCallback_Activate(*this, *appUserModelId, *invokedArgs, *data.NOTIFICATION_USER_INPUT_DATA, Count)
  Protected i
  Protected sAppUserModelId.s, sInvokedArgs.s, sKey.s, sValue.s
  
  mDebugOutput("----------------------------------------------------------")
  mDebugOutput("*** NotificationActivationCallback")
  
  If *appUserModelId
    LockMutex(MutexStringSync)
    sAppUserModelId = PeekS(*appUserModelId)
    UnlockMutex(MutexStringSync)
    
    mDebugOutput("AppUserModelId: " + sAppUserModelId)
  EndIf
  
  If *invokedArgs
    LockMutex(MutexStringSync)
    sInvokedArgs = PeekS(*invokedArgs)
    UnlockMutex(MutexStringSync)
    
    mDebugOutput("InvokedArgs: " + sInvokedArgs)
  EndIf
  
  mDebugOutput("Data count: " + Str(Count))
  
  If *data
    For i = 0 To Count - 1
      LockMutex(MutexStringSync)
      If *data\pKey
        sKey = PeekS(*data\pKey)
      EndIf
      If *data\pValue
        sValue = PeekS(*data\pValue)
      EndIf
      UnlockMutex(MutexStringSync)
      
      mDebugOutput("Data:   Key= " + sKey + "   //   Value= " + sValue)
      
      *data + SizeOf(NOTIFICATION_USER_INPUT_DATA)
    Next
  EndIf
  
  mDebugOutput("----------------------------------------------------------")
  
  ProcedureReturn #S_OK
EndProcedure


Procedure.l IClassFactory_QueryInterface(*this.STRUC_IUnknownBase, *riid.IID, *ppvObject.Integer)
  If *riid And *ppvObject
    If CompareMemory(*riid, ?IID_IUnknown, SizeOf(IID)) Or CompareMemory(*riid, ?IID_IClassFactory, SizeOf(IID))
      *this\lRefCount + 1
      *ppvObject\i = *this
      
    ElseIf CompareMemory(*riid, ?IID_INotificationActivationCallback, SizeOf(IID))
      *NotificationActivationCallback\lRefCount + 1
      *ppvObject\i = *NotificationActivationCallback
      
    Else
      *ppvObject\i = 0
      ProcedureReturn #E_NOINTERFACE
    EndIf
  Else
    ProcedureReturn #E_POINTER
  EndIf
  
  ProcedureReturn #S_OK
EndProcedure

Procedure.l IClassFactory_CreateInstance(*this, *pUnkOuter.IUnknown, *riid.IID, *ppvObject.Integer)
  Protected ClassFactory.IClassFactory = *ClassFactory
  
  ;mDebugOutput("IClassFactory_CreateInstance")
  
  If *riid = 0 Or *ppvObject = 0
    ProcedureReturn #E_INVALIDARG
  EndIf
  
  If *pUnkOuter
    *ppvObject\i = 0
    ProcedureReturn #CLASS_E_NOAGGREGATION
  EndIf
  
  ProcedureReturn ClassFactory\QueryInterface(*riid, *ppvObject)
EndProcedure

Procedure.l IClassFactory_LockServer(*this, fLock)
  ;mDebugOutput("IClassFactory_LockServer")
  ProcedureReturn #E_UNEXPECTED
EndProcedure

Procedure.l ToastEventHandler_QueryInterface(*this.STRUC_IUnknownBase, *riid.IID, *ppvObject.Integer)
  If *ppvObject And *riid
    If CompareMemory(*riid, ?IID_IUnknown, SizeOf(IID))
      *this\lRefCount + 1
      *ppvObject\i = *this
      
    ElseIf CompareMemory(*riid, ?IID_DesktopToastActivatedEventHandler, SizeOf(IID))
      *this\pActivatedHandler\lRefCount + 1
      *ppvObject\i = *this\pActivatedHandler
      
    ElseIf CompareMemory(*riid, ?IID_DesktopToastDismissedEventHandler, SizeOf(IID))
      *this\pDismissedHandler\lRefCount + 1
      *ppvObject\i = *this\pDismissedHandler
      
    ElseIf CompareMemory(*riid, ?IID_DesktopToastFailedEventHandler, SizeOf(IID))
      *this\pFailedHandler\lRefCount + 1
      *ppvObject\i = *this\pFailedHandler
      
    Else
      *ppvObject\i = 0
      ProcedureReturn #E_NOINTERFACE
    EndIf
  Else
    ProcedureReturn #E_POINTER
  EndIf
  
  ProcedureReturn #S_OK
EndProcedure

Procedure.l ActivatedEventHandler_Invoke(*this.STRUC_IUnknownBase, *sender.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification, *args.__x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs)
  Protected r, hstring, Type
  Protected *args2.__x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs2
  Protected *ui.IInspectable
  Protected *iterable.__FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable
  Protected *iterator.__FIIterator_1___FIKeyValuePair_2_HSTRING_IInspectable
  Protected *KeyValuePair.__FIKeyValuePair_2_HSTRING_IInspectable
  Protected *value.IInspectable
  Protected *pv.__x_ABI_CWindows_CFoundation_CIPropertyValue
  Protected sArguments.s, sKey.s, sValue.s
  
  If *args = 0 : ProcedureReturn #E_POINTER : EndIf
  
  mDebugOutput("----------------------------------------------------------")
  mDebugOutput("*** Toast notification event: Activated")
  
  If *args\get_Arguments(@hstring) = #S_OK
    LockMutex(MutexStringSync)
    sArguments = GetStringFromHString(hstring)
    UnlockMutex(MutexStringSync)
    
    mDebugOutput("Arguments: " + sArguments)
  EndIf
  
  r = *args\QueryInterface(?IID_IToastActivatedEventArgs2, @*args2)
  If r <> #S_OK Or *args2 = 0 : Goto _Proc_Exit : EndIf
  
  r = *args2\get_UserInput(@*ui)
  If r <> #S_OK Or *ui = 0 : Goto _Proc_Exit : EndIf
  
  r = *ui\QueryInterface(?IID___FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable, @*iterable)
  If r <> #S_OK Or *iterable = 0 : Goto _Proc_Exit : EndIf
  
  r = *iterable\First(@*iterator)
  If r <> #S_OK Or *iterator = 0 : Goto _Proc_Exit : EndIf
  
  r = *iterator\get_Current(@*KeyValuePair)
  If r <> #S_OK Or *KeyValuePair = 0 : Goto _Proc_Exit : EndIf
  
  If *KeyValuePair\get_Key(@hstring) = 0
    LockMutex(MutexStringSync)
    sKey = GetStringFromHString(hstring)
    UnlockMutex(MutexStringSync)
    
    mDebugOutput("Key: " + sKey)
  EndIf
  
  r = *KeyValuePair\get_Value(@*value)
  If r <> #S_OK Or *value = 0 : Goto _Proc_Exit : EndIf
  
  r = *value\QueryInterface(?IID_IPropertyValue, @*pv)
  If r <> #S_OK Or *pv = 0 : Goto _Proc_Exit : EndIf
  
  r = *pv\get_Type(@Type)
  If r <> #S_OK : Goto _Proc_Exit : EndIf
  
  If Type = #PropertyType_String
    If *pv\GetString(@hstring) = #S_OK
      LockMutex(MutexStringSync)
      sValue = GetStringFromHString(hstring)
      UnlockMutex(MutexStringSync)
      
      mDebugOutput("Value: " + sValue)
    EndIf
  EndIf
  
  _Proc_Exit:
  If *pv : *pv\Release() : EndIf
  If *value : *value\Release() : EndIf
  If *KeyValuePair : *KeyValuePair\Release() : EndIf
  If *iterator : *iterator\Release() : EndIf
  If *iterable : *iterable\Release() : EndIf
  If *ui : *ui\Release() : EndIf
  If *args2 : *args2\Release() : EndIf
  
  mDebugOutput("----------------------------------------------------------")
  
  If *this\hEventExitThread
    SetEvent_(*this\hEventExitThread)
  EndIf
  
  ProcedureReturn #S_OK
EndProcedure

Procedure.l DismissedEventHandler_Invoke(*this.STRUC_IUnknownBase, *sender.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification, *args.__x_ABI_CWindows_CUI_CNotifications_CIToastDismissedEventArgs)
  Protected ReasonCode
  
  If *args = 0 : ProcedureReturn #E_POINTER : EndIf
  
  mDebugOutput("----------------------------------------------------------")
  mDebugOutput("*** Toast notification event: Dismissed")
  
  If *args\get_Reason(@ReasonCode) = #S_OK
    Select ReasonCode
      Case #ToastDismissalReason_UserCanceled
        mDebugOutput("Reason: UserCanceled. The user dismissed the toast.")
      Case #ToastDismissalReason_ApplicationHidden
        mDebugOutput("Reason: ApplicationHidden. The application hid the toast using ToastNotifier\Hide().")
      Case #ToastDismissalReason_TimedOut
        mDebugOutput("Reason: TimedOut. The toast has expired.")
      Default
        mDebugOutput("Unknown Reason code: " + Str(ReasonCode))
    EndSelect
  EndIf
  
  mDebugOutput("----------------------------------------------------------")
  
  If *this\lDismissCount > 0
    If *this\hEventExitThread
      SetEvent_(*this\hEventExitThread)
    EndIf
  Else
    *this\lDismissCount + 1
  EndIf
  
  ProcedureReturn #S_OK
EndProcedure

Procedure.l FailedEventHandler_Invoke(*this.STRUC_IUnknownBase, *sender.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification, *args.__x_ABI_CWindows_CUI_CNotifications_CIToastFailedEventArgs)
  Protected Result.l
  
  If *args = 0 : ProcedureReturn #E_POINTER : EndIf
  
  mDebugOutput("----------------------------------------------------------")
  mDebugOutput("*** Toast notification event: Failed")
  
  If *args\get_ErrorCode(@Result) = #S_OK
    mDebugOutput("HRESULT code: " + Hex(Result))
  EndIf
  
  mDebugOutput("----------------------------------------------------------")
  
  If *this\hEventExitThread
    SetEvent_(*this\hEventExitThread)
  EndIf
  
  ProcedureReturn #S_OK
EndProcedure

Procedure InitInterfaces()
  *NotificationActivationCallback = AllocateMemory(SizeOf(STRUC_INotificationActivationCallbackVtbl))
  If *NotificationActivationCallback = 0 : ProcedureReturn 0 : EndIf
  
  *ClassFactory = AllocateMemory(SizeOf(STRUC_IClassFactoryVtbl))
  If *ClassFactory = 0 : ProcedureReturn 0 : EndIf
  
  With *ClassFactory
    \pVtbl = *ClassFactory + OffsetOf(STRUC_IClassFactoryVtbl\pQueryInterface)
    \pQueryInterface = @IClassFactory_QueryInterface()
    \pAddRef = @IUnknown_AddRef()
    \pRelease = @IUnknown_Release()
    \pCreateInstance = @IClassFactory_CreateInstance()
    \pLockServer = @IClassFactory_LockServer()
  EndWith
  
  With *NotificationActivationCallback
    \pVtbl = *NotificationActivationCallback + OffsetOf(STRUC_INotificationActivationCallbackVtbl\pQueryInterface)
    \pQueryInterface = @INotificationActivationCallback_QueryInterface()
    \pAddRef = @IUnknown_AddRef()
    \pRelease = @IUnknown_Release()
    \pActivate = @INotificationActivationCallback_Activate()
  EndWith
  
  ProcedureReturn 1
EndProcedure

Procedure InitPropVariantFromString(String.s, *ppropvar.PROPVARIANT)
  Protected ByteCount, Result
  
  If *ppropvar = 0 : ProcedureReturn #E_INVALIDARG : EndIf
  If String = ""
    Result = #E_INVALIDARG
  EndIf
  
  ByteCount = StringByteLength(String) + SizeOf(Character)
  
  *ppropvar\pwszVal = CoTaskMemAlloc_(ByteCount)
  If *ppropvar\pwszVal
    CopyMemory(@String, *ppropvar\pwszVal, ByteCount)
    *ppropvar\vt = #VT_LPWSTR
    Result = #S_OK
  Else
    Result = #E_OUTOFMEMORY
  EndIf
  
  If Result <> #S_OK
    FillMemory(*ppropvar, SizeOf(PROPVARIANT), 0)
  EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure InstallShortcut(sShortcutFile.s)
  Protected sExePath.s = #DOUBLEQUOTE$ + ProgramFilename() + #DOUBLEQUOTE$
  Protected *ShellLink.IShellLink
  Protected *PropertyStore.IPropertyStore
  Protected *PersistFile.IPersistFileW
  Protected AppIdPropVar.PROPVARIANT
  Protected AppCLSID.CLSID, Result
  
  If sShortcutFile = "" : ProcedureReturn #E_INVALIDARG : EndIf
  
  Result = CoCreateInstance_(?CLSID_ShellLink, 0, #CLSCTX_INPROC_SERVER, ?IID_IShellLinkW, @*ShellLink)
  If Result <> #S_OK : ProcedureReturn Result : EndIf
  If *ShellLink = 0 : ProcedureReturn #E_NOINTERFACE : EndIf
  
  Result = *ShellLink\SetPath(sExePath)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
;   *ShellLink\SetArguments(Arguments$)
;   *ShellLink\SetWorkingDirectory(WorkingDirectory$)
;   *ShellLink\SetDescription(Description$)
;   *ShellLink\SetShowCmd(ShowCommand)
;   *ShellLink\SetHotkey(HotKey)
;   *ShellLink\SetIconLocation(IconFile$, IconIndex)
  
  Result = *ShellLink\QueryInterface(?IID_IPropertyStore, @*PropertyStore)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *PropertyStore = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = InitPropVariantFromString(#App_ID, @AppIdPropVar)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
  Result = *PropertyStore\SetValue(?PKEY_AppUserModel_ID, AppIdPropVar)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
  If AppIdPropVar\pwszVal
    AppIdPropVar\vt = 0
    CoTaskMemFree_(AppIdPropVar\pwszVal)
  EndIf
  
  AppIdPropVar\vt = #VT_CLSID
  CLSIDFromString_(#App_CLSID, AppCLSID)
  AppIdPropVar\puuid = @AppCLSID
  
  Result = *PropertyStore\SetValue(?PKEY_AppUserModel_ToastActivatorCLSID, AppIdPropVar)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
  Result = *ShellLink\QueryInterface(?IID_IPersistFileW, @*PersistFile)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *PersistFile = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = *PersistFile\Save(sShortcutFile, 1)
  
  SHChangeNotify_(#SHCNE_ASSOCCHANGED, #SHCNF_IDLIST, 0, 0)
  
  _Proc_Exit:
  If *PersistFile : *PersistFile\Release() : EndIf
  If *PropertyStore : *PropertyStore\Release() : EndIf
  If *ShellLink : *ShellLink\Release() : EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure CreateXmlDocumentFromString(xmlString.s, *idoc.Integer)
  Protected Result = #E_FAIL
  Protected *doc.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument
  Protected *docIO.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO
  Protected *pInspectable.IInspectable
  Protected header_IXmlDocumentHString.HSTRING_HEADER, *IXmlDocumentHString.HSTRING
  Protected header_XmlString.HSTRING_HEADER, *XmlString.HSTRING
  
  Result = WindowsCreateStringReference(#RuntimeClass_Windows_Data_Xml_Dom_XmlDocument, Len(#RuntimeClass_Windows_Data_Xml_Dom_XmlDocument), @header_IXmlDocumentHString, @*IXmlDocumentHString)
  If Result <> #S_OK : ProcedureReturn Result :	EndIf
  If *IXmlDocumentHString = 0 : ProcedureReturn #E_POINTER : EndIf
  
  Result = RoActivateInstance(*IXmlDocumentHString, @*pInspectable)
  If Result <> #S_OK : ProcedureReturn Result : EndIf
  If *pInspectable = 0 : ProcedureReturn #E_NOINTERFACE : EndIf
  
  Result = *pInspectable\QueryInterface(?IID_IXmlDocument, @*doc)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *doc = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = *doc\QueryInterface(?IID_IXmlDocumentIO, @*docIO)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *docIO = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = WindowsCreateStringReference(xmlString, Len(xmlString), @header_XmlString, @*XmlString)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *XmlString = 0 : Result = #E_POINTER : Goto _Proc_Exit : EndIf
  
  Result = *docIO\LoadXML(*XmlString)
  
  _Proc_Exit:
  
  If Result = #S_OK
    *idoc\i = *doc
  Else
    If *doc : *doc\Release() : EndIf
  EndIf
  
  If *docIO : *docIO\Release() : EndIf
  If *pInspectable : *pInspectable\Release() : EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure ShowMsg(*xml)
  Protected Result, xml.s
  Protected header_AppIdHString.HSTRING_HEADER, *AppIdHString.HSTRING
  Protected header_ToastNotificationManagerHString.HSTRING_HEADER, *ToastNotificationManagerHString.HSTRING
  Protected header_ToastNotificationHString.HSTRING_HEADER, *ToastNotificationHString.HSTRING
  Protected *inputXml.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument
  Protected *toastStatics.__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics
  Protected *notifier.__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier
  Protected *notifFactory.__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory
  Protected *toast.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification
  Protected ActivatedToken.EventRegistrationToken, DismissedToken.EventRegistrationToken, FailedToken.EventRegistrationToken
  Protected *DismissedEventHandler.STRUC_ToastEventHandler
  Protected *ActivatedEventHandler.STRUC_ToastEventHandler
  Protected *FailedEventHandler.STRUC_ToastEventHandler
  Protected Dim hEventThreadExit.i(1), hEventThreadExit
  
  If *xml
    LockMutex(MutexStringSync)
    xml = PeekS(*xml)
    FreeMemory(*xml)
    UnlockMutex(MutexStringSync)
  Else
    ProcedureReturn
  EndIf
  
  If RoInitialize(#RO_INIT_MULTITHREADED) <> #S_OK : ProcedureReturn : EndIf
  
  If WindowsCreateStringReference(#App_ID, Len(#App_ID), @header_AppIdHString, @*AppIdHString) <> #S_OK : Goto _Proc_Exit : EndIf
  If *AppIdHString = 0 : Goto _Proc_Exit : EndIf
  
  If CreateXmlDocumentFromString(xml, @*inputXml) <> #S_OK : Goto _Proc_Exit : EndIf
  If *inputXml = 0 : Goto _Proc_Exit : EndIf
  
  If WindowsCreateStringReference(#RuntimeClass_Windows_UI_Notifications_ToastNotificationManager, Len(#RuntimeClass_Windows_UI_Notifications_ToastNotificationManager),
                                  @header_ToastNotificationManagerHString, @*ToastNotificationManagerHString) <> #S_OK : Goto _Proc_Exit : EndIf
  If *ToastNotificationManagerHString = 0 : Goto _Proc_Exit : EndIf
  
  If RoGetActivationFactory(*ToastNotificationManagerHString, ?IID_IToastNotificationManagerStatics, @*toastStatics) <> #S_OK : Goto _Proc_Exit : EndIf
  If *toastStatics = 0 : Goto _Proc_Exit : EndIf
  
  If *toastStatics\CreateToastNotifierWithId(*AppIdHString, @*notifier) <> #S_OK : Goto _Proc_Exit : EndIf
  If *notifier = 0 : Goto _Proc_Exit : EndIf
  
  If WindowsCreateStringReference(#RuntimeClass_Windows_UI_Notifications_ToastNotification, Len(#RuntimeClass_Windows_UI_Notifications_ToastNotification),
                                  @header_ToastNotificationHString, @*ToastNotificationHString) <> #S_OK : Goto _Proc_Exit : EndIf
  If *ToastNotificationHString = 0 : Goto _Proc_Exit : EndIf
  
  If RoGetActivationFactory(*ToastNotificationHString, ?IID_IToastNotificationFactory, @*notifFactory) <> #S_OK : Goto _Proc_Exit : EndIf
  If *notifFactory = 0 : Goto _Proc_Exit : EndIf
  
  If *notifFactory\CreateToastNotification(*inputXml, @*toast) <> #S_OK : Goto _Proc_Exit : EndIf
  If *toast = 0 : Goto _Proc_Exit : EndIf
  
  *ActivatedEventHandler = AllocateMemory(SizeOf(STRUC_ToastEventHandler))
  *DismissedEventHandler = AllocateMemory(SizeOf(STRUC_ToastEventHandler))
  *FailedEventHandler = AllocateMemory(SizeOf(STRUC_ToastEventHandler))
  If *ActivatedEventHandler = 0 Or *DismissedEventHandler = 0 Or *FailedEventHandler = 0 : Goto _Proc_Exit : EndIf
  
  hEventThreadExit = CreateEvent_(0, 0, 0, 0)
  If hEventThreadExit = 0 : Goto _Proc_Exit : EndIf
  
  hEventThreadExit(0) = hEventAllThreadExit
  hEventThreadExit(1) = hEventThreadExit
  
  With *ActivatedEventHandler
    \pVtbl = *ActivatedEventHandler + OffsetOf(STRUC_ToastEventHandler\pQueryInterface)
    \pActivatedHandler = *ActivatedEventHandler
    \pDismissedHandler = *DismissedEventHandler
    \pFailedHandler = *FailedEventHandler
    \hEventExitThread = hEventThreadExit
    \pQueryInterface = @ToastEventHandler_QueryInterface()
    \pAddRef = @IUnknown_AddRef()
    \pRelease = @IUnknown_Release()
    \pInvoke = @ActivatedEventHandler_Invoke()
  EndWith
  
  With *DismissedEventHandler
    \pVtbl = *DismissedEventHandler + OffsetOf(STRUC_ToastEventHandler\pQueryInterface)
    \pActivatedHandler = *ActivatedEventHandler
    \pDismissedHandler = *DismissedEventHandler
    \pFailedHandler = *FailedEventHandler
    \hEventExitThread = hEventThreadExit
    \pQueryInterface = @ToastEventHandler_QueryInterface()
    \pAddRef = @IUnknown_AddRef()
    \pRelease = @IUnknown_Release()
    \pInvoke = @DismissedEventHandler_Invoke()
  EndWith
  
  With *FailedEventHandler
    \pVtbl = *FailedEventHandler + OffsetOf(STRUC_ToastEventHandler\pQueryInterface)
    \pActivatedHandler = *ActivatedEventHandler
    \pDismissedHandler = *DismissedEventHandler
    \pFailedHandler = *FailedEventHandler
    \pQueryInterface = @ToastEventHandler_QueryInterface()
    \pAddRef = @IUnknown_AddRef()
    \pRelease = @IUnknown_Release()
    \pInvoke = @FailedEventHandler_Invoke()
  EndWith
  
  If *toast\add_Activated(*ActivatedEventHandler, @ActivatedToken) <> #S_OK : Goto _Proc_Exit : EndIf
  If *toast\add_Dismissed(*DismissedEventHandler, @DismissedToken) <> #S_OK : Goto _Proc_Exit : EndIf
  If *toast\add_Failed(*FailedEventHandler, @FailedToken) <> #S_OK : Goto _Proc_Exit : EndIf
  
  Result = *notifier\Show(*toast)
  
  If Result = #S_OK
    Result = WaitForMultipleObjects_(2, @hEventThreadExit(), 0, #INFINITE)
    If Result - #WAIT_OBJECT_0 = 1
      Sleep_(2000)
    EndIf
  EndIf
  
  _Proc_Exit:
  
  If *toast
    If ActivatedToken\value
      *toast\remove_Activated(ActivatedToken\value)
    EndIf
    If DismissedToken\value
      *toast\remove_Dismissed(DismissedToken\value)
    EndIf
    If FailedToken\value
      *toast\remove_Failed(FailedToken\value)
    EndIf
  EndIf
  
  If *toast : *toast\Release() : EndIf
  If *notifFactory : *notifFactory\Release() : EndIf
  If *notifier : *notifier\Release() : EndIf
  If *toastStatics : *toastStatics\Release() : EndIf
  If *inputXml : *inputXml\Release() : EndIf
  
  If *ActivatedEventHandler : FreeMemory(*ActivatedEventHandler) : EndIf
  If *DismissedEventHandler : FreeMemory(*DismissedEventHandler) : EndIf
  If *FailedEventHandler : FreeMemory(*FailedEventHandler) : EndIf
  
  If hEventThreadExit : CloseHandle_(hEventThreadExit) : EndIf
  
  RoUninitialize()
EndProcedure

Procedure CreateToastNotificationThread(xml.s, List Threads.i())
  Protected *xml, Result
  LockMutex(MutexStringSync)
  If xml
    If AddElement(Threads())
      *xml = AllocateMemory(StringByteLength(xml) + SizeOf(Character))
      If *xml
        PokeS(*xml, xml)
        Threads() = CreateThread(@ShowMsg(), *xml)
        If IsThread(Threads())
          Result = 1
        Else
          DeleteElement(Threads())
        EndIf
      EndIf
    EndIf
  EndIf
  UnlockMutex(MutexStringSync)
  ProcedureReturn Result
EndProcedure


Define lRegister, CLSID.CLSID, sLinkFile.s
Define xml.s, e

NewList Threads.i()

xml = ~"<toast duration='short'>" +
      ~"    <visual>" +
      ~"        <binding template='ToastGeneric'>" +
      ~"            <text>Test Notification</text>" +
      ~"            <text>blah blah blah...</text>" +
      ~"        </binding>" +
      ~"    </visual>" +
      ~"    <actions>" +
      ~"        <input id='MyMessageInput' type='text' title='Message' placeHolderContent='Enter a message' defaultInput='Toast Notification Test' />" +
      ~"        <action activationType='foreground' arguments='QuickReply' content='Submit' />" +
      ~"        <action activationType='foreground' arguments='Cancel' content='Cancel' />" +
      ~"    </actions>" +
      ~"</toast>"

If OSVersion() < #PB_OS_Windows_10
  MessageRequester("Warning", "Windows 10 or later is required.")
  End
EndIf

If InitInterfaces() = 0
  End
EndIf

If InitLibraries() = 0
  End
EndIf

MutexStringSync = CreateMutex()
If MutexStringSync = 0
  End
EndIf

hEventAllThreadExit = CreateEvent_(0, 1, 0, 0)
If hEventAllThreadExit = 0
  End
EndIf

CoInitialize_(0)

sLinkFile = GetEnvironmentVariable("APPDATA")
If sLinkFile
  sLinkFile + "\Microsoft\Windows\Start Menu\Programs\" + #App_Name + ".lnk"
  If FileSize(sLinkFile) = -1
    If InstallShortcut(sLinkFile) <> #S_OK
      Goto Quit
    EndIf
  EndIf
EndIf

;SHChangeNotify_(#SHCNE_ASSOCCHANGED, #SHCNF_FLUSH, 0, 0)
SHChangeNotify_(#SHCNE_ALLEVENTS, #SHCNF_FLUSH, 0, 0)
SendMessageTimeout_(#HWND_BROADCAST, #WM_SETTINGCHANGE, 0, 0, #SMTO_ABORTIFHUNG, 5000, 0)

MessageRequester("", "Wait a few seconds for the setting to be reflected in the system.")

If CLSIDFromString_(#App_CLSID, @CLSID) <> #NOERROR
  Goto Quit
EndIf

If CoRegisterClassObject_(CLSID, *ClassFactory, #CLSCTX_LOCAL_SERVER, #REGCLS_MULTIPLEUSE, @lRegister) <> #S_OK
  Goto Quit
EndIf

If OpenWindow(0, 0, 0, 600, 650, "Toast Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ButtonGadget(0, 10, 2, 200, 25, "Show message")
  ButtonGadget(1, 390, 2, 200, 25, "Clear")
  EditorGadget(#EditorGadget, 0, 30, 600, 620, #PB_Editor_ReadOnly)
  
  mDebugOutput(ProgramFilename())
  
  Repeat
    e = WaitWindowEvent()
    If e = #PB_Event_Gadget
      Select EventGadget()
        Case 0
          CreateToastNotificationThread(xml, Threads())
          
        Case 1
          ClearGadgetItems(#EditorGadget)
      EndSelect
    EndIf
    
    If e = #App_DebugEvent_NewDebugString And EventType() = #App_DebugEventType_Value
      ShowDebugString()
    EndIf
    
  Until e = #PB_Event_CloseWindow
  
  CloseWindow(0)
  
  SetEvent_(hEventAllThreadExit)
  
  ForEach Threads()
    If IsThread(Threads())
      WaitThread(Threads(), 5000)
    EndIf
  Next
EndIf

Quit:

;delete .lnk file.
If FileSize(sLinkFile) >= 0
  DeleteFile(sLinkFile)
EndIf

If hEventAllThreadExit : CloseHandle_(hEventAllThreadExit) : EndIf

CloseLibraries()

If lRegister
  CoRevokeClassObject_(lRegister)
EndIf

CoUninitialize_()


DataSection
  CLSID_ShellLink:
  ;00021401-0000-0000-C000-000000000046
  Data.l $00021401
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46
  
  ;UUIDs obtained from <windows.ui.notifications.h>
  ;ABI.Windows.UI.Notifications.IToastNotificationManagerStatics
  ;50ac103f-d235-4598-bbef-98fe4d1a3ad4
  IID_IToastNotificationManagerStatics:
  Data.l $50ac103f
  Data.w $d235, $4598
  Data.b $bb, $ef, $98, $fe, $4d, $1a, $3a, $d4
  
  ;ABI.Windows.Notifications.IToastNotificationFactory
  ;04124b20-82c6-4229-b109-fd9ed4662b53
  IID_IToastNotificationFactory:
  Data.l $04124b20
  Data.w $82c6, $4229
  Data.b $b1, $09, $fd, $9e, $d4, $66, $2b, $53
  
  ;UUIDs obtained from <windows.data.xml.dom.h>
  ;ABI.Windows.Data.Xml.Dom.IXmlDocument
  ;f7f3a506-1e87-42d6-bcfb-b8c809fa5494
  IID_IXmlDocument:
  Data.l $f7f3a506
  Data.w $1e87, $42d6
  Data.b $bc, $fb, $b8, $c8, $09, $fa, $54, $94
  
  ;ABI.Windows.Data.Xml.Dom.IXmlDocumentIO
  ;6cd0e74e-ee65-4489-9ebf-ca43e87ba637
  IID_IXmlDocumentIO:
  Data.l $6cd0e74e
  Data.w $ee65, $4489
  Data.b $9e, $bf, $ca, $43, $e8, $7b, $a6, $37
  
  IID_INotificationActivationCallback:
  ;53E31837-6600-4A81-9395-75CFFE746F94
  Data.l $53E31837
  Data.w $6600, $4A81
  Data.b $93, $95, $75, $CF, $FE, $74, $6F, $94
  
  IID_IShellLinkW:
  ;000214F9-0000-0000-C000-000000000046
  Data.l $000214F9
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46
  
  IID_IPersistFileW:
  ;0000010b-0000-0000-C000-000000000046 
  Data.l $0000010b 
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46 
  
  IID_IPropertyStore:
  ;886d8eeb-8cf2-4446-8d02-cdba1dbdcf99
  Data.l $886d8eeb
  Data.w $8cf2, $4446
  Data.b $8d, $02, $cd, $ba, $1d, $bd, $cf, $99
  
  IID_IUnknown:
  Data.l $00000000
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46
  
  IID_IToastActivatedEventArgs2:
  ;ab7da512-cc61-568e-81be-304ac31038fa
  Data.l $ab7da512
  Data.w $cc61, $568e
  Data.b $81, $be, $30, $4a, $c3, $10, $38, $fa
  
  IID_IToastActivatedEventArgs:
  ;E3BF92F3-C197-436F-8265-0625824F8DAC
  Data.l $E3BF92F3
  Data.w $C197, $436F
  Data.b $82, $65, $06, $25, $82, $4F, $8D, $AC
  
  IID_IToastFailedEventArgs:
  ;35176862-cfd4-44f8-ad64-f500fd896c3b
  Data.l $35176862
  Data.w $cfd4, $44f8
  Data.b $ad, $64, $f5, $00, $fd, $89, $6c, $3b
  
  IID_IToastDismissedEventArgs:
  ;3f89d935-d9cb-4538-a0f0-ffe7659938f8
  Data.l $3f89d935
  Data.w $d9cb, $4538
  Data.b $a0, $f0, $ff, $e7, $65, $99, $38, $f8
  
  IID___FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable:
  ;fe2f3d47-5d47-5499-8374-430c7cda0204
  Data.l $fe2f3d47
  Data.w $5d47, $5499
  Data.b $83, $74, $43, $0c, $7c, $da, $02, $04
  
  IID_IPropertyValue:
  ;4bd682dd-7554-40e9-9a9b-82654ede7e62
  Data.l $4bd682dd
  Data.w $7554, $40e9
  Data.b $9a, $9b, $82, $65, $4e, $de, $7e, $62
  
  IID_DesktopToastActivatedEventHandler:
  ;ab54de2d-97d9-5528-b6ad-105afe156530
  Data.l $ab54de2d
  Data.w $97d9, $5528
  Data.b $b6, $ad, $10, $5a, $fe, $15, $65, $30
  
  IID_DesktopToastDismissedEventHandler:
  ;61c2402f-0ed0-5a18-ab69-59f4aa99a368
  Data.l $61c2402f
  Data.w $0ed0, $5a18
  Data.b $ab, $69, $59, $f4, $aa, $99, $a3, $68
  
  IID_DesktopToastFailedEventHandler:
  ;95e3e803-c969-5e3a-9753-ea2ad22a9a33
  Data.l $95e3e803
  Data.w $c969, $5e3a
  Data.b $97, $53, $ea, $2a, $d2, $2a, $9a, $33
  
  IID_IClassFactory:
  Data.l $00000001
  Data.w $0000, $0000
  Data.b $C0, $0, $0, $0, $0, $0, $0, $46
  
  ;PROPERTYKEY structures
  ;propkey.h
  PKEY_AppUserModel_ID:
  ; { { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 5 }
  Data.l $9F4C2855
  Data.w $9F79, $4B39
  Data.b $A8, $D0, $E1, $D4, $2D, $E1, $D5, $F3
  Data.l 5
  
  PKEY_AppUserModel_ToastActivatorCLSID:
  ; { { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 26 }
  Data.l $9F4C2855
  Data.w $9F79, $4B39
  Data.b $A8, $D0, $E1, $D4, $2D, $E1, $D5, $F3
  Data.l 26
EndDataSection
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Windows Toastnotification without PowerShell

Post by infratec »

Awesome :!:

What a job to figure this out.

Thanks a lot for this.

I'm working on an eays way to make this solution useful for the daily work.
I already excluded the ComBase stuff in a module and combined the whole initialization an termination into
subroutines. If everything works as expected I also build a ToastNotification module.

One question:
I try to remove the link creation.
But as soon as I remove this part, the toast is not shown anymore.
In your first example it works without.
Any hints for me?

Of course I will present my solution as soon as they is ready.
breeze4me
Enthusiast
Enthusiast
Posts: 511
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Windows Toastnotification without PowerShell

Post by breeze4me »

infratec wrote: Thu Jun 02, 2022 3:46 pm One question:
I try to remove the link creation.
But as soon as I remove this part, the toast is not shown anymore.
In your first example it works without.
Any hints for me?
The link file is essential.
Sending toast notifications from desktop apps
https://docs.microsoft.com/en-us/previo ... (v=win.10)

Generally, sending a toast notification from a desktop app is the same as sending it from a Windows Store app. However, you should be aware of these differences and requirements:

- The app must have a shortcut installed (though not necessarily pinned) to the Start screen or in the Apps view
- The shortcut must have an AppUserModelID
- ......
You must create it from the installer of your program or directly in the program.

Here's a modularized code for easier use.

ToastNotification.pbi

Code: Select all

;- Module ToastNotification
DeclareModule ToastNotification
  Enumeration ;__x_ABI_CWindows_CUI_CNotifications_CToastDismissalReason
    #ToastDismissalReason_UserCanceled
    #ToastDismissalReason_ApplicationHidden
    #ToastDismissalReason_TimedOut
  EndEnumeration
  
  Structure STRUC_ToastEventDataKeyValue
    psKey.i
    psValue.i
  EndStructure
  
  Structure STRUC_ToastEventData
    psAppUserModelId.i
    psArguments.i
    iDataCount.i
    psKeyValue.STRUC_ToastEventDataKeyValue[0]
  EndStructure
  
  Declare Initialize(sAppUserModelId.s, sAppCLSID.s, iToastEvent, iToastEventTypeStart)
  Declare Finalize()
  Declare SendToastNotification(sXmlForm.s)
  Declare GetToastEventData(iToastEventType, *EventData = 0)
  Declare.s GetStringFromToastEventData(pString)
  Declare FreeToastEventData(*EventData.STRUC_ToastEventData)
EndDeclareModule

Module ToastNotification
  EnableExplicit
  ; Sending toast notifications from desktop apps
  ; https://docs.microsoft.com/en-us/previous-versions/windows/apps/hh779727(v=win.10)
  ; 
  ; Generally, sending a toast notification from a desktop app is the same as sending it from a Windows Store app. However, you should be aware of these differences and requirements:
  ;
  ; - The app must have a shortcut installed (though not necessarily pinned) to the Start screen or in the Apps view
  ; - The shortcut must have an AppUserModelID
  ; - Desktop apps cannot schedule a toast
  ; - All toasts raised by desktop apps are the same system-defined color
  ; - Desktop apps cannot use web images
  ; - Desktop app notifications will not appear on the lock screen
  
  ; How to enable desktop toast notifications through an AppUserModelID
  ; https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/hh802762(v=vs.85)
  
  ; Application User Model IDs (AppUserModelIDs)
  ; https://docs.microsoft.com/en-us/windows/win32/shell/appids
  
  CompilerIf #PB_Compiler_Debugger
    ;CompilerError "Turn OFF debugger.   "
  CompilerEndIf
  
  CompilerIf Not #PB_Compiler_Thread
    CompilerError "Turn ON thread-safe.   "
  CompilerEndIf
  
  ;- Constants.
  #REGCLS_SINGLEUSE = 0       ;class object only generates one instance
  #REGCLS_MULTIPLEUSE = 1     ;same class object genereates multiple inst. and local automatically goes into inproc tbl.
  #REGCLS_MULTI_SEPARATE = 2  ;multiple use, but separate control over each context.
  #REGCLS_SUSPENDED = 4       ;register is as suspended, will be activated when app calls CoResumeClassObjects
  #REGCLS_SURROGATE = 8       ;must be used when a surrogate process is registering a class object that will be loaded in the surrogate
  
  #CLSCTX_INPROC_SERVER	= $1
  #CLSCTX_INPROC_HANDLER	= $2
  #CLSCTX_LOCAL_SERVER	= $4
  #CLSCTX_INPROC_SERVER16	= $8
  #CLSCTX_REMOTE_SERVER	= $10
  #CLSCTX_INPROC_HANDLER16	= $20
  #CLSCTX_RESERVED1	= $40
  #CLSCTX_RESERVED2	= $80
  #CLSCTX_RESERVED3	= $100
  #CLSCTX_RESERVED4	= $200
  #CLSCTX_NO_CODE_DOWNLOAD	= $400
  #CLSCTX_RESERVED5	= $800
  #CLSCTX_NO_CUSTOM_MARSHAL	= $1000
  #CLSCTX_ENABLE_CODE_DOWNLOAD	= $2000
  #CLSCTX_NO_FAILURE_LOG	= $4000
  #CLSCTX_DISABLE_AAA	= $8000
  #CLSCTX_ENABLE_AAA	= $10000
  #CLSCTX_FROM_DEFAULT_CONTEXT	= $20000
  #CLSCTX_ACTIVATE_X86_SERVER	= $40000
  #CLSCTX_ACTIVATE_32_BIT_SERVER	= #CLSCTX_ACTIVATE_X86_SERVER
  #CLSCTX_ACTIVATE_64_BIT_SERVER	= $80000
  #CLSCTX_ENABLE_CLOAKING	= $100000
  #CLSCTX_APPCONTAINER	= $400000
  #CLSCTX_ACTIVATE_AAA_AS_IU	= $800000
  #CLSCTX_RESERVED6	= $1000000
  #CLSCTX_ACTIVATE_ARM32_SERVER	= $2000000
  #CLSCTX_PS_DLL	= $80000000
  
  #RO_INIT_MULTITHREADED = 1
  
  ;__x_ABI_CWindows_CFoundation_CPropertyType
  #PropertyType_Empty = 0
  #PropertyType_UInt8 = 1
  #PropertyType_Int16 = 2
  #PropertyType_UInt16 = 3
  #PropertyType_Int32 = 4
  #PropertyType_UInt32 = 5
  #PropertyType_Int64 = 6
  #PropertyType_UInt64 = 7
  #PropertyType_Single = 8
  #PropertyType_Double = 9
  #PropertyType_Char16 = 10
  #PropertyType_Boolean = 11
  #PropertyType_String = 12
  #PropertyType_Inspectable = 13
  #PropertyType_DateTime = 14
  #PropertyType_TimeSpan = 15
  #PropertyType_Guid = 16
  #PropertyType_Point = 17
  #PropertyType_Size = 18
  #PropertyType_Rect = 19
  #PropertyType_OtherType = 20
  #PropertyType_UInt8Array = 1025
  #PropertyType_Int16Array = 1026
  #PropertyType_UInt16Array = 1027
  #PropertyType_Int32Array = 1028
  #PropertyType_UInt32Array = 1029
  #PropertyType_Int64Array = 1030
  #PropertyType_UInt64Array = 1031
  #PropertyType_SingleArray = 1032
  #PropertyType_DoubleArray = 1033
  #PropertyType_Char16Array = 1034
  #PropertyType_BooleanArray = 1035
  #PropertyType_StringArray = 1036
  #PropertyType_InspectableArray = 1037
  #PropertyType_DateTimeArray = 1038
  #PropertyType_TimeSpanArray = 1039
  #PropertyType_GuidArray = 1040
  #PropertyType_PointArray = 1041
  #PropertyType_SizeArray = 1042
  #PropertyType_RectArray = 1043
  #PropertyType_OtherTypeArray = 1044
  
  Enumeration
    #ToastEventType_Launched
    #ToastEventType_Activated
    #ToastEventType_Dismissed
    #ToastEventType_Failed
  EndEnumeration
  
  ;- Interfaces.
  ;{
  ;winrt\inspectable.h
  Interface IInspectable Extends IUnknown
    GetIids(*iidCount, *iids)
    GetRuntimeClassName(*className)
    GetTrustLevel(*trustLevel)
  EndInterface
  
  ;winrt\Windows.Data.Xml.Dom.h
  Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument Extends IInspectable
    get_Doctype(*value)
    get_Implementation(*value)
    get_DocumentElement(*value)
    CreateElement(tagName, *newElement)
    CreateDocumentFragment(*newDocumentFragment)
    CreateTextNode(hStringData, *newTextNode)
    CreateComment(hStringData, *newComment)
    CreateProcessingInstruction(target, hStringData, *newProcessingInstruction)
    CreateAttribute(name, *newAttribute)
    CreateEntityReference(name, *newEntityReference)
    GetElementsByTagName(tagName, *elements)
    CreateCDataSection(hStringData, *newCDataSection)
    get_DocumentUri(*value)
    CreateAttributeNS(*namespaceUri, qualifiedName, *newAttribute)
    CreateElementNS(*namespaceUri, qualifiedName, *newElement)
    GetElementById(elementId, *element)
    ImportNode(*node, deep, *newNode)
  EndInterface
  
  Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlElement Extends IInspectable
    get_TagName(*value)
    GetAttribute(attributeName, *attributeValue)
    SetAttribute(attributeName, attributeValue)
    RemoveAttribute(attributeName)
    GetAttributeNode(attributeName, *attributeNode)
    SetAttributeNode(*newAttribute, *previousAttribute)
    RemoveAttributeNode(*attributeNode, *removedAttribute)
    GetElementsByTagName(tagName, *elements)
    SetAttributeNS(*namespaceUri, qualifiedName, value)
    GetAttributeNS(*namespaceUri, localName, *value)
    RemoveAttributeNS(*namespaceUri, localName)
    SetAttributeNodeNS(*newAttribute, *previousAttribute)
    GetAttributeNodeNS(*namespaceUri, localName, *previousAttribute)
  EndInterface
  
  Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlNodeList Extends IInspectable
    get_Length(*value)
    Item(index, *node)
  EndInterface
  
  Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlNode Extends IInspectable
    get_NodeValue(*value)
    put_NodeValue(*value)
    get_NodeType(*value)
    get_NodeName(*value)
    get_ParentNode(*value)
    get_ChildNodes(*value)
    get_FirstChild(*value)
    get_LastChild(*value)
    get_PreviousSibling(*value)
    get_NextSibling(*value)
    get_Attributes(*value)
    HasChildNodes(*value)
    get_OwnerDocument(*value)
    InsertBefore(*newChild, *referenceChild, *insertedChild)
    ReplaceChild(*newChild, *referenceChild, *previousChild)
    RemoveChild(*childNode, *removedChild)
    AppendChild(*newChild, *appendedChild)
    CloneNode(deep, *newNode)
    get_NamespaceUri(*value)
    get_LocalName(*value)
    get_Prefix(*value)
    Normalize()
    put_Prefix(*value)
  EndInterface
  
  Interface __x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO Extends IInspectable
    LoadXML(xml)
    LoadXmlWithSettings(xml, *loadSettings)
    SaveToFileAsync(*file, *asyncInfo)
  EndInterface
  
  Macro __x_ABI_CWindows_CFoundation_CCollections_CIPropertySet
    IInspectable
  EndMacro
  
  ;winrt\windows.foundation.h
  Interface __FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable Extends IInspectable
    First(*result)
  EndInterface
  
  Interface __FIIterator_1___FIKeyValuePair_2_HSTRING_IInspectable Extends IInspectable
    get_Current(*result)
    get_HasCurrent(*result)
    MoveNext(*result)
    GetMany(itemsLength, *items, *result)
  EndInterface
  
  Interface __FIKeyValuePair_2_HSTRING_IInspectable Extends IInspectable
    get_Key(*result)
    get_Value(*result)
  EndInterface
  
  Interface __x_ABI_CWindows_CFoundation_CIPropertyValue Extends IInspectable
    get_Type(*value)
    get_IsNumericScalar(*value)
    GetUInt8(*value)
    GetInt16(*value)
    GetUInt16(*value)
    GetInt32(*value)
    GetUInt32(*value)
    GetInt64(*value)
    GetUInt64(*value)
    GetSingle(*value)
    GetDouble(*value)
    GetChar16(*value)
    GetBoolean(*value)
    GetString(*value)
    GetGuid(*value)
    GetDateTime(*value)
    GetTimeSpan(*value)
    GetPoint(*value)
    GetSize(*value)
    GetRect(*value)
    GetUInt8Array(*valueLength, *value)
    GetInt16Array(*valueLength, *value)
    GetUInt16Array(*valueLength, *value)
    GetInt32Array(*valueLength, *value)
    GetUInt32Array(*valueLength, *value)
    GetInt64Array(*valueLength, *value)
    GetUInt64Array(*valueLength, *value)
    GetSingleArray(*valueLength, *value)
    GetDoubleArray(*valueLength, *value)
    GetChar16Array(*valueLength, *value)
    GetBooleanArray(*valueLength, *value)
    GetStringArray(*valueLength, *value)
    GetInspectableArray(*valueLength, *value)
    GetGuidArray(*valueLength, *value)
    GetDateTimeArray(*valueLength, *value)
    GetTimeSpanArray(*valueLength, *value)
    GetPointArray(*valueLength, *value)
    GetSizeArray(*valueLength, *value)
    GetRectArray(*valueLength, *value)
  EndInterface
  
  ;winrt\windows.ui.notifications.h
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics Extends IInspectable
    CreateToastNotifier(*result)
    CreateToastNotifierWithId(applicationId, *result)
    GetTemplateContent(type, *result)
  EndInterface
  
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotifier Extends IInspectable
    Show(*notification)
    Hide(*notification)
    get_Setting(*value)
    AddToSchedule(*scheduledToast)
    RemoveFromSchedule(*scheduledToast)
    GetScheduledToastNotifications(*result)
  EndInterface
  
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory Extends IInspectable
    CreateToastNotification(*content, *value)
  EndInterface
  
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastNotification Extends IInspectable
    get_Content(*value)
    put_ExpirationTime(*value)
    get_ExpirationTime(*value)
    add_Dismissed(*handler, *token)
    remove_Dismissed(token.q)
    add_Activated(*handler, *token)
    remove_Activated(token.q)
    add_Failed(*handler, *token)
    remove_Failed(token.q)
  EndInterface
  
  ; Interface __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_IInspectable Extends IUnknown
  ;   Invoke(*sender, *args)
  ; EndInterface
  ; Interface __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastDismissedEventArgs Extends IUnknown
  ;   Invoke(*sender, *args)
  ; EndInterface
  ; Interface __FITypedEventHandler_2_Windows__CUI__CNotifications__CToastNotification_Windows__CUI__CNotifications__CToastFailedEventArgs Extends IUnknown
  ;   Invoke(*sender, *args)
  ; EndInterface
  Interface ITypedEventHandler_2_ToastEventArgs Extends IUnknown
    Invoke(*sender, *args)
  EndInterface
  
  ;um\NotificationActivationCallback.h
  Interface INotificationActivationCallback Extends IUnknown
    Activate(*appUserModelId, *invokedArgs, *data, count)
  EndInterface
  
  ;winrt\windows.ui.notifications.h
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs2 Extends IInspectable
    get_UserInput(*value)
  EndInterface
  
  ;winrt\windows.ui.notifications.h
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs Extends IInspectable
    get_Arguments(*value)
  EndInterface
  
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastDismissedEventArgs Extends IInspectable
    get_Reason(*value)
  EndInterface
  
  Interface __x_ABI_CWindows_CUI_CNotifications_CIToastFailedEventArgs Extends IInspectable
    get_ErrorCode(*value)
  EndInterface
  ;}
  
  ;- Structures.
  
  ;winrt\hstring.h
  Structure HSTRING
    unused.i
  EndStructure
  
  Structure HSTRING_HEADER
    StructureUnion
      *Reserved1
      CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
        Reserved2.c[24]
      CompilerElse
        Reserved2.c[20]
      CompilerEndIf
    EndStructureUnion
  EndStructure
  
  ;winrt\EventToken.h
  Structure EventRegistrationToken
    value.q
  EndStructure
  
  ;um\NotificationActivationCallback.h
  Structure NOTIFICATION_USER_INPUT_DATA
    *pKey
    *pValue
  EndStructure
  
  ;shared\wtypes.h
  Structure PROPERTYKEY
    fmtid.GUID
    pid.l
  EndStructure
  
  Structure STRUC_IUnknownBase
    *pVtbl
    lRefCount.l
    lDismissCount.l
    *pActivatedHandler.STRUC_IUnknownBase
    *pDismissedHandler.STRUC_IUnknownBase
    *pFailedHandler.STRUC_IUnknownBase
    hEventExitThread.i
  EndStructure
  
  Structure STRUC_IUnknownVtbl Extends STRUC_IUnknownBase
    *pQueryInterface
    *pAddRef
    *pRelease
  EndStructure
  
  Structure STRUC_IClassFactoryVtbl Extends STRUC_IUnknownVtbl
    *pCreateInstance
    *pLockServer
  EndStructure
  
  Structure STRUC_INotificationActivationCallbackVtbl Extends STRUC_IUnknownVtbl
    *pActivate
  EndStructure
  
  Structure STRUC_ToastEventHandler Extends STRUC_IUnknownVtbl
    *pInvoke
  EndStructure
  
  ;- Global variables.
  Global gToastEvent, gToastEventType, gModuleInit
  Global gAppUserModelId.s
  Global hEventAllThreadExit
  Global LibComBase
  Global gRegister.l
  Global *NotificationActivationCallback.STRUC_INotificationActivationCallbackVtbl
  Global *ClassFactory.STRUC_IClassFactoryVtbl
  Global NewList Threads.i()
  
  Global RuntimeClass_Windows_Data_Xml_Dom_XmlDocument.s = "Windows.Data.Xml.Dom.XmlDocument"
  Global RuntimeClass_Windows_UI_Notifications_ToastNotificationManager.s = "Windows.UI.Notifications.ToastNotificationManager"
  Global RuntimeClass_Windows_UI_Notifications_ToastNotification.s = "Windows.UI.Notifications.ToastNotification"
  
  ;- Prototypes.
  Prototype ptRoInitialize(initType)
  Global RoInitialize.ptRoInitialize
  
  Prototype ptRoUninitialize()
  Global RoUninitialize.ptRoUninitialize
  
  Prototype ptRoGetActivationFactory(activatableClassId, iid, *factory)
  Global RoGetActivationFactory.ptRoGetActivationFactory
  
  Prototype ptWindowsCreateStringReference(*sourceString, length.l, *hstringHeader, *string)
  Global WindowsCreateStringReference.ptWindowsCreateStringReference
  
  Prototype ptRoActivateInstance(activatableClassId, *instance)
  Global RoActivateInstance.ptRoActivateInstance
  
  Prototype ptWindowsGetStringRawBuffer(string, *length)
  Global WindowsGetStringRawBuffer.ptWindowsGetStringRawBuffer
  
  Prototype ptWindowsDeleteString(string)
  Global WindowsDeleteString.ptWindowsDeleteString
  
  ;- Functions.
  Procedure GetHStringBuffer(HString)
    Protected *buffer
    If HString
      *buffer = WindowsGetStringRawBuffer(HString, 0)
      If *buffer
        ProcedureReturn *buffer
      EndIf
    EndIf
    ProcedureReturn 0
  EndProcedure
  
  Procedure.l IUnknown_AddRef(*this.STRUC_IUnknownBase)
    *this\lRefCount + 1
    ProcedureReturn *this\lRefCount
  EndProcedure
  
  Procedure.l IUnknown_Release(*this.STRUC_IUnknownBase)
    *this\lRefCount - 1
    ;No need to call FreeMemory.
    ProcedureReturn *this\lRefCount
  EndProcedure
  
  Procedure.l INotificationActivationCallback_QueryInterface(*this.STRUC_IUnknownBase, *riid.IID, *ppvObject.Integer)
    If *ppvObject And *riid
      If CompareMemory(*riid, ?IID_IUnknown, SizeOf(IID)) Or CompareMemory(*riid, ?IID_INotificationActivationCallback, SizeOf(IID))
        *this\lRefCount + 1
        *ppvObject\i = *this
        
      Else
        *ppvObject\i = 0
        ProcedureReturn #E_NOINTERFACE
      EndIf
    Else
      ProcedureReturn #E_POINTER
    EndIf
    
    ProcedureReturn #S_OK
  EndProcedure
  
  Procedure.l INotificationActivationCallback_Activate(*this, *appUserModelId, *invokedArgs, *data.NOTIFICATION_USER_INPUT_DATA, Count)
    Protected r, i
    Protected *LaunchedData.STRUC_ToastEventData
    
    *LaunchedData = AllocateMemory(SizeOf(STRUC_ToastEventData) + SizeOf(STRUC_ToastEventDataKeyValue) * Count)
    If *LaunchedData = 0 : ProcedureReturn #S_OK : EndIf
    
    If *appUserModelId
      *LaunchedData\psAppUserModelId = SysAllocString_(*appUserModelId)
    EndIf
    
    If *invokedArgs
      *LaunchedData\psArguments = SysAllocString_(*invokedArgs)
    EndIf
    
    *LaunchedData\iDataCount = Count
    
    If Count > 0 And *data
      For i = 0 To Count - 1
        If *data\pKey
          *LaunchedData\psKeyValue[i]\psKey = SysAllocString_(*data\pKey)
        EndIf
        
        If *data\pValue
          *LaunchedData\psKeyValue[i]\psValue = SysAllocString_(*data\pValue)
        EndIf
        
        *data + SizeOf(NOTIFICATION_USER_INPUT_DATA)
      Next
    EndIf
    
    PostEvent(gToastEvent, 0, 0, gToastEventType + #ToastEventType_Launched, *LaunchedData)
    
    ProcedureReturn #S_OK
  EndProcedure
  
  Procedure.l IClassFactory_QueryInterface(*this.STRUC_IUnknownBase, *riid.IID, *ppvObject.Integer)
    If *riid And *ppvObject
      If CompareMemory(*riid, ?IID_IUnknown, SizeOf(IID)) Or CompareMemory(*riid, ?IID_IClassFactory, SizeOf(IID))
        *this\lRefCount + 1
        *ppvObject\i = *this
        
      ElseIf CompareMemory(*riid, ?IID_INotificationActivationCallback, SizeOf(IID))
        *NotificationActivationCallback\lRefCount + 1
        *ppvObject\i = *NotificationActivationCallback
        
      Else
        *ppvObject\i = 0
        ProcedureReturn #E_NOINTERFACE
      EndIf
    Else
      ProcedureReturn #E_POINTER
    EndIf
    
    ProcedureReturn #S_OK
  EndProcedure
  
  Procedure.l IClassFactory_CreateInstance(*this, *pUnkOuter.IUnknown, *riid.IID, *ppvObject.Integer)
    Protected ClassFactory.IClassFactory = *ClassFactory
    
    If *riid = 0 Or *ppvObject = 0
      ProcedureReturn #E_INVALIDARG
    EndIf
    
    If *pUnkOuter
      *ppvObject\i = 0
      ProcedureReturn #CLASS_E_NOAGGREGATION
    EndIf
    
    ProcedureReturn ClassFactory\QueryInterface(*riid, *ppvObject)
  EndProcedure
  
  Procedure.l IClassFactory_LockServer(*this, fLock)
    ProcedureReturn #E_UNEXPECTED
  EndProcedure
  
  Procedure.l ToastEventHandler_QueryInterface(*this.STRUC_IUnknownBase, *riid.IID, *ppvObject.Integer)
    If *ppvObject And *riid
      If CompareMemory(*riid, ?IID_IUnknown, SizeOf(IID))
        *this\lRefCount + 1
        *ppvObject\i = *this
        
      ElseIf CompareMemory(*riid, ?IID_DesktopToastActivatedEventHandler, SizeOf(IID))
        *this\pActivatedHandler\lRefCount + 1
        *ppvObject\i = *this\pActivatedHandler
        
      ElseIf CompareMemory(*riid, ?IID_DesktopToastDismissedEventHandler, SizeOf(IID))
        *this\pDismissedHandler\lRefCount + 1
        *ppvObject\i = *this\pDismissedHandler
        
      ElseIf CompareMemory(*riid, ?IID_DesktopToastFailedEventHandler, SizeOf(IID))
        *this\pFailedHandler\lRefCount + 1
        *ppvObject\i = *this\pFailedHandler
        
      Else
        *ppvObject\i = 0
        ProcedureReturn #E_NOINTERFACE
      EndIf
    Else
      ProcedureReturn #E_POINTER
    EndIf
    
    ProcedureReturn #S_OK
  EndProcedure
  
  Procedure.l ActivatedEventHandler_Invoke(*this.STRUC_IUnknownBase, *sender.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification, *args.__x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs)
    Protected r, hsArguments, hsKey, hsValue, Type
    Protected *sArguments, *sKey, *sValue
    Protected *args2.__x_ABI_CWindows_CUI_CNotifications_CIToastActivatedEventArgs2
    Protected *ui.IInspectable
    Protected *iterable.__FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable
    Protected *iterator.__FIIterator_1___FIKeyValuePair_2_HSTRING_IInspectable
    Protected *KeyValuePair.__FIKeyValuePair_2_HSTRING_IInspectable
    Protected *value.IInspectable
    Protected *pv.__x_ABI_CWindows_CFoundation_CIPropertyValue
    Protected *ActivatedData.STRUC_ToastEventData
    
    If *args = 0 : ProcedureReturn #E_POINTER : EndIf
    
    r = *args\get_Arguments(@hsArguments)
    If r <> #S_OK : Goto _Proc_Exit : EndIf
    
    r = *args\QueryInterface(?IID_IToastActivatedEventArgs2, @*args2)
    If r <> #S_OK Or *args2 = 0 : Goto _Proc_Exit : EndIf
    
    r = *args2\get_UserInput(@*ui)
    If r <> #S_OK Or *ui = 0 : Goto _Proc_Exit : EndIf
    
    r = *ui\QueryInterface(?IID___FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable, @*iterable)
    If r <> #S_OK Or *iterable = 0 : Goto _Proc_Exit : EndIf
    
    r = *iterable\First(@*iterator)
    If r <> #S_OK Or *iterator = 0 : Goto _Proc_Exit : EndIf
    
    r = *iterator\get_Current(@*KeyValuePair)
    If r <> #S_OK Or *KeyValuePair = 0 : Goto _Proc_Exit : EndIf
    
    r = *KeyValuePair\get_Key(@hsKey)
    If r <> #S_OK : Goto _Proc_Exit : EndIf
    
    r = *KeyValuePair\get_Value(@*value)
    If r <> #S_OK Or *value = 0 : Goto _Proc_Exit : EndIf
    
    r = *value\QueryInterface(?IID_IPropertyValue, @*pv)
    If r <> #S_OK Or *pv = 0 : Goto _Proc_Exit : EndIf
    
    r = *pv\get_Type(@Type)
    If r <> #S_OK : Goto _Proc_Exit : EndIf
    
    If Type = #PropertyType_String
      r = *pv\GetString(@hsValue)
      If r <> #S_OK : Goto _Proc_Exit : EndIf
      
      *ActivatedData = AllocateMemory(SizeOf(STRUC_ToastEventData) + SizeOf(STRUC_ToastEventDataKeyValue))
      If *ActivatedData
        With *ActivatedData
          \iDataCount = 1
          
          *sArguments = GetHStringBuffer(hsArguments)
          If *sArguments
            \psArguments = SysAllocString_(*sArguments)
          EndIf
          
          *sKey = GetHStringBuffer(hsKey)
          If *sKey
            \psKeyValue[0]\psKey = SysAllocString_(*sKey)
          EndIf
          
          *sValue = GetHStringBuffer(hsValue)
          If *sValue
            \psKeyValue[0]\psValue = SysAllocString_(*sValue)
          EndIf
        EndWith
        PostEvent(gToastEvent, 0, 0, gToastEventType + #ToastEventType_Activated, *ActivatedData)
      EndIf
    EndIf
    
    _Proc_Exit:
    If hsArguments : WindowsDeleteString(hsArguments) : EndIf
    If hsKey : WindowsDeleteString(hsKey) : EndIf
    If hsValue : WindowsDeleteString(hsValue) : EndIf
    
    If *pv : *pv\Release() : EndIf
    If *value : *value\Release() : EndIf
    If *KeyValuePair : *KeyValuePair\Release() : EndIf
    If *iterator : *iterator\Release() : EndIf
    If *iterable : *iterable\Release() : EndIf
    If *ui : *ui\Release() : EndIf
    If *args2 : *args2\Release() : EndIf
    
    If *this\hEventExitThread
      SetEvent_(*this\hEventExitThread)
    EndIf
    
    ProcedureReturn #S_OK
  EndProcedure
  
  Procedure.l DismissedEventHandler_Invoke(*this.STRUC_IUnknownBase, *sender.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification, *args.__x_ABI_CWindows_CUI_CNotifications_CIToastDismissedEventArgs)
    Protected ReasonCode
    
    If *args = 0 : ProcedureReturn #E_POINTER : EndIf
    
    If *args\get_Reason(@ReasonCode) = #S_OK
      PostEvent(gToastEvent, 0, 0, gToastEventType + #ToastEventType_Dismissed, ReasonCode)
    EndIf
    
    If *this\lDismissCount > 0
      If *this\hEventExitThread
        SetEvent_(*this\hEventExitThread)
      EndIf
    Else
      *this\lDismissCount + 1
    EndIf
    
    ProcedureReturn #S_OK
  EndProcedure
  
  Procedure.l FailedEventHandler_Invoke(*this.STRUC_IUnknownBase, *sender.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification, *args.__x_ABI_CWindows_CUI_CNotifications_CIToastFailedEventArgs)
    Protected Result.l
    
    If *args = 0 : ProcedureReturn #E_POINTER : EndIf
    
    If *args\get_ErrorCode(@Result) = #S_OK
      PostEvent(gToastEvent, 0, 0, gToastEventType + #ToastEventType_Failed, Result)
    EndIf
    
    If *this\hEventExitThread
      SetEvent_(*this\hEventExitThread)
    EndIf
    
    ProcedureReturn #S_OK
  EndProcedure
  
  Procedure CreateXmlDocumentFromString(*SrcXmlString, *idoc.Integer)
    Protected Result = #E_FAIL
    Protected *doc.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument
    Protected *docIO.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocumentIO
    Protected *pInspectable.IInspectable
    Protected header_IXmlDocumentHString.HSTRING_HEADER, *IXmlDocumentHString.HSTRING
    Protected header_XmlString.HSTRING_HEADER, *XmlString.HSTRING
    
    ;If *SrcXmlString = 0 Or *idoc = 0: ProcedureReturn #E_INVALIDARG : EndIf
    
    Result = WindowsCreateStringReference(@RuntimeClass_Windows_Data_Xml_Dom_XmlDocument, Len(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument), @header_IXmlDocumentHString, @*IXmlDocumentHString)
    If Result <> #S_OK : ProcedureReturn Result :	EndIf
    If *IXmlDocumentHString = 0 : ProcedureReturn #E_POINTER : EndIf
    
    Result = RoActivateInstance(*IXmlDocumentHString, @*pInspectable)
    If Result <> #S_OK : ProcedureReturn Result : EndIf
    If *pInspectable = 0 : ProcedureReturn #E_NOINTERFACE : EndIf
    
    Result = *pInspectable\QueryInterface(?IID_IXmlDocument, @*doc)
    If Result <> #S_OK : Goto _Proc_Exit : EndIf
    If *doc = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
    
    Result = *doc\QueryInterface(?IID_IXmlDocumentIO, @*docIO)
    If Result <> #S_OK : Goto _Proc_Exit : EndIf
    If *docIO = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
    
    Result = WindowsCreateStringReference(*SrcXmlString, MemoryStringLength(*SrcXmlString), @header_XmlString, @*XmlString)
    If Result <> #S_OK : Goto _Proc_Exit : EndIf
    If *XmlString = 0 : Result = #E_POINTER : Goto _Proc_Exit : EndIf
    
    Result = *docIO\LoadXML(*XmlString)
    
    _Proc_Exit:
    
    If Result = #S_OK
      *idoc\i = *doc
    Else
      If *doc : *doc\Release() : EndIf
    EndIf
    
    If *docIO : *docIO\Release() : EndIf
    If *pInspectable : *pInspectable\Release() : EndIf
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure ShowMsg(*xml)
    Protected Result
    Protected header_AppIdHString.HSTRING_HEADER, *AppIdHString.HSTRING
    Protected header_ToastNotificationManagerHString.HSTRING_HEADER, *ToastNotificationManagerHString.HSTRING
    Protected header_ToastNotificationHString.HSTRING_HEADER, *ToastNotificationHString.HSTRING
    Protected *inputXml.__x_ABI_CWindows_CData_CXml_CDom_CIXmlDocument
    Protected *toastStatics.__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationManagerStatics
    Protected *notifier.__x_ABI_CWindows_CUI_CNotifications_CIToastNotifier
    Protected *notifFactory.__x_ABI_CWindows_CUI_CNotifications_CIToastNotificationFactory
    Protected *toast.__x_ABI_CWindows_CUI_CNotifications_CIToastNotification
    Protected ActivatedToken.EventRegistrationToken, DismissedToken.EventRegistrationToken, FailedToken.EventRegistrationToken
    Protected *DismissedEventHandler.STRUC_ToastEventHandler
    Protected *ActivatedEventHandler.STRUC_ToastEventHandler
    Protected *FailedEventHandler.STRUC_ToastEventHandler
    Protected Dim hEventThreadExit.i(1)
    
    If *xml = 0 : ProcedureReturn : EndIf
    
    If RoInitialize(#RO_INIT_MULTITHREADED) <> #S_OK : Goto _Proc_Exit_FreeString : EndIf
    
    If WindowsCreateStringReference(@gAppUserModelId, Len(gAppUserModelId), @header_AppIdHString, @*AppIdHString) <> #S_OK : Goto _Proc_Exit : EndIf
    If *AppIdHString = 0 : Goto _Proc_Exit : EndIf
    
    If CreateXmlDocumentFromString(*xml, @*inputXml) <> #S_OK : Goto _Proc_Exit : EndIf
    If *inputXml = 0 : Goto _Proc_Exit : EndIf
    
    If WindowsCreateStringReference(@RuntimeClass_Windows_UI_Notifications_ToastNotificationManager, Len(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager),
                                    @header_ToastNotificationManagerHString, @*ToastNotificationManagerHString) <> #S_OK : Goto _Proc_Exit : EndIf
    If *ToastNotificationManagerHString = 0 : Goto _Proc_Exit : EndIf
    
    If RoGetActivationFactory(*ToastNotificationManagerHString, ?IID_IToastNotificationManagerStatics, @*toastStatics) <> #S_OK : Goto _Proc_Exit : EndIf
    If *toastStatics = 0 : Goto _Proc_Exit : EndIf
    
    If *toastStatics\CreateToastNotifierWithId(*AppIdHString, @*notifier) <> #S_OK : Goto _Proc_Exit : EndIf
    If *notifier = 0 : Goto _Proc_Exit : EndIf
    
    If WindowsCreateStringReference(@RuntimeClass_Windows_UI_Notifications_ToastNotification, Len(RuntimeClass_Windows_UI_Notifications_ToastNotification),
                                    @header_ToastNotificationHString, @*ToastNotificationHString) <> #S_OK : Goto _Proc_Exit : EndIf
    If *ToastNotificationHString = 0 : Goto _Proc_Exit : EndIf
    
    If RoGetActivationFactory(*ToastNotificationHString, ?IID_IToastNotificationFactory, @*notifFactory) <> #S_OK : Goto _Proc_Exit : EndIf
    If *notifFactory = 0 : Goto _Proc_Exit : EndIf
    
    If *notifFactory\CreateToastNotification(*inputXml, @*toast) <> #S_OK : Goto _Proc_Exit : EndIf
    If *toast = 0 : Goto _Proc_Exit : EndIf
    
    *ActivatedEventHandler = AllocateMemory(SizeOf(STRUC_ToastEventHandler))
    *DismissedEventHandler = AllocateMemory(SizeOf(STRUC_ToastEventHandler))
    *FailedEventHandler = AllocateMemory(SizeOf(STRUC_ToastEventHandler))
    If *ActivatedEventHandler = 0 Or *DismissedEventHandler = 0 Or *FailedEventHandler = 0 : Goto _Proc_Exit : EndIf
    
    hEventThreadExit(1) = CreateEvent_(0, 0, 0, 0)
    If hEventThreadExit(1) = 0 : Goto _Proc_Exit : EndIf
    
    hEventThreadExit(0) = hEventAllThreadExit
    
    With *ActivatedEventHandler
      \pVtbl = *ActivatedEventHandler + OffsetOf(STRUC_ToastEventHandler\pQueryInterface)
      \pActivatedHandler = *ActivatedEventHandler
      \pDismissedHandler = *DismissedEventHandler
      \pFailedHandler = *FailedEventHandler
      \hEventExitThread = hEventThreadExit(1)
      \pQueryInterface = @ToastEventHandler_QueryInterface()
      \pAddRef = @IUnknown_AddRef()
      \pRelease = @IUnknown_Release()
      \pInvoke = @ActivatedEventHandler_Invoke()
    EndWith
    
    With *DismissedEventHandler
      \pVtbl = *DismissedEventHandler + OffsetOf(STRUC_ToastEventHandler\pQueryInterface)
      \pActivatedHandler = *ActivatedEventHandler
      \pDismissedHandler = *DismissedEventHandler
      \pFailedHandler = *FailedEventHandler
      \hEventExitThread = hEventThreadExit(1)
      \pQueryInterface = @ToastEventHandler_QueryInterface()
      \pAddRef = @IUnknown_AddRef()
      \pRelease = @IUnknown_Release()
      \pInvoke = @DismissedEventHandler_Invoke()
    EndWith
    
    With *FailedEventHandler
      \pVtbl = *FailedEventHandler + OffsetOf(STRUC_ToastEventHandler\pQueryInterface)
      \pActivatedHandler = *ActivatedEventHandler
      \pDismissedHandler = *DismissedEventHandler
      \pFailedHandler = *FailedEventHandler
      \hEventExitThread = hEventThreadExit(1)
      \pQueryInterface = @ToastEventHandler_QueryInterface()
      \pAddRef = @IUnknown_AddRef()
      \pRelease = @IUnknown_Release()
      \pInvoke = @FailedEventHandler_Invoke()
    EndWith
    
    If *toast\add_Activated(*ActivatedEventHandler, @ActivatedToken) <> #S_OK : Goto _Proc_Exit : EndIf
    If *toast\add_Dismissed(*DismissedEventHandler, @DismissedToken) <> #S_OK : Goto _Proc_Exit : EndIf
    If *toast\add_Failed(*FailedEventHandler, @FailedToken) <> #S_OK : Goto _Proc_Exit : EndIf
    
    Result = *notifier\Show(*toast)
    
    If Result = #S_OK
      Result = WaitForMultipleObjects_(2, @hEventThreadExit(), 0, #INFINITE)
      If Result - #WAIT_OBJECT_0 = 1
        Sleep_(1000)
      EndIf
    EndIf
    
    _Proc_Exit:
    
    If *toast
      If ActivatedToken\value
        *toast\remove_Activated(ActivatedToken\value)
      EndIf
      If DismissedToken\value
        *toast\remove_Dismissed(DismissedToken\value)
      EndIf
      If FailedToken\value
        *toast\remove_Failed(FailedToken\value)
      EndIf
    EndIf
    
    If *toast : *toast\Release() : EndIf
    If *notifFactory : *notifFactory\Release() : EndIf
    If *notifier : *notifier\Release() : EndIf
    If *toastStatics : *toastStatics\Release() : EndIf
    If *inputXml : *inputXml\Release() : EndIf
    
    If *ActivatedEventHandler : FreeMemory(*ActivatedEventHandler) : EndIf
    If *DismissedEventHandler : FreeMemory(*DismissedEventHandler) : EndIf
    If *FailedEventHandler : FreeMemory(*FailedEventHandler) : EndIf
    
    If hEventThreadExit(1) : CloseHandle_(hEventThreadExit(1)) : EndIf
    
    RoUninitialize()
    
    _Proc_Exit_FreeString:
    If *xml : FreeMemory(*xml) : EndIf
  EndProcedure
  
  Procedure InitInterfaces()
    *NotificationActivationCallback = AllocateMemory(SizeOf(STRUC_INotificationActivationCallbackVtbl))
    If *NotificationActivationCallback = 0 : ProcedureReturn 0 : EndIf
    
    *ClassFactory = AllocateMemory(SizeOf(STRUC_IClassFactoryVtbl))
    If *ClassFactory = 0 : ProcedureReturn 0 : EndIf
    
    With *ClassFactory
      \pVtbl = *ClassFactory + OffsetOf(STRUC_IClassFactoryVtbl\pQueryInterface)
      \pQueryInterface = @IClassFactory_QueryInterface()
      \pAddRef = @IUnknown_AddRef()
      \pRelease = @IUnknown_Release()
      \pCreateInstance = @IClassFactory_CreateInstance()
      \pLockServer = @IClassFactory_LockServer()
    EndWith
    
    With *NotificationActivationCallback
      \pVtbl = *NotificationActivationCallback + OffsetOf(STRUC_INotificationActivationCallbackVtbl\pQueryInterface)
      \pQueryInterface = @INotificationActivationCallback_QueryInterface()
      \pAddRef = @IUnknown_AddRef()
      \pRelease = @IUnknown_Release()
      \pActivate = @INotificationActivationCallback_Activate()
    EndWith
    
    ProcedureReturn 1
  EndProcedure
  
  Procedure SendToastNotification(sXmlForm.s)
    Protected *xml, Result
    
    If sXmlForm And gModuleInit
      If AddElement(Threads())
        *xml = AllocateMemory(StringByteLength(sXmlForm) + SizeOf(Character))
        If *xml
          PokeS(*xml, sXmlForm)
          Threads() = CreateThread(@ShowMsg(), *xml)
          If IsThread(Threads())
            Result = 1
          EndIf
        EndIf
        If Result = 0
          If *xml: FreeMemory(*xml) : EndIf
          DeleteElement(Threads())
        EndIf
      EndIf
    EndIf
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure.s GetStringFromToastEventData(pString)
    Protected sResult.s
    If pString And gModuleInit
      sResult = PeekS(pString)
    EndIf
    ProcedureReturn sResult
  EndProcedure
  
  Procedure FreeToastEventData(*EventData.STRUC_ToastEventData)
    Protected i
    If *EventData And gModuleInit
      With *EventData
        If \psAppUserModelId
          SysFreeString_(\psAppUserModelId)
        EndIf
        If \psArguments
          SysFreeString_(\psArguments)
        EndIf
        If \iDataCount > 0
          For i = 0 To \iDataCount - 1
            If \psKeyValue[i]\psKey
              SysFreeString_(\psKeyValue[i]\psKey)
            EndIf
            If \psKeyValue[i]\psValue
              SysFreeString_(\psKeyValue[i]\psValue)
            EndIf
          Next
        EndIf
      EndWith
      FreeMemory(*EventData)
    EndIf
    ProcedureReturn 1
  EndProcedure
  
  Procedure GetToastEventData(iToastEventType, *EventData.Integer = 0)
    Protected Result, BufferSize, *Data = EventData()
    
    If gModuleInit = 0 : ProcedureReturn 0 : EndIf
    
    Select iToastEventType
      Case (gToastEventType + #ToastEventType_Launched), (gToastEventType + #ToastEventType_Activated)
        If *data
          BufferSize = MemorySize(*data)
          If BufferSize > 0
            If *EventData
              *EventData\i = *data
              Result = 1
            EndIf
          EndIf
          If Result = 0
            FreeToastEventData(*data)
          EndIf
        EndIf
        
        If Result = 0 And *EventData
          *EventData\i = 0
        EndIf
        
      Case (gToastEventType + #ToastEventType_Dismissed), (gToastEventType + #ToastEventType_Failed)
        Result = *data
    EndSelect
    ProcedureReturn Result
  EndProcedure
  
  Procedure InitLibraries()
    LibComBase = OpenLibrary(#PB_Any, "combase.dll")
    If LibComBase = 0 : ProcedureReturn 0 : EndIf
    
    RoInitialize = GetFunction(LibComBase, "RoInitialize")
    RoUninitialize = GetFunction(LibComBase, "RoUninitialize")
    WindowsCreateStringReference = GetFunction(LibComBase, "WindowsCreateStringReference")
    RoActivateInstance = GetFunction(LibComBase, "RoActivateInstance")
    RoGetActivationFactory = GetFunction(LibComBase, "RoGetActivationFactory")
    WindowsGetStringRawBuffer = GetFunction(LibComBase, "WindowsGetStringRawBuffer")
    WindowsDeleteString = GetFunction(LibComBase, "WindowsDeleteString")
    
    If RoInitialize = 0 Or
       RoUninitialize = 0 Or
       WindowsCreateStringReference = 0 Or
       RoActivateInstance = 0 Or
       RoGetActivationFactory = 0 Or
       WindowsGetStringRawBuffer = 0 Or
       WindowsDeleteString = 0
      
      ProcedureReturn 0
    EndIf
    ProcedureReturn 1
  EndProcedure
  
  Procedure Initialize(sAppUserModelId.s, sAppCLSID.s, iToastEvent, iToastEventTypeStart)
    Protected Result, CLSID.CLSID
    
    If gModuleInit : ProcedureReturn 0 : EndIf
    
    If sAppUserModelId = "" Or sAppCLSID = "" Or iToastEvent < #PB_Event_FirstCustomValue Or iToastEventTypeStart < #PB_EventType_FirstCustomValue
      ProcedureReturn 0
    EndIf
    
    If OSVersion() < #PB_OS_Windows_10 : ProcedureReturn 0 : EndIf
    If InitInterfaces() = 0 : ProcedureReturn 0 : EndIf
    If InitLibraries() = 0 : ProcedureReturn 0 : EndIf
    
    hEventAllThreadExit = CreateEvent_(0, 1, 0, 0)
    If hEventAllThreadExit = 0 : Goto _Proc_Exit : EndIf
    
    CoInitialize_(0)
    
    If CLSIDFromString_(sAppCLSID, @CLSID) <> #NOERROR
      Goto _Proc_Exit
    EndIf
    
    If CoRegisterClassObject_(CLSID, *ClassFactory, #CLSCTX_LOCAL_SERVER, #REGCLS_MULTIPLEUSE, @gRegister) <> #S_OK
      Goto _Proc_Exit
    EndIf
    
    gAppUserModelId = sAppUserModelId
    gToastEvent = iToastEvent
    gToastEventType = iToastEventTypeStart
    gModuleInit = 1
    Result = 1
    
    _Proc_Exit:
    If Result = 0
      If *NotificationActivationCallback : FreeMemory(*NotificationActivationCallback) : EndIf
      If *ClassFactory : FreeMemory(*ClassFactory) : EndIf
      If LibComBase : CloseLibrary(LibComBase) : EndIf
      If hEventAllThreadExit : CloseHandle_(hEventAllThreadExit) : EndIf
      CoUninitialize_()
    EndIf
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure Finalize()
    If gModuleInit = 0 : ProcedureReturn 0 : EndIf
    
    SetEvent_(hEventAllThreadExit)
    
    ForEach Threads()
      If IsThread(Threads())
        WaitThread(Threads(), 4000)
      EndIf
    Next
    
    If LibComBase : CloseLibrary(LibComBase) : EndIf
    If hEventAllThreadExit : CloseHandle_(hEventAllThreadExit) : EndIf
    If gRegister
      CoRevokeClassObject_(gRegister)
    EndIf
    
    CoUninitialize_()
  EndProcedure
  
  ;- IIDs
  DataSection
    ;UUIDs obtained from <windows.ui.notifications.h>
    ;ABI.Windows.UI.Notifications.IToastNotificationManagerStatics
    ;50ac103f-d235-4598-bbef-98fe4d1a3ad4
    IID_IToastNotificationManagerStatics:
    Data.l $50ac103f
    Data.w $d235, $4598
    Data.b $bb, $ef, $98, $fe, $4d, $1a, $3a, $d4
    
    ;ABI.Windows.Notifications.IToastNotificationFactory
    ;04124b20-82c6-4229-b109-fd9ed4662b53
    IID_IToastNotificationFactory:
    Data.l $04124b20
    Data.w $82c6, $4229
    Data.b $b1, $09, $fd, $9e, $d4, $66, $2b, $53
    
    ;UUIDs obtained from <windows.data.xml.dom.h>
    ;ABI.Windows.Data.Xml.Dom.IXmlDocument
    ;f7f3a506-1e87-42d6-bcfb-b8c809fa5494
    IID_IXmlDocument:
    Data.l $f7f3a506
    Data.w $1e87, $42d6
    Data.b $bc, $fb, $b8, $c8, $09, $fa, $54, $94
    
    ;ABI.Windows.Data.Xml.Dom.IXmlDocumentIO
    ;6cd0e74e-ee65-4489-9ebf-ca43e87ba637
    IID_IXmlDocumentIO:
    Data.l $6cd0e74e
    Data.w $ee65, $4489
    Data.b $9e, $bf, $ca, $43, $e8, $7b, $a6, $37
    
    IID_INotificationActivationCallback:
    ;53E31837-6600-4A81-9395-75CFFE746F94
    Data.l $53E31837
    Data.w $6600, $4A81
    Data.b $93, $95, $75, $CF, $FE, $74, $6F, $94
    
    IID_IUnknown:
    Data.l $00000000
    Data.w $0000, $0000
    Data.b $C0, $00, $00, $00, $00, $00, $00, $46
    
    IID_IToastActivatedEventArgs2:
    ;ab7da512-cc61-568e-81be-304ac31038fa
    Data.l $ab7da512
    Data.w $cc61, $568e
    Data.b $81, $be, $30, $4a, $c3, $10, $38, $fa
    
    IID_IToastActivatedEventArgs:
    ;E3BF92F3-C197-436F-8265-0625824F8DAC
    Data.l $E3BF92F3
    Data.w $C197, $436F
    Data.b $82, $65, $06, $25, $82, $4F, $8D, $AC
    
    IID_IToastFailedEventArgs:
    ;35176862-cfd4-44f8-ad64-f500fd896c3b
    Data.l $35176862
    Data.w $cfd4, $44f8
    Data.b $ad, $64, $f5, $00, $fd, $89, $6c, $3b
    
    IID_IToastDismissedEventArgs:
    ;3f89d935-d9cb-4538-a0f0-ffe7659938f8
    Data.l $3f89d935
    Data.w $d9cb, $4538
    Data.b $a0, $f0, $ff, $e7, $65, $99, $38, $f8
    
    IID___FIIterable_1___FIKeyValuePair_2_HSTRING_IInspectable:
    ;fe2f3d47-5d47-5499-8374-430c7cda0204
    Data.l $fe2f3d47
    Data.w $5d47, $5499
    Data.b $83, $74, $43, $0c, $7c, $da, $02, $04
    
    IID_IPropertyValue:
    ;4bd682dd-7554-40e9-9a9b-82654ede7e62
    Data.l $4bd682dd
    Data.w $7554, $40e9
    Data.b $9a, $9b, $82, $65, $4e, $de, $7e, $62
    
    IID_DesktopToastActivatedEventHandler:
    ;ab54de2d-97d9-5528-b6ad-105afe156530
    Data.l $ab54de2d
    Data.w $97d9, $5528
    Data.b $b6, $ad, $10, $5a, $fe, $15, $65, $30
    
    IID_DesktopToastDismissedEventHandler:
    ;61c2402f-0ed0-5a18-ab69-59f4aa99a368
    Data.l $61c2402f
    Data.w $0ed0, $5a18
    Data.b $ab, $69, $59, $f4, $aa, $99, $a3, $68
    
    IID_DesktopToastFailedEventHandler:
    ;95e3e803-c969-5e3a-9753-ea2ad22a9a33
    Data.l $95e3e803
    Data.w $c969, $5e3a
    Data.b $97, $53, $ea, $2a, $d2, $2a, $9a, $33
    
    IID_IClassFactory:
    Data.l $00000001
    Data.w $0000, $0000
    Data.b $C0, $0, $0, $0, $0, $0, $0, $46
  EndDataSection
EndModule
Last edited by breeze4me on Thu Jun 02, 2022 11:38 pm, edited 4 times in total.
breeze4me
Enthusiast
Enthusiast
Posts: 511
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Windows Toastnotification without PowerShell

Post by breeze4me »

Code: Select all

;---------------------------------------
;- Test code.
;---------------------------------------
EnableExplicit

XIncludeFile "ToastNotification.pbi"

;- Define unique App IDs.

;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.
#AppUserModelId = "MyCompany.TestApp.TestSubProduct"

; Unique CLSID of your app.
#AppCLSID = "{394391cf-093b-4cb1-895b-5505ee760cc2}"

;Set a name to be used as the shortcut file(.lnk) name and the toast notification title.
#App_Name = "My Toast Notification Test App"


;- Define event constants.
Enumeration #PB_Event_FirstCustomValue
  ;
  ; your custom events here.
  ;
  
  #App_Event_ToastNotification
EndEnumeration

Enumeration #PB_EventType_FirstCustomValue
  ;
  ; your custom event types here.
  ;
  
  ;The order is important.
  #App_EventType_ToastNotification_Launched
  #App_EventType_ToastNotification_Activated
  #App_EventType_ToastNotification_Dismissed
  #App_EventType_ToastNotification_Failed
EndEnumeration

#SHCNE_ASSOCCHANGED = $8000000
#SHCNF_IDLIST = 0

#SHCNE_ALLEVENTS = $7FFFFFFF
#SHCNF_FLUSH = $1000
#SHREGSET_FORCE_HKCU = 2

; https://www.purebasic.fr/english/viewtopic.php?p=489813&#p489813
Structure PROPVARIANT_CLIPDATA
  cbSize.l
  ulClipFmt.l
  *pClipData.BYTE
EndStructure

Structure PROPVARIANT_BSTRBLOB
  cbSize.l
  *pData.BYTE
EndStructure

Structure PROPVARIANT_BLOB
  cbSize.l
  *pBlobData.BYTE
EndStructure

Structure PROPVARIANT_VERSIONEDSTREAM
  guidVersion.GUID
  *pStream.IStream
EndStructure

Structure PROPVARIANT_CAC
  cElems.l
  *pElems.BYTE
EndStructure

Structure PROPVARIANT_CAUB
  cElems.l
  *pElems.BYTE
EndStructure

Structure PROPVARIANT_CAI
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CAUI
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CAL
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAUL
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAFLT
  cElems.l
  *pElems.FLOAT
EndStructure

Structure PROPVARIANT_CADBL
  cElems.l
  *pElems.DOUBLE
EndStructure

Structure PROPVARIANT_CACY
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CADATE
  cElems.l
  *pElems.DOUBLE
EndStructure

Structure PROPVARIANT_CABSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CABSTRBLOB
  cElems.l
  *pElems.PROPVARIANT_BSTRBLOB
EndStructure

Structure PROPVARIANT_CABOOL
  cElems.l
  *pElems.WORD
EndStructure

Structure PROPVARIANT_CASCODE
  cElems.l
  *pElems.LONG
EndStructure

Structure PROPVARIANT_CAPROPVARIANT
  cElems.l
  *pElems.PROPVARIANT
EndStructure

Structure PROPVARIANT_CAH
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CAUH
  cElems.l
  *pElems.QUAD
EndStructure

Structure PROPVARIANT_CALPSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CALPWSTR
  cElems.l
  *pElems.INTEGER
EndStructure

Structure PROPVARIANT_CAFILETIME
  cElems.l
  *pElems.FILETIME
EndStructure

Structure PROPVARIANT_CACLIPDATA
  cElems.l
  *pElems.PROPVARIANT_CLIPDATA
EndStructure

Structure PROPVARIANT_CACLSID
  cElems.l
  *pElems.CLSID
EndStructure

; https://docs.microsoft.com/en-us/windows/win32/api/propidl/ns-propidl-propvariant
Structure PROPVARIANT Align #PB_Structure_AlignC   ;x64 24 bytes // x86 16 bytes.
  vt.w
  wReserved1.w
  wReserved2.w
  wReserved3.w
  StructureUnion
    cVal.b
    bVal.b
    iVal.w
    uiVal.w
    lVal.l
    ulVal.l
    intVal.l
    uintVal.l
    hVal.q
    uhVal.q
    fltVal.f
    dblVal.d
    boolVal.w
    scode.l
    cyVal.q
    date.d
    filetime.FILETIME
    *puuid.CLSID
    *pclipdata.PROPVARIANT_CLIPDATA
    bstrVal.i
    bstrblobVal.PROPVARIANT_BSTRBLOB
    blob.PROPVARIANT_BLOB
    *pszVal
    *pwszVal
    *punkVal.IUnknown
    *pdispVal.IDispatch
    *pStream.IStream
    *pStorage.IStorage
    *pVersionedStream.PROPVARIANT_VERSIONEDSTREAM
    *parray.SAFEARRAY
    cac.PROPVARIANT_CAC
    caub.PROPVARIANT_CAUB
    cai.PROPVARIANT_CAI
    caui.PROPVARIANT_CAUI
    cal.PROPVARIANT_CAL
    caul.PROPVARIANT_CAUL
    cah.PROPVARIANT_CAH
    cauh.PROPVARIANT_CAUH
    caflt.PROPVARIANT_CAFLT
    cadbl.PROPVARIANT_CADBL
    cabool.PROPVARIANT_CABOOL
    cascode.PROPVARIANT_CASCODE
    cacy.PROPVARIANT_CACY
    cadate.PROPVARIANT_CADATE
    cafiletime.PROPVARIANT_CAFILETIME
    cauuid.PROPVARIANT_CACLSID
    caclipdata.PROPVARIANT_CACLIPDATA
    cabstr.PROPVARIANT_CABSTR
    cabstrblob.PROPVARIANT_CABSTRBLOB
    calpstr.PROPVARIANT_CALPSTR
    calpwstr.PROPVARIANT_CALPWSTR
    capropvar.PROPVARIANT_CAPROPVARIANT
    *pcVal.BYTE
    *pbVal.BYTE
    *piVal.WORD
    *puiVal.WORD
    *plVal.LONG
    *pulVal.LONG
    *pintVal.LONG
    *puintVal.LONG
    *pfltVal.FLOAT
    *pdblVal.DOUBLE
    *pboolVal.WORD
    *pdecVal.VARIANT_DECIMAL
    *pscode.LONG
    *pcyVal.QUAD
    *pdate.DOUBLE
    *pbstrVal.INTEGER
    *ppunkVal.INTEGER
    *ppdispVal.INTEGER
    *pparray.INTEGER
    *pvarVal.PROPVARIANT
  EndStructureUnion
EndStructure

;um\propsys.h
Interface IPropertyStore Extends IUnknown
  GetCount(*cProps)
  GetAt(iProp, *pkey)
  GetValue(key, *pv)
  SetValue(key, propvar)
  Commit()
EndInterface

;um\objidl.h
Interface IPersistFileW Extends IUnknown
  GetClassID(*pClassID)
  IsDirty()
  Load(pszFileName.s, dwMode)
  Save(pszFileName.s, fRemember)
  SaveCompleted(pszFileName.s)
  GetCurFile(*ppszFileName)
EndInterface

;um\ShObjIdl_core.h
Interface IShellLink Extends IUnknown   ;IShellLinkW
  GetPath(*pszFile, cch, *pfd, fFlags)
  GetIDList(*ppidl)
  SetIDList(pidl)
  GetDescription(*pszName, cch)
  SetDescription(pszName.s)
  GetWorkingDirectory(*pszDir, cch)
  SetWorkingDirectory(pszDir.s)
  GetArguments(*pszArgs, cch)
  SetArguments(pszArgs.s)
  GetHotkey(*pwHotkey)
  SetHotkey(wHotkey)
  GetShowCmd(*piShowCmd)
  SetShowCmd(iShowCmd)
  GetIconLocation(*pszIconPath, cch, *piIcon)
  SetIconLocation(pszIconPath.s, iIcon)
  SetRelativePath(pszPathRel.s, dwReserved)
  Resolve(hwnd, fFlags)
  SetPath(pszFile.s)
EndInterface

Macro mDebugOutput(String)
  AddGadgetItem(2, -1, FormatDate("%hh:%ii:%ss", Date()) + " |    " + String)
EndMacro

Procedure InitPropVariantFromString(String.s, *ppropvar.PROPVARIANT)
  Protected ByteCount, Result
  
  If *ppropvar = 0 : ProcedureReturn #E_INVALIDARG : EndIf
  If String = ""
    Result = #E_INVALIDARG
  EndIf
  
  ByteCount = StringByteLength(String) + SizeOf(Character)
  
  *ppropvar\pwszVal = CoTaskMemAlloc_(ByteCount)
  If *ppropvar\pwszVal
    CopyMemory(@String, *ppropvar\pwszVal, ByteCount)
    *ppropvar\vt = #VT_LPWSTR
    Result = #S_OK
  Else
    Result = #E_OUTOFMEMORY
  EndIf
  
  If Result <> #S_OK
    FillMemory(*ppropvar, SizeOf(PROPVARIANT), 0)
  EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure InstallShortcut(sShortcutFile.s)
  Protected sExePath.s = #DOUBLEQUOTE$ + ProgramFilename() + #DOUBLEQUOTE$
  Protected *ShellLink.IShellLink
  Protected *PropertyStore.IPropertyStore
  Protected *PersistFile.IPersistFileW
  Protected AppIdPropVar.PROPVARIANT
  Protected AppCLSID.CLSID, Result
  
  If sShortcutFile = "" : ProcedureReturn #E_INVALIDARG : EndIf
  
  Result = CoCreateInstance_(?CLSID_ShellLink, 0, #CLSCTX_INPROC_SERVER, ?IID_IShellLinkW, @*ShellLink)
  If Result <> #S_OK : ProcedureReturn Result : EndIf
  If *ShellLink = 0 : ProcedureReturn #E_NOINTERFACE : EndIf
  
  Result = *ShellLink\SetPath(sExePath)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
  ;   *ShellLink\SetArguments(Arguments$)
  ;   *ShellLink\SetWorkingDirectory(WorkingDirectory$)
  ;   *ShellLink\SetDescription(Description$)
  ;   *ShellLink\SetShowCmd(ShowCommand)
  ;   *ShellLink\SetHotkey(HotKey)
  ;   *ShellLink\SetIconLocation(IconFile$, IconIndex)
  
  Result = *ShellLink\QueryInterface(?IID_IPropertyStore, @*PropertyStore)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *PropertyStore = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = InitPropVariantFromString(#AppUserModelId, @AppIdPropVar)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
  Result = *PropertyStore\SetValue(?PKEY_AppUserModel_ID, AppIdPropVar)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
  If AppIdPropVar\pwszVal
    AppIdPropVar\vt = 0
    CoTaskMemFree_(AppIdPropVar\pwszVal)
  EndIf
  
  AppIdPropVar\vt = #VT_CLSID
  CLSIDFromString_(#AppCLSID, AppCLSID)
  AppIdPropVar\puuid = @AppCLSID
  
  Result = *PropertyStore\SetValue(?PKEY_AppUserModel_ToastActivatorCLSID, AppIdPropVar)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  
  Result = *ShellLink\QueryInterface(?IID_IPersistFileW, @*PersistFile)
  If Result <> #S_OK : Goto _Proc_Exit : EndIf
  If *PersistFile = 0 : Result = #E_NOINTERFACE : Goto _Proc_Exit : EndIf
  
  Result = *PersistFile\Save(sShortcutFile, 1)
  
  SHChangeNotify_(#SHCNE_ASSOCCHANGED, #SHCNF_IDLIST, 0, 0)
  
  _Proc_Exit:
  If *PersistFile : *PersistFile\Release() : EndIf
  If *PropertyStore : *PropertyStore\Release() : EndIf
  If *ShellLink : *ShellLink\Release() : EndIf
  
  ProcedureReturn Result
EndProcedure

;- Define EventData. (STRUC_ToastEventData)
Define *EventData.ToastNotification::STRUC_ToastEventData

Define Result, i, MaxIndex
Define sAppUserModelId.s, sArguments.s, sKey.s, sValue.s
Define e, et, xml.s

;- Initialize the module first.
ToastNotification::Initialize(#AppUserModelId, #AppCLSID, #App_Event_ToastNotification, #App_EventType_ToastNotification_Launched)

;Create a Shortcut link file for testing.
Define sLinkFile.s = GetEnvironmentVariable("APPDATA")
If sLinkFile
  sLinkFile + "\Microsoft\Windows\Start Menu\Programs\" + #App_Name + ".lnk"
  If FileSize(sLinkFile) = -1
    If InstallShortcut(sLinkFile) <> #S_OK
      Goto Quit
    EndIf
  EndIf
EndIf

;SHChangeNotify_(#SHCNE_ASSOCCHANGED, #SHCNF_FLUSH, 0, 0)
SHChangeNotify_(#SHCNE_ALLEVENTS, #SHCNF_FLUSH, 0, 0)
SendMessageTimeout_(#HWND_BROADCAST, #WM_SETTINGCHANGE, 0, 0, #SMTO_ABORTIFHUNG, 5000, 0)
MessageRequester("", "Wait a few seconds for the setting to be reflected in the system.")

xml = "<toast launch='LaunchArgs' duration='short'>" +
      "    <visual>" +
      "        <binding template='ToastGeneric'>" +
      "            <text>Test Notification</text>" +
      "            <text>blah blah blah...</text>" +
      "        </binding>" +
      "    </visual>" +
      "    <actions>" +
      "        <input id='MyMessageInput' type='text' title='Message' placeHolderContent='Enter a message' defaultInput='Toast Notification Test' />" +
      "        <action activationType='foreground' arguments='QuickReply' content='Submit' />" +
      "        <action activationType='foreground' arguments='Cancel' content='Cancel' />" +
      "    </actions>" +
      "</toast>"

If OpenWindow(0, 0, 0, 600, 650, "Toast Test", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  ButtonGadget(0, 10, 2, 200, 25, "Show message")
  ButtonGadget(1, 390, 2, 200, 25, "Clear")
  EditorGadget(2, 0, 30, 600, 620, #PB_Editor_ReadOnly)
  
  Repeat
    e = WaitWindowEvent()
    
    If e = #PB_Event_Gadget
      Select EventGadget()
        Case 0
          ToastNotification::SendToastNotification(xml)
        Case 1
          ClearGadgetItems(2)
      EndSelect
    EndIf
    
    If e = #App_Event_ToastNotification
      et = EventType()
      
      Select et
        Case #App_EventType_ToastNotification_Launched
          mDebugOutput("----------------------------------------------------------")
          mDebugOutput("*** Toast notification event: Launched (NotificationActivationCallback)")
          
          ;- Get the toast event data.
          Result = ToastNotification::GetToastEventData(et, @*EventData)
          If Result And *EventData
            With *EventData
              sAppUserModelId = ""
              If \psAppUserModelId
                sAppUserModelId = ToastNotification::GetStringFromToastEventData(\psAppUserModelId)
              EndIf
              mDebugOutput("AppUserModelId: " + sAppUserModelId)
              
              sArguments = ""
              If \psArguments
                sArguments = ToastNotification::GetStringFromToastEventData(\psArguments)
              EndIf
              mDebugOutput("Arguments: " + sArguments)
              
              MaxIndex = \iDataCount - 1
              
              If MaxIndex >= 0
                For i = 0 To MaxIndex
                  sKey = ""
                  If \psKeyValue[i]\psKey
                    sKey = ToastNotification::GetStringFromToastEventData(\psKeyValue[i]\psKey)
                  EndIf
                  mDebugOutput("Key" + Str(i) + ": " + sKey)
                  
                  sValue = ""
                  If \psKeyValue[i]\psValue
                    sValue = ToastNotification::GetStringFromToastEventData(\psKeyValue[i]\psValue)
                  EndIf
                  mDebugOutput("Value" + Str(i) + ": " + sValue)
                Next
              EndIf
            EndWith
            
            ;- Freeing the memory buffer is essential.
            ToastNotification::FreeToastEventData(*EventData)
          EndIf
          
          mDebugOutput("----------------------------------------------------------")
          PostMessage_(GadgetID(2), #EM_SCROLL, #SB_PAGEDOWN, 0)
          
        Case #App_EventType_ToastNotification_Activated
          mDebugOutput("----------------------------------------------------------")
          mDebugOutput("*** Toast notification event: Activated")
          
          ;- Get the toast event data.
          Result = ToastNotification::GetToastEventData(et, @*EventData)
          If Result And *EventData
            With *EventData
              sArguments = ""
              If \psArguments
                sArguments = ToastNotification::GetStringFromToastEventData(\psArguments)
              EndIf
              mDebugOutput("Arguments: " + sArguments)
              
              sKey = ""
              If \psKeyValue[0]\psKey
                sKey = ToastNotification::GetStringFromToastEventData(\psKeyValue[0]\psKey)
              EndIf
              mDebugOutput("Key: " + sKey)
              
              sValue = ""
              If \psKeyValue[0]\psValue
                sValue = ToastNotification::GetStringFromToastEventData(\psKeyValue[0]\psValue)
              EndIf
              mDebugOutput("Value: " + sValue)
            EndWith
            
            ;- Freeing the memory buffer is essential.
            ToastNotification::FreeToastEventData(*EventData)
          EndIf
          
          mDebugOutput("----------------------------------------------------------")
          PostMessage_(GadgetID(2), #EM_SCROLL, #SB_PAGEDOWN, 0)
          
        Case #App_EventType_ToastNotification_Dismissed
          mDebugOutput("----------------------------------------------------------")
          mDebugOutput("*** Toast notification event: Dismissed")
          
          Result = ToastNotification::GetToastEventData(et)
          Select Result
            Case ToastNotification::#ToastDismissalReason_UserCanceled
              mDebugOutput("Reason: UserCanceled. The user dismissed the toast.")
              
            Case ToastNotification::#ToastDismissalReason_ApplicationHidden
              mDebugOutput("Reason: ApplicationHidden. The application hid the toast using ToastNotifier\Hide().")
              
            Case ToastNotification::#ToastDismissalReason_TimedOut
              mDebugOutput("Reason: TimedOut. The toast has expired.")
              
            Default
              mDebugOutput("Unknown Reason code: " + Str(Result))
          EndSelect
          
          mDebugOutput("----------------------------------------------------------")
          PostMessage_(GadgetID(2), #EM_SCROLL, #SB_PAGEDOWN, 0)
          
        Case #App_EventType_ToastNotification_Failed
          mDebugOutput("----------------------------------------------------------")
          mDebugOutput("*** Toast notification event: Failed")
          
          Result = ToastNotification::GetToastEventData(et)
          
          mDebugOutput("HRESULT code: " + Hex(Result, #PB_Long))
          mDebugOutput("----------------------------------------------------------")
          PostMessage_(GadgetID(2), #EM_SCROLL, #SB_PAGEDOWN, 0)
      EndSelect
    EndIf
    
  Until e = #PB_Event_CloseWindow
  
  CloseWindow(0)
EndIf

Quit:
;Delete the test .lnk file.
If FileSize(sLinkFile) >= 0
  DeleteFile(sLinkFile)
EndIf

;- Release the resources of the module.
ToastNotification::Finalize()


DataSection
  CLSID_ShellLink:
  ;00021401-0000-0000-C000-000000000046
  Data.l $00021401
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46
  
  IID_IShellLinkW:
  ;000214F9-0000-0000-C000-000000000046
  Data.l $000214F9
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46
  
  IID_IPersistFileW:
  ;0000010b-0000-0000-C000-000000000046 
  Data.l $0000010b 
  Data.w $0000, $0000
  Data.b $C0, $00, $00, $00, $00, $00, $00, $46 
  
  IID_IPropertyStore:
  ;886d8eeb-8cf2-4446-8d02-cdba1dbdcf99
  Data.l $886d8eeb
  Data.w $8cf2, $4446
  Data.b $8d, $02, $cd, $ba, $1d, $bd, $cf, $99
  
  ;PROPERTYKEY structures
  ;propkey.h
  PKEY_AppUserModel_ID:
  ; { { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 5 }
  Data.l $9F4C2855
  Data.w $9F79, $4B39
  Data.b $A8, $D0, $E1, $D4, $2D, $E1, $D5, $F3
  Data.l 5
  
  PKEY_AppUserModel_ToastActivatorCLSID:
  ; { { 0x9F4C2855, 0x9F79, 0x4B39, 0xA8, 0xD0, 0xE1, 0xD4, 0x2D, 0xE1, 0xD5, 0xF3 }, 26 }
  Data.l $9F4C2855
  Data.w $9F79, $4B39
  Data.b $A8, $D0, $E1, $D4, $2D, $E1, $D5, $F3
  Data.l 26
EndDataSection
fryquez
Enthusiast
Enthusiast
Posts: 362
Joined: Mon Dec 21, 2015 8:12 pm

Re: Windows Toastnotification without PowerShell

Post by fryquez »

It should be possible without shortcut: https://github.com/WindowsNotifications/desktop-toasts
breeze4me
Enthusiast
Enthusiast
Posts: 511
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Windows Toastnotification without PowerShell

Post by breeze4me »

fryquez wrote: Thu Jun 02, 2022 7:11 pm It should be possible without shortcut: https://github.com/WindowsNotifications/desktop-toasts
It seems that a kind of installer or packaging app called "Desktop Bridge" is needed. It seems to convert desktop apps into UWP apps.
I've already tried to register only in the registry, but it didn't work properly.
As Microsoft mentioned, desktop apps seem to need shortcut links without that conversion.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Windows Toastnotification without PowerShell

Post by infratec »

What a mess.

In your posted link
https://docs.microsoft.com/en-us/previo ... =win.10%29
It is written:
Desktop app notifications will not appear on the lock screen
Unfortunatelly that's exacly what I need.
Because I wrote a SIP phone in PB and I want to see an incoming call also when the lock screen is active. :cry:

In WIn11 the toast appears on the lock screen, but not in Win10.
Again: what a mess.
infratec
Always Here
Always Here
Posts: 6817
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Windows Toastnotification without PowerShell

Post by infratec »

I found the solution for VoIP on lock screen:

Code: Select all

<toast scenario='incomingCall'
With this 'scenario' it is shown according to my system settings.
User avatar
jacdelad
Addict
Addict
Posts: 1431
Joined: Wed Feb 03, 2021 12:46 pm
Location: Planet Riesa
Contact:

Re: Windows Toastnotification without PowerShell

Post by jacdelad »

@breeze4me:
I "played" a bit with your examples and was able to integrate it into one of my programs. Unfortunately the shown icon is extremely small. Is this still under development?

Also I searched for some kind of documentation, but couldn't find much, especially for the xml-syntax/-keywords. .net seems to use an own implementation. Can anybody help me with a link or something to explore all the design features possible (which are a lot)?
PureBasic 6.04/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/130TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
breeze4me
Enthusiast
Enthusiast
Posts: 511
Joined: Thu Mar 09, 2006 9:24 am
Location: S. Kor

Re: Windows Toastnotification without PowerShell

Post by breeze4me »

jacdelad wrote: Fri Nov 11, 2022 2:03 am @breeze4me:
I "played" a bit with your examples and was able to integrate it into one of my programs. Unfortunately the shown icon is extremely small. Is this still under development?

Also I searched for some kind of documentation, but couldn't find much, especially for the xml-syntax/-keywords. .net seems to use an own implementation. Can anybody help me with a link or something to explore all the design features possible (which are a lot)?
I don't know what kind of icon it is, but unfortunately, some features of toast notification cannot be used properly in desktop apps. At least in Windows 10.

Toast schema
Toast content schema
The toast template catalog (Windows Runtime apps)

Notification Templates
User avatar
jacdelad
Addict
Addict
Posts: 1431
Joined: Wed Feb 03, 2021 12:46 pm
Location: Planet Riesa
Contact:

Re: Windows Toastnotification without PowerShell

Post by jacdelad »

Aye thanks. The links seem very useful. Mind if I use your module, modify it a bit for easier use, add easy access to some predefined templates to be used with PureBasic, and post it here? At least if I can produce something that works.

Update: Great links indeed. I found out, that the mini-icon is normal. Also I found out how to use a big image. I will experiment a bit more and post the results; it is working with templates, so these ones are easily to adapt to PureBasic.
PureBasic 6.04/XProfan X4a/Embarcadero RAD Studio 11/Perl 5.2/Python 3.10
Windows 11/Ryzen 5800X/32GB RAM/Radeon 7770 OC/3TB SSD/11TB HDD
Synology DS1821+/36GB RAM/130TB
Synology DS920+/20GB RAM/54TB
Synology DS916+ii/8GB RAM/12TB
Post Reply