Can AHK detect when Action Center notifications occur?

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Can AHK detect when Action Center notifications occur?

Post by malcev » 27 May 2020, 15:59

If You dont need text of notification You can do like this:

Code: Select all

AppName := "TextNow - Unlimited Text + Calls"

setbatchlines -1
CreateClass("Windows.UI.Notifications.Management.UserNotificationListener", IUserNotificationListenerStatics := "{FF6123CF-4386-4AA3-B73D-B804E5B63B23}", UserNotificationListenerStatics)
DllCall(NumGet(NumGet(UserNotificationListenerStatics+0)+6*A_PtrSize), "ptr", UserNotificationListenerStatics, "ptr*", listener)   ; get_Current
DllCall(NumGet(NumGet(listener+0)+6*A_PtrSize), "ptr", listener, "int*", accessStatus)   ; RequestAccessAsync
WaitForAsync(accessStatus)
if (accessStatus != 1)
{
   msgbox AccessStatus Denied
   exitapp
}
loop
{
   DllCall(NumGet(NumGet(listener+0)+10*A_PtrSize), "ptr", listener, "int", 1, "ptr*", UserNotificationReadOnlyList)   ; GetNotificationsAsync
   WaitForAsync(UserNotificationReadOnlyList)
   DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+7*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int*", count)   ; count
   loop % count
   {
      DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+6*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int", A_Index-1, "ptr*", UserNotification)   ; get_Item
      DllCall(NumGet(NumGet(UserNotification+0)+8*A_PtrSize), "ptr", UserNotification, "uint*", id)   ; get_Id
      if !InStr(idList, "|" id "|")
      {
         idList .= "|" id "|"
         if !DllCall(NumGet(NumGet(UserNotification+0)+7*A_PtrSize), "ptr", UserNotification, "ptr*", AppInfo)   ; get_AppInfo
         {
            DllCall(NumGet(NumGet(AppInfo+0)+8*A_PtrSize), "ptr", AppInfo, "ptr*", AppDisplayInfo)   ; get_DisplayInfo
            DllCall(NumGet(NumGet(AppDisplayInfo+0)+6*A_PtrSize), "ptr", AppDisplayInfo, "ptr*", hText)   ; get_DisplayName
            buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
            text := StrGet(buffer, "UTF-16")
            DeleteHString(hText)
            ObjRelease(AppDisplayInfo)
            ObjRelease(AppInfo)
            if (text = AppName)
            {
               ; here your code

            }
         }
      }
      ObjRelease(UserNotification)
   }
   ObjRelease(UserNotificationReadOnlyList)
   DllCall("psapi.dll\EmptyWorkingSet", "ptr", -1)
   sleep 50
}


CreateClass(string, interface, ByRef Class)
{
   CreateHString(string, hString)
   VarSetCapacity(GUID, 16)
   DllCall("ole32\CLSIDFromString", "wstr", interface, "ptr", &GUID)
   result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", &GUID, "ptr*", Class, "uint")
   if (result != 0)
   {
      if (result = 0x80004002)
         msgbox No such interface supported
      else if (result = 0x80040154)
         msgbox Class not registered
      else
         msgbox error: %result%
      ExitApp
   }
   DeleteHString(hString)
}

CreateHString(string, ByRef hString)
{
   DllCall("Combase.dll\WindowsCreateString", "wstr", string, "uint", StrLen(string), "ptr*", hString)
}

DeleteHString(hString)
{
   DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
}

WaitForAsync(ByRef Object)
{
   AsyncInfo := ComObjQuery(Object, IAsyncInfo := "{00000036-0000-0000-C000-000000000046}")
   loop
   {
      DllCall(NumGet(NumGet(AsyncInfo+0)+7*A_PtrSize), "ptr", AsyncInfo, "uint*", status)   ; IAsyncInfo.Status
      if (status != 0)
      {
         if (status != 1)
         {
            DllCall(NumGet(NumGet(AsyncInfo+0)+8*A_PtrSize), "ptr", AsyncInfo, "uint*", ErrorCode)   ; IAsyncInfo.ErrorCode
            msgbox AsyncInfo status error: %ErrorCode%
            ExitApp
         }
         ObjRelease(AsyncInfo)
         break
      }
      sleep 10
   }
   DllCall(NumGet(NumGet(Object+0)+8*A_PtrSize), "ptr", Object, "ptr*", ObjectResult)   ; GetResults
   ObjRelease(Object)
   Object := ObjectResult
}
Insert Your code under

Code: Select all

; here your code
How to activate Your application read manual.
PS Changed code in 6th code (forgot to release 1 object).

MostHated
Posts: 1
Joined: 29 May 2020, 15:31

Re: Can AHK detect when Action Center notifications occur?

Post by MostHated » 29 May 2020, 19:30

malcev wrote:
27 May 2020, 15:59
If You dont need text of notification You can do like this:

**EDIT** --- Nevermind, I got it all figured out!-------------------------------------------------------


Hey there,
Thanks a ton for sharing this code. I have been looking for something to be able to read and react to notifications for quite some time. I am trying to figure out exactly how I go about modifying this though, is this using AHK to call what I am guessing is maybe the Universal Windows framework (I don't recall the actual name), instead of going about it other ways I had found earlier, such as using an Acc.ahk library that I came across?

The one I was looking at had functionality such as the following:

Code: Select all

HookProc( hWinEventHook, Event, hWnd, idObject, idChild, dwEventThread, dwmsEventTime ) {
	Global sender_o, message_o
	
	app_name:=""
	sender:=""
	message:=""
	try{	
		oAcc := Acc_Get("Object", "4.1.3", 0, "ahk_id " hWnd)
		app_name:= oAcc.accName(0)
		oAcc := ""
		oAcc := Acc_Get("Object", "4.1.1", 0, "ahk_id " hWnd)
		sender:= oAcc.accName(0)
		oAcc := ""
		oAcc := Acc_Get("Object", "4.1.2", 0, "ahk_id " hWnd)
		message:= oAcc.accName(0)
		oAcc := ""
	}	
	
	if (app_name == "Discord"){
		message:= RegExReplace(message,"ims):"," ")
		message:= RegExReplace(message,"ims)\.",",")
		message:= RegExReplace(message,"ims)`r"," ")
		message:= RegExReplace(message,"ims)`n"," ")
		message:= RegExReplace(message,"ims)\s+"," ")
		message:= RegExReplace(message,"ims)\/\/"," ")
		
		if (message != message_o){
			sender_o := sender
			message_o:= message
				
			ACTIONS(sender, message)
		}
		
		return
	}
			
}
It was easy enough to work with. I am wanting to use the code you shared, as it seems to work great in the test that I did. I was just hoping you might be able to fill in a few pieces of information for me?

For the section below:

Code: Select all

        
         DllCall(NumGet(NumGet(AppInfo+0)+8*A_PtrSize), "ptr", AppInfo, "ptr*", AppDisplayInfo)   ; get_DisplayInfo          		; ---------- Usable data?
         DllCall(NumGet(NumGet(AppDisplayInfo+0)+6*A_PtrSize), "ptr", [b][u]AppDisplayInfo[/u][/b], "ptr*", [b][u]hText[/u][/b])    ; ---------- Usable data?
         buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")							; ----------  ^ need to make an additional buffer for these to get it?
         text := StrGet(buffer, "UTF-16")
         DeleteHString(hText)
         ObjRelease(AppDisplayInfo)
         ObjRelease(AppInfo)
         msgbox % text
   
As seen in the comments I made above, is there additional data that can be used out of those, and would another buffer/StrGet need to be used for each one? ex.

Code: Select all

	buffer1 := DllCall(NumGet(NumGet(AppInfo+0)+8*A_PtrSize)....
	buffer2 := DllCall(NumGet(NumGet(AppDisplayInfo+0)+6*A_PtrSize)....
	buffer3 := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")....
	text1 := StrGet(buffer1, "UTF-16")	
	text2 := StrGet(buffer2, "UTF-16")	
	text3 := StrGet(buffer3, "UTF-16")	
Then, for the current text item, how might one go about splitting the data? I am not familiar with the DllCall or what it is doing exactly here. It seems that the current text variable is being fed by the WindowsGetStringRawBuffer function, and pulling that from hText, but the line above that, is hText coming from AppDisplayInfo, which is then coming from AppInfo? So then does AppInfo contain all of the subsequent data and in your example, that data is just being discarded?

If I wanted to separate the data, (such as the app name, the title/message, or whatever the proper name is for them in this instance) into separate variables instead of a single text string, what would be the best approach to use in this particular situation?

Thanks!
-MH

Kilk1
Posts: 19
Joined: 18 May 2020, 14:45

Re: Can AHK detect when Action Center notifications occur?

Post by Kilk1 » 01 Jun 2020, 11:55

@malcev Thanks for everything you've done to help! Now I finally can rely on the TextNow app!

User avatar
hoppfrosch
Posts: 443
Joined: 07 Oct 2013, 04:05
Location: Rhine-Maine-Area, Hesse, Germany
Contact:

Re: Can AHK detect when Action Center notifications occur?

Post by hoppfrosch » 14 Jul 2021, 02:17

Just for completion:
In order to allow your apps access notifications, you have to set the correct permissions: Let Windows apps access notifications

Miss_Analyst_BPPD
Posts: 2
Joined: 14 Sep 2021, 14:50

Re: Can AHK detect when Action Center notifications occur?

Post by Miss_Analyst_BPPD » 30 Dec 2021, 16:51

@Kilk1 Would you be willing to post the funtional code you used to accomplish this? I'm working on something similar at the moment.

Kilk1
Posts: 19
Joined: 18 May 2020, 14:45

Re: Can AHK detect when Action Center notifications occur?

Post by Kilk1 » 30 Dec 2021, 17:23

Miss_Analyst_BPPD wrote:
30 Dec 2021, 16:51
@Kilk1 Would you be willing to post the funtional code you used to accomplish this? I'm working on something similar at the moment.

Sure thing! Is this what you're looking for?

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.

AppName := "TextNow - Unlimited Text + Calls"

setbatchlines -1
CreateClass("Windows.UI.Notifications.Management.UserNotificationListener", IUserNotificationListenerStatics := "{FF6123CF-4386-4AA3-B73D-B804E5B63B23}", UserNotificationListenerStatics)
DllCall(NumGet(NumGet(UserNotificationListenerStatics+0)+6*A_PtrSize), "ptr", UserNotificationListenerStatics, "ptr*", listener)   ; get_Current
DllCall(NumGet(NumGet(listener+0)+6*A_PtrSize), "ptr", listener, "int*", accessStatus)   ; RequestAccessAsync
WaitForAsync(accessStatus)
if (accessStatus != 1)
{
   msgbox AccessStatus Denied
   exitapp
}
loop
{
   DllCall(NumGet(NumGet(listener+0)+10*A_PtrSize), "ptr", listener, "int", 1, "ptr*", UserNotificationReadOnlyList)   ; GetNotificationsAsync
   WaitForAsync(UserNotificationReadOnlyList)
   DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+7*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int*", count)   ; count
   loop % count
   {
      DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+6*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int", A_Index-1, "ptr*", UserNotification)   ; get_Item
      DllCall(NumGet(NumGet(UserNotification+0)+8*A_PtrSize), "ptr", UserNotification, "uint*", id)   ; get_Id
      if InStr(idList, "|" id "|")
      {
         ObjRelease(UserNotification)
         Continue
      }
      idList .= "|" id "|"
      if !DllCall(NumGet(NumGet(UserNotification+0)+7*A_PtrSize), "ptr", UserNotification, "ptr*", AppInfo)   ; get_AppInfo
      {
         DllCall(NumGet(NumGet(AppInfo+0)+8*A_PtrSize), "ptr", AppInfo, "ptr*", AppDisplayInfo)   ; get_DisplayInfo
         DllCall(NumGet(NumGet(AppDisplayInfo+0)+6*A_PtrSize), "ptr", AppDisplayInfo, "ptr*", hText)   ; get_DisplayName
         buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
         text := StrGet(buffer, "UTF-16")
         DeleteHString(hText)
         ObjRelease(AppDisplayInfo)
         ObjRelease(AppInfo)
         if (text != AppName)
            Continue
      }
      else
         Continue
      DllCall(NumGet(NumGet(UserNotification+0)+6*A_PtrSize), "ptr", UserNotification, "ptr*", Notification)   ; get_Notification
      DllCall(NumGet(NumGet(Notification+0)+8*A_PtrSize), "ptr", Notification, "ptr*", NotificationVisual)   ; get_Visual
      DllCall(NumGet(NumGet(NotificationVisual+0)+8*A_PtrSize), "ptr", NotificationVisual, "ptr*", NotificationBindingList)   ; get_Bindings
      DllCall(NumGet(NumGet(NotificationBindingList+0)+7*A_PtrSize), "ptr", NotificationBindingList, "int*", count)   ; count
      loop % count
      {
         DllCall(NumGet(NumGet(NotificationBindingList+0)+6*A_PtrSize), "ptr", NotificationBindingList, "int", A_Index-1, "ptr*", NotificationBinding)   ; get_Item
         DllCall(NumGet(NumGet(NotificationBinding+0)+11*A_PtrSize), "ptr", NotificationBinding, "ptr*", AdaptiveNotificationTextReadOnlyList)   ; GetTextElements
         DllCall(NumGet(NumGet(AdaptiveNotificationTextReadOnlyList+0)+7*A_PtrSize), "ptr", AdaptiveNotificationTextReadOnlyList, "int*", count)   ; count
         loop % count
         {
            DllCall(NumGet(NumGet(AdaptiveNotificationTextReadOnlyList+0)+6*A_PtrSize), "ptr", AdaptiveNotificationTextReadOnlyList, "int", A_Index-1, "ptr*", AdaptiveNotificationText)   ; get_Item
            DllCall(NumGet(NumGet(AdaptiveNotificationText+0)+6*A_PtrSize), "ptr", AdaptiveNotificationText, "ptr*", hText)   ; get_Text
            buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
            if (A_Index = 1)
               text := StrGet(buffer, "UTF-16")
            else
               text .= "`n" StrGet(buffer, "UTF-16")
            DeleteHString(hText)
            ObjRelease(AdaptiveNotificationText)
         }
         ObjRelease(AdaptiveNotificationTextReadOnlyList)
         ObjRelease(NotificationBinding)
         msgbox % text
      }
      ObjRelease(NotificationBindingList)
      ObjRelease(NotificationVisual)
      ObjRelease(Notification)
      ObjRelease(UserNotification)
   }
   ObjRelease(UserNotificationReadOnlyList)
   DllCall("psapi.dll\EmptyWorkingSet", "ptr", -1)
   sleep 50
}


CreateClass(string, interface, ByRef Class)
{
   CreateHString(string, hString)
   VarSetCapacity(GUID, 16)
   DllCall("ole32\CLSIDFromString", "wstr", interface, "ptr", &GUID)
   result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", &GUID, "ptr*", Class, "uint")
   if (result != 0)
   {
      if (result = 0x80004002)
         msgbox No such interface supported
      else if (result = 0x80040154)
         msgbox Class not registered
      else
         msgbox error: %result%
      ExitApp
   }
   DeleteHString(hString)
}

CreateHString(string, ByRef hString)
{
   DllCall("Combase.dll\WindowsCreateString", "wstr", string, "uint", StrLen(string), "ptr*", hString)
}

DeleteHString(hString)
{
   DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
}

WaitForAsync(ByRef Object)
{
   AsyncInfo := ComObjQuery(Object, IAsyncInfo := "{00000036-0000-0000-C000-000000000046}")
   loop
   {
      DllCall(NumGet(NumGet(AsyncInfo+0)+7*A_PtrSize), "ptr", AsyncInfo, "uint*", status)   ; IAsyncInfo.Status
      if (status != 0)
      {
         if (status != 1)
         {
            DllCall(NumGet(NumGet(AsyncInfo+0)+8*A_PtrSize), "ptr", AsyncInfo, "uint*", ErrorCode)   ; IAsyncInfo.ErrorCode
            msgbox AsyncInfo status error: %ErrorCode%
            ExitApp
         }
         ObjRelease(AsyncInfo)
         break
      }
      sleep 10
   }
   DllCall(NumGet(NumGet(Object+0)+8*A_PtrSize), "ptr", Object, "ptr*", ObjectResult)   ; GetResults
   ObjRelease(Object)
   Object := ObjectResult
}
Last edited by Kilk1 on 30 Dec 2021, 17:32, edited 1 time in total.

Kilk1
Posts: 19
Joined: 18 May 2020, 14:45

Re: Can AHK detect when Action Center notifications occur?

Post by Kilk1 » 30 Dec 2021, 17:25

By the way, @malcev, is it possible to make the code above run fewer times per second to save resources and battery life? That'd be the only improvement I'd consider. Other than that, it's been a perfect app! :D

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Can AHK detect when Action Center notifications occur?

Post by malcev » 31 Dec 2021, 08:30

Replace sleep 50, to bigger value.

Kilk1
Posts: 19
Joined: 18 May 2020, 14:45

Re: Can AHK detect when Action Center notifications occur?

Post by Kilk1 » 31 Dec 2021, 09:14

malcev wrote:
31 Dec 2021, 08:30
Replace sleep 50, to bigger value.
Thanks for the reply! Does sleep 50 control how many frames the app "sleeps" before running again, meaning the code as posted above would run 50 fps?

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Can AHK detect when Action Center notifications occur?

Post by malcev » 31 Dec 2021, 09:39

sleep 50 means that script sleeps 1/20 sec between checking for new notifications.

Kilk1
Posts: 19
Joined: 18 May 2020, 14:45

Re: Can AHK detect when Action Center notifications occur?

Post by Kilk1 » 31 Dec 2021, 10:42

malcev wrote:
31 Dec 2021, 09:39
sleep 50 means that script sleeps 1/20 sec between checking for new notifications.
If I wanted the script to run every second instead of every 1/20 of a second, should I change the 50 to be 1000 (50 x 20)?

User avatar
boiler
Posts: 17108
Joined: 21 Dec 2014, 02:44

Re: Can AHK detect when Action Center notifications occur?

Post by boiler » 31 Dec 2021, 10:57

Yes, the value in Sleep’s Delay parameter is how many milliseconds to delay before continuing to the next line. It’s not super accurate, however.

widp
Posts: 1
Joined: 23 May 2022, 06:12

Re: Can AHK detect when Action Center notifications occur?

Post by widp » 23 May 2022, 06:16

How do I retrieve other notification properties such as notification text?
@MostHated could you please explain how you achieved this? Some code would be nice.


For people looking to do this, the windows runtime api is much easier to use with pywinrt https://pypi.org/project/winrt/ .

hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Can AHK detect when Action Center notifications occur?

Post by hasantr » 17 Jul 2022, 02:22

malcev wrote:
20 May 2020, 09:57
Tried to do it but there is memory leaking, therefore I empty working set.
Memory leaking may be because I not correct send enumerations
https://docs.microsoft.com/en-us/uwp/api/windows.ui.notifications.notificationkinds?view=winrt-18362
But despite of that code is working.
Code shows notification send by autohotkey.

Code: Select all

AppName := "Autohotkey"

setbatchlines -1
CreateClass("Windows.UI.Notifications.Management.UserNotificationListener", IUserNotificationListenerStatics := "{FF6123CF-4386-4AA3-B73D-B804E5B63B23}", UserNotificationListenerStatics)
DllCall(NumGet(NumGet(UserNotificationListenerStatics+0)+6*A_PtrSize), "ptr", UserNotificationListenerStatics, "ptr*", listener)   ; get_Current
DllCall(NumGet(NumGet(listener+0)+6*A_PtrSize), "ptr", listener, "int*", accessStatus)   ; RequestAccessAsync
WaitForAsync(accessStatus)
if (accessStatus != 1)
{
   msgbox AccessStatus Denied
   exitapp
}
loop
{
   DllCall(NumGet(NumGet(listener+0)+10*A_PtrSize), "ptr", listener, "int", 1, "ptr*", UserNotificationReadOnlyList)   ; GetNotificationsAsync
   WaitForAsync(UserNotificationReadOnlyList)
   DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+7*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int*", count)   ; count
   loop % count
   {
      DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+6*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int", A_Index-1, "ptr*", UserNotification)   ; get_Item
      DllCall(NumGet(NumGet(UserNotification+0)+8*A_PtrSize), "ptr", UserNotification, "uint*", id)   ; get_Id
      if InStr(idList, "|" id "|")
      {
         ObjRelease(UserNotification)
         Continue
      }
      idList .= "|" id "|"
      if !DllCall(NumGet(NumGet(UserNotification+0)+7*A_PtrSize), "ptr", UserNotification, "ptr*", AppInfo)   ; get_AppInfo
      {
         DllCall(NumGet(NumGet(AppInfo+0)+8*A_PtrSize), "ptr", AppInfo, "ptr*", AppDisplayInfo)   ; get_DisplayInfo
         DllCall(NumGet(NumGet(AppDisplayInfo+0)+6*A_PtrSize), "ptr", AppDisplayInfo, "ptr*", hText)   ; get_DisplayName
         buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
         text := StrGet(buffer, "UTF-16")
         DeleteHString(hText)
         ObjRelease(AppDisplayInfo)
         ObjRelease(AppInfo)
         if (text != AppName)
         {
            ObjRelease(UserNotification)
            Continue
         }
      }
      else
      {
         ObjRelease(UserNotification)
         Continue
      }
      DllCall(NumGet(NumGet(UserNotification+0)+6*A_PtrSize), "ptr", UserNotification, "ptr*", Notification)   ; get_Notification
      DllCall(NumGet(NumGet(Notification+0)+8*A_PtrSize), "ptr", Notification, "ptr*", NotificationVisual)   ; get_Visual
      DllCall(NumGet(NumGet(NotificationVisual+0)+8*A_PtrSize), "ptr", NotificationVisual, "ptr*", NotificationBindingList)   ; get_Bindings
      DllCall(NumGet(NumGet(NotificationBindingList+0)+7*A_PtrSize), "ptr", NotificationBindingList, "int*", count)   ; count
      loop % count
      {
         DllCall(NumGet(NumGet(NotificationBindingList+0)+6*A_PtrSize), "ptr", NotificationBindingList, "int", A_Index-1, "ptr*", NotificationBinding)   ; get_Item
         DllCall(NumGet(NumGet(NotificationBinding+0)+11*A_PtrSize), "ptr", NotificationBinding, "ptr*", AdaptiveNotificationTextReadOnlyList)   ; GetTextElements
         DllCall(NumGet(NumGet(AdaptiveNotificationTextReadOnlyList+0)+7*A_PtrSize), "ptr", AdaptiveNotificationTextReadOnlyList, "int*", count)   ; count
         loop % count
         {
            DllCall(NumGet(NumGet(AdaptiveNotificationTextReadOnlyList+0)+6*A_PtrSize), "ptr", AdaptiveNotificationTextReadOnlyList, "int", A_Index-1, "ptr*", AdaptiveNotificationText)   ; get_Item
            DllCall(NumGet(NumGet(AdaptiveNotificationText+0)+6*A_PtrSize), "ptr", AdaptiveNotificationText, "ptr*", hText)   ; get_Text
            buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
            if (A_Index = 1)
               text := StrGet(buffer, "UTF-16")
            else
               text .= "`n" StrGet(buffer, "UTF-16")
            DeleteHString(hText)
            ObjRelease(AdaptiveNotificationText)
         }
         ObjRelease(AdaptiveNotificationTextReadOnlyList)
         ObjRelease(NotificationBinding)
         msgbox % text
      }
      ObjRelease(NotificationBindingList)
      ObjRelease(NotificationVisual)
      ObjRelease(Notification)
      ObjRelease(UserNotification)
   }
   ObjRelease(UserNotificationReadOnlyList)
   DllCall("psapi.dll\EmptyWorkingSet", "ptr", -1)
   sleep 50
}


CreateClass(string, interface, ByRef Class)
{
   CreateHString(string, hString)
   VarSetCapacity(GUID, 16)
   DllCall("ole32\CLSIDFromString", "wstr", interface, "ptr", &GUID)
   result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", &GUID, "ptr*", Class, "uint")
   if (result != 0)
   {
      if (result = 0x80004002)
         msgbox No such interface supported
      else if (result = 0x80040154)
         msgbox Class not registered
      else
         msgbox error: %result%
      ExitApp
   }
   DeleteHString(hString)
}

CreateHString(string, ByRef hString)
{
   DllCall("Combase.dll\WindowsCreateString", "wstr", string, "uint", StrLen(string), "ptr*", hString)
}

DeleteHString(hString)
{
   DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
}

WaitForAsync(ByRef Object)
{
   AsyncInfo := ComObjQuery(Object, IAsyncInfo := "{00000036-0000-0000-C000-000000000046}")
   loop
   {
      DllCall(NumGet(NumGet(AsyncInfo+0)+7*A_PtrSize), "ptr", AsyncInfo, "uint*", status)   ; IAsyncInfo.Status
      if (status != 0)
      {
         if (status != 1)
         {
            DllCall(NumGet(NumGet(AsyncInfo+0)+8*A_PtrSize), "ptr", AsyncInfo, "uint*", ErrorCode)   ; IAsyncInfo.ErrorCode
            msgbox AsyncInfo status error: %ErrorCode%
            ExitApp
         }
         ObjRelease(AsyncInfo)
         break
      }
      sleep 10
   }
   DllCall(NumGet(NumGet(Object+0)+8*A_PtrSize), "ptr", Object, "ptr*", ObjectResult)   ; GetResults
   ObjRelease(Object)
   Object := ObjectResult
}
Notification send with this code:

Code: Select all

; Unlike TrayTip, this API does not require a tray icon:
#NoTrayIcon

; Toast notifications from desktop apps can only use local image files.
if !FileExist("sample.png")
    URLDownloadToFile https://autohotkey.com/boards/styles/simplicity/theme/images/announce_unread.png
        , % A_ScriptDir "\sample.png"
; The templates are described here:
;  http://msdn.com/library/windows/apps/windows.ui.notifications.toasttemplatetype.aspx
toast_template := "toastImageAndText02"
; Image path/URL must be absolute, not relative.
toast_image := A_ScriptDir "\sample.png"
; Text is an array because some templates have multiple text elements.
toast_text := ["Hello, world!", "This is the sub-text."]

; For Windows 10.0.16299 (and possibly earlier or later versions), the AppID
; must identify an app which has a shortcut on the Start screen, otherwise
; the notification won't display.  AppIDs for desktop apps seem to be the
; path of the executable, with system/known folders replaced with GUIDs.
; If this doesn't work, the Get-StartApps powershell command can be used to
; get a list of AppIDs on the system.
; This assumes AutoHotkey is installed in the default location:
toast_appid := (A_Is64bitOS ? "{6D809377-6AF0-444b-8957-A3773F02200E}"
                            : "{905e63b6-c1bf-494e-b29c-65b732d3d21a}")
    . "\AutoHotkey\AutoHotkey.exe"

; Only the Edge version of JsRT supports WinRT.
js := new JsRT.Edge
js.AddObject("yesno", Func("yesno"))
yesno(s) {

        return
}

; Enable use of WinRT.  "Windows.UI" or "Windows" would also work.
js.ProjectWinRTNamespace("Windows.UI.Notifications")
code =
(
    function toast(template, image, text, app) {
        // Alias for convenience.
        var N = Windows.UI.Notifications;
        // Get the template XML as an XmlDocument.
        var toastXml = N.ToastNotificationManager
            .getTemplateContent(N.ToastTemplateType[template]);
        // Insert our content.
        var i = 0;
        for (let el of toastXml.getElementsByTagName("text")) {
            if (typeof text == 'string') {
                el.innerText = text;
                break;
            }
            el.innerText = text[++i];
        }
        toastXml.getElementsByTagName("image")[0]
            .setAttribute("src", image);
        // Show the notification.
        var toastNotifier = N.ToastNotificationManager
            .createToastNotifier(app || "AutoHotkey");
        var notification = new N.ToastNotification(toastXml);
        toastNotifier.show(notification);
        // Unlike TrayTip, this API lets us hide the notification:
        if (yesno("Hide the notification?")) {
            toastNotifier.hide(notification);
        }
    }
)
try {
    ; Define the toast function.
    js.Exec(code)
    ; Show a toast notification.
    js.toast(toast_template, toast_image, toast_text, toast_appid)
}
catch ex {
    try errmsg := ex.stack
    if !errmsg
        errmsg := "Error: " ex.message
    MsgBox % errmsg
}
; Note: If the notification wasn't hidden, it will remain after we exit.
ExitApp


/*
 *  JsRT for AutoHotkey v1.1
 *
 *  Utilizes the JavaScript engine that comes with IE11.
 *
 *  License: Use, modify and redistribute without limitation, but at your own risk.
 */
class JsRT extends ActiveScript._base
{
    __New()
    {
        throw Exception("This class is abstract. Use JsRT.IE or JSRT.Edge instead.", -1)
    }
    
    class IE extends JsRT
    {
        __New()
        {
            if !this._hmod := DllCall("LoadLibrary", "str", "jscript9", "ptr")
                throw Exception("Failed to load jscript9.dll", -1)
            if DllCall("jscript9\JsCreateRuntime", "int", 0, "int", -1
                , "ptr", 0, "ptr*", runtime) != 0
                throw Exception("Failed to initialize JsRT", -1)
            DllCall("jscript9\JsCreateContext", "ptr", runtime, "ptr", 0, "ptr*", context)
            this._Initialize("jscript9", runtime, context)
        }
    }
    
    class Edge extends JsRT
    {
        __New()
        {
            if !this._hmod := DllCall("LoadLibrary", "str", "chakra", "ptr")
                throw Exception("Failed to load chakra.dll", -1)
            if DllCall("chakra\JsCreateRuntime", "int", 0
                , "ptr", 0, "ptr*", runtime) != 0
                throw Exception("Failed to initialize JsRT", -1)
            DllCall("chakra\JsCreateContext", "ptr", runtime, "ptr*", context)
            this._Initialize("chakra", runtime, context)
        }
        
        ProjectWinRTNamespace(namespace)
        {
            return DllCall("chakra\JsProjectWinRTNamespace", "wstr", namespace)
        }
    }
    
    _Initialize(dll, runtime, context)
    {
        this._dll := dll
        this._runtime := runtime
        this._context := context
        DllCall(dll "\JsSetCurrentContext", "ptr", context)
        DllCall(dll "\JsGetGlobalObject", "ptr*", globalObject)
        this._dsp := this._JsToVt(globalObject)
    }
    
    __Delete()
    {
        this._dsp := ""
        if dll := this._dll
        {
            DllCall(dll "\JsSetCurrentContext", "ptr", 0)
            DllCall(dll "\JsDisposeRuntime", "ptr", this._runtime)
        }
        DllCall("FreeLibrary", "ptr", this._hmod)
    }
    
    _JsToVt(valref)
    {
        VarSetCapacity(variant, 24, 0)
        DllCall(this._dll "\JsValueToVariant", "ptr", valref, "ptr", &variant)
        ref := ComObject(0x400C, &variant), val := ref[], ref[] := 0
        return val
    }
    
    _ToJs(val)
    {
        VarSetCapacity(variant, 24, 0)
        ref := ComObject(0x400C, &variant) ; VT_BYREF|VT_VARIANT
        ref[] := val
        DllCall(this._dll "\JsVariantToValue", "ptr", &variant, "ptr*", valref)
        ref[] := 0
        return valref
    }
    
    _JsEval(code)
    {
        e := DllCall(this._dll "\JsRunScript", "wstr", code, "uptr", 0, "wstr", "source.js"
            , "ptr*", result)
        if e
        {
            if DllCall(this._dll "\JsGetAndClearException", "ptr*", excp) = 0
                throw this._JsToVt(excp)
            throw Exception("JsRT error", -2, format("0x{:X}", e))
        }
        return result
    }
    
    Exec(code)
    {
        this._JsEval(code)
    }
    
    Eval(code)
    {
        return this._JsToVt(this._JsEval(code))
    }
    
    AddObject(name, obj, addMembers := false)
    {
        if addMembers
            throw Exception("AddMembers=true is not supported", -1)
        this._dsp[name] := obj
    }
}




/*
 *  ActiveScript for AutoHotkey v1.1
 *
 *  Provides an interface to Active Scripting languages like VBScript and JScript,
 *  without relying on Microsoft's ScriptControl, which is not available to 64-bit
 *  programs.
 *
 *  License: Use, modify and redistribute without limitation, but at your own risk.
 */
class ActiveScript extends ActiveScript._base
{
    __New(Language)
    {
        if this._script := ComObjCreate(Language, ActiveScript.IID)
            this._scriptParse := ComObjQuery(this._script, ActiveScript.IID_Parse)
        if !this._scriptParse
            throw Exception("Invalid language", -1, Language)
        this._site := new ActiveScriptSite(this)
        this._SetScriptSite(this._site.ptr)
        this._InitNew()
        this._objects := {}
        this.Error := ""
        this._dsp := this._GetScriptDispatch()  ; Must be done last.
        try
            if this.ScriptEngine() = "JScript"
                this.SetJScript58()
    }

    SetJScript58()
    {
        static IID_IActiveScriptProperty := "{4954E0D0-FBC7-11D1-8410-006008C3FBFC}"
        if !prop := ComObjQuery(this._script, IID_IActiveScriptProperty)
            return false
        VarSetCapacity(var, 24, 0), NumPut(2, NumPut(3, var, "short") + 6)
        hr := DllCall(NumGet(NumGet(prop+0)+4*A_PtrSize), "ptr", prop, "uint", 0x4000
            , "ptr", 0, "ptr", &var), ObjRelease(prop)
        return hr >= 0
    }
    
    Eval(Code)
    {
        pvar := NumGet(ComObjValue(arr:=ComObjArray(0xC,1)) + 8+A_PtrSize)
        this._ParseScriptText(Code, 0x20, pvar)  ; SCRIPTTEXT_ISEXPRESSION := 0x20
        return arr[0]
    }
    
    Exec(Code)
    {
        this._ParseScriptText(Code, 0x42, 0)  ; SCRIPTTEXT_ISVISIBLE := 2, SCRIPTTEXT_ISPERSISTENT := 0x40
        this._SetScriptState(2)  ; SCRIPTSTATE_CONNECTED := 2
    }
    
    AddObject(Name, DispObj, AddMembers := false)
    {
        static a, supports_dispatch ; Test for built-in IDispatch support.
            := a := ((a:=ComObjArray(0xC,1))[0]:=[42]) && a[0][1]=42
        if IsObject(DispObj) && !(supports_dispatch || ComObjType(DispObj))
            throw Exception("Adding a non-COM object requires AutoHotkey v1.1.17+", -1)
        this._objects[Name] := DispObj
        this._AddNamedItem(Name, AddMembers ? 8 : 2)  ; SCRIPTITEM_ISVISIBLE := 2, SCRIPTITEM_GLOBALMEMBERS := 8
    }
    
    _GetObjectUnk(Name)
    {
        return !IsObject(dsp := this._objects[Name]) ? dsp  ; Pointer
            : ComObjValue(dsp) ? ComObjValue(dsp)  ; ComObject
            : &dsp  ; AutoHotkey object
    }
    
    class _base
    {
        __Call(Method, Params*)
        {
            if ObjHasKey(this, "_dsp")
                try
                    return (this._dsp)[Method](Params*)
                catch e
                    throw Exception(e.Message, -1, e.Extra)
        }
        
        __Get(Property, Params*)
        {
            if ObjHasKey(this, "_dsp")
                try
                    return (this._dsp)[Property, Params*]
                catch e
                    throw Exception(e.Message, -1, e.Extra)
        }
        
        __Set(Property, Params*)
        {
            if ObjHasKey(this, "_dsp")
            {
                Value := Params.Pop()
                try
                    return (this._dsp)[Property, Params*] := Value
                catch e
                    throw Exception(e.Message, -1, e.Extra)
            }
        }
    }
    
    _SetScriptSite(Site)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+3*A_PtrSize), "ptr", p, "ptr", Site)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::SetScriptSite")
    }
    
    _SetScriptState(State)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+5*A_PtrSize), "ptr", p, "int", State)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::SetScriptState")
    }
    
    _AddNamedItem(Name, Flags)
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+8*A_PtrSize), "ptr", p, "wstr", Name, "uint", Flags)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::AddNamedItem")
    }
    
    _GetScriptDispatch()
    {
        hr := DllCall(NumGet(NumGet((p:=this._script)+0)+10*A_PtrSize), "ptr", p, "ptr", 0, "ptr*", pdsp)
        if (hr < 0)
            this._HRFail(hr, "IActiveScript::GetScriptDispatch")
        return ComObject(9, pdsp, 1)
    }
    
    _InitNew()
    {
        hr := DllCall(NumGet(NumGet((p:=this._scriptParse)+0)+3*A_PtrSize), "ptr", p)
        if (hr < 0)
            this._HRFail(hr, "IActiveScriptParse::InitNew")
    }
    
    _ParseScriptText(Code, Flags, pvarResult)
    {
        VarSetCapacity(excp, 8 * A_PtrSize, 0)
        hr := DllCall(NumGet(NumGet((p:=this._scriptParse)+0)+5*A_PtrSize), "ptr", p
            , "wstr", Code, "ptr", 0, "ptr", 0, "ptr", 0, "uptr", 0, "uint", 1
            , "uint", Flags, "ptr", pvarResult, "ptr", 0)
        if (hr < 0)
            this._HRFail(hr, "IActiveScriptParse::ParseScriptText")
    }
    
    _HRFail(hr, what)
    {
        if e := this.Error
        {
            this.Error := ""
            throw Exception("`nError code:`t" this._HRFormat(e.HRESULT)
                . "`nSource:`t`t" e.Source "`nDescription:`t" e.Description
                . "`nLine:`t`t" e.Line "`nColumn:`t`t" e.Column
                . "`nLine text:`t`t" e.LineText, -3)
        }
        throw Exception(what " failed with code " this._HRFormat(hr), -2)
    }
    
    _HRFormat(hr)
    {
        return Format("0x{1:X}", hr & 0xFFFFFFFF)
    }
    
    _OnScriptError(err) ; IActiveScriptError err
    {
        VarSetCapacity(excp, 8 * A_PtrSize, 0)
        DllCall(NumGet(NumGet(err+0)+3*A_PtrSize), "ptr", err, "ptr", &excp) ; GetExceptionInfo
        DllCall(NumGet(NumGet(err+0)+4*A_PtrSize), "ptr", err, "uint*", srcctx, "uint*", srcline, "int*", srccol) ; GetSourcePosition
        DllCall(NumGet(NumGet(err+0)+5*A_PtrSize), "ptr", err, "ptr*", pbstrcode) ; GetSourceLineText
        code := StrGet(pbstrcode, "UTF-16"), DllCall("OleAut32\SysFreeString", "ptr", pbstrcode)
        if fn := NumGet(excp, 6 * A_PtrSize) ; pfnDeferredFillIn
            DllCall(fn, "ptr", &excp)
        wcode := NumGet(excp, 0, "ushort")
        hr := wcode ? 0x80040200 + wcode : NumGet(excp, 7 * A_PtrSize, "uint")
        this.Error := {HRESULT: hr, Line: srcline, Column: srccol, LineText: code}
        static Infos := "Source,Description,HelpFile"
        Loop Parse, % Infos, `,
            if pbstr := NumGet(excp, A_Index * A_PtrSize)
                this.Error[A_LoopField] := StrGet(pbstr, "UTF-16"), DllCall("OleAut32\SysFreeString", "ptr", pbstr)
        return 0x80004001 ; E_NOTIMPL (let Exec/Eval get a fail result)
    }
    
    __Delete()
    {
        if this._script
        {
            DllCall(NumGet(NumGet((p:=this._script)+0)+7*A_PtrSize), "ptr", p)  ; Close
            ObjRelease(this._script)
        }
        if this._scriptParse
            ObjRelease(this._scriptParse)
    }
    
    static IID := "{BB1A2AE1-A4F9-11cf-8F20-00805F2CD064}"
    static IID_Parse := A_PtrSize=8 ? "{C7EF7658-E1EE-480E-97EA-D52CB4D76D17}" : "{BB1A2AE2-A4F9-11cf-8F20-00805F2CD064}"
}

class ActiveScriptSite
{
    __New(Script)
    {
        ObjSetCapacity(this, "_site", 3 * A_PtrSize)
        NumPut(&Script
        , NumPut(ActiveScriptSite._vftable("_vft_w", "31122", 0x100)
        , NumPut(ActiveScriptSite._vftable("_vft", "31125232211", 0)
            , this.ptr := ObjGetAddress(this, "_site"))))
    }
    
    _vftable(Name, PrmCounts, EIBase)
    {
        if p := ObjGetAddress(this, Name)
            return p
        ObjSetCapacity(this, Name, StrLen(PrmCounts) * A_PtrSize)
        p := ObjGetAddress(this, Name)
        Loop Parse, % PrmCounts
        {
            cb := RegisterCallback("_ActiveScriptSite", "F", A_LoopField, A_Index + EIBase)
            NumPut(cb, p + (A_Index-1) * A_PtrSize)
        }
        return p
    }
}

_ActiveScriptSite(this, a1:=0, a2:=0, a3:=0, a4:=0, a5:=0)
{
    Method := A_EventInfo & 0xFF
    if A_EventInfo >= 0x100  ; IActiveScriptSiteWindow
    {
        if Method = 4  ; GetWindow
        {
            NumPut(0, a1+0) ; *phwnd := 0
            return 0 ; S_OK
        }
        if Method = 5  ; EnableModeless
        {
            return 0 ; S_OK
        }
        this -= A_PtrSize     ; Cast to IActiveScriptSite
    }
    ;else: IActiveScriptSite
    if Method = 1  ; QueryInterface
    {
        iid := _AS_GUIDToString(a1)
        if (iid = "{00000000-0000-0000-C000-000000000046}"  ; IUnknown
         || iid = "{DB01A1E3-A42B-11cf-8F20-00805F2CD064}") ; IActiveScriptSite
        {
            NumPut(this, a2+0)
            return 0 ; S_OK
        }
        if (iid = "{D10F6761-83E9-11cf-8F20-00805F2CD064}") ; IActiveScriptSiteWindow
        {
            NumPut(this + A_PtrSize, a2+0)
            return 0 ; S_OK
        }
        NumPut(0, a2+0)
        return 0x80004002 ; E_NOINTERFACE
    }
    if Method = 5  ; GetItemInfo
    {
        a1 := StrGet(a1, "UTF-16")
        , (a3 && NumPut(0, a3+0))  ; *ppiunkItem := NULL
        , (a4 && NumPut(0, a4+0))  ; *ppti := NULL
        if (a2 & 1) ; SCRIPTINFO_IUNKNOWN
        {
            if !(unk := Object(NumGet(this + A_PtrSize*2))._GetObjectUnk(a1))
                return 0x8002802B ; TYPE_E_ELEMENTNOTFOUND
            ObjAddRef(unk), NumPut(unk, a3+0)
        }
        return 0 ; S_OK
    }
    if Method = 9  ; OnScriptError
        return Object(NumGet(this + A_PtrSize*2))._OnScriptError(a1)
    
    ; AddRef and Release don't do anything because we want to avoid circular references.
    ; The site and IActiveScript are both released when the AHK script releases its last
    ; reference to the ActiveScript object.
    
    ; All of the other methods don't require implementations.
    return 0x80004001 ; E_NOTIMPL
}

_AS_GUIDToString(pGUID)
{
    VarSetCapacity(String, 38*2)
    DllCall("ole32\StringFromGUID2", "ptr", pGUID, "str", String, "int", 39)
    return String
}
This works very well. I tested it with Windows 11. Thank you so much.

Descolada
Posts: 1154
Joined: 23 Dec 2021, 02:30

Re: Can AHK detect when Action Center notifications occur?

Post by Descolada » 17 Jul 2022, 10:39

Conceptually I prefer @malcev's way, but since recently I'm playing around with UIA events I tried to do that using WindowOpened events. Running this requires UIA_Interface.ahk.

Code: Select all

#Include <UIA_Interface>

UIA := UIA_Interface()
handler := UIA_CreateEventHandler("WindowOpenedEvent")
UIA.AddAutomationEventHandler(UIA_Enum.UIA_Window_WindowOpenedEventId, UIA.GetRootElement(), 0x2,, handler)
OnExit("ExitFunc") ; Set up an OnExit call to clean up the handler when exiting the script

Sleep, 1000
Traytip, Test, Notification
return

WindowOpenedEvent(sender, eventId) {
   if ((sender.CurrentName == "New notification") && sender.FindFirstBy("AutomationId=NormalToastView")) {
        ToolTip, % "Notification caught!"
        . "`nSender: " sender.FindFirstBy("AutomationId=SenderName").CurrentName
        . "`nTitle: " sender.FindFirstBy("AutomationId=TitleText").CurrentName
        . "`nMessageText: " sender.FindFirstBy("AutomationId=MessageText").CurrentName
        SetTimer, RemoveToolTip, -3000
    }
}
ExitFunc() {
	UIA_Interface().RemoveAllEventHandlers()
}
RemoveToolTip:
    ToolTip
    return
    
F5::ExitApp
Note: this method fails if a new notification appears when the old one hasn't closed. This issue probably has workarounds though.

Jolly
Posts: 3
Joined: 20 Jul 2022, 16:05
Contact:

Re: Can AHK detect when Action Center notifications occur?

Post by Jolly » 20 Jul 2022, 16:12

Anyone know if there is a way to force the action center to expand all notifications?

Also if it helps anyone - the notifications are all stored in AppData\Local\Microsoft\Windows\Notifications\wpndatabase.db which is a Sqlite db.
If you change the Metadata table, there's two parameters toast:maxCount and toastCondensed:maxCount that control the max number of notifications that windows will display. Lets you override the 20 per app, 80 total limits.

joshwanner
Posts: 1
Joined: 23 Jun 2022, 12:09

Re: Can AHK detect when Action Center notifications occur?

Post by joshwanner » 21 Jul 2022, 11:50

I tried doing this with a chrome app.

I want google calendar to popup every time I get a notification.

I used the script provided b @malcev, what does it mean when I get a popup stating no AppInfo

Kilk1
Posts: 19
Joined: 18 May 2020, 14:45

Re: Can AHK detect when Action Center notifications occur?

Post by Kilk1 » 20 Aug 2022, 18:51

I've been really enjoying this app! :D To improve it even further, I have another question: Can AutoHotKey trigger when my computer loses Internet? For example, the TextNow app I use has a "Quit" option when you right-click on its icon in the Action Center. Is there a way to make "Quit" occur whenever my computer loses Internet? If you know a method, thanks!

dostroll
Posts: 40
Joined: 03 Nov 2021, 08:56

Re: Can AHK detect when Action Center notifications occur?

Post by dostroll » 18 Oct 2022, 14:17

I was able to detect the notification with this great code, thanks.
Only the app name is detected, how do I change the code to detect the notification content?
malcev wrote:
20 May 2020, 09:57

Code: Select all

AppName := "Autohotkey"

setbatchlines -1
CreateClass("Windows.UI.Notifications.Management.UserNotificationListener", IUserNotificationListenerStatics := "{FF6123CF-4386-4AA3-B73D-B804E5B63B23}", UserNotificationListenerStatics)
DllCall(NumGet(NumGet(UserNotificationListenerStatics+0)+6*A_PtrSize), "ptr", UserNotificationListenerStatics, "ptr*", listener)   ; get_Current
DllCall(NumGet(NumGet(listener+0)+6*A_PtrSize), "ptr", listener, "int*", accessStatus)   ; RequestAccessAsync
WaitForAsync(accessStatus)
if (accessStatus != 1)
{
   msgbox AccessStatus Denied
   exitapp
}
loop
{
   DllCall(NumGet(NumGet(listener+0)+10*A_PtrSize), "ptr", listener, "int", 1, "ptr*", UserNotificationReadOnlyList)   ; GetNotificationsAsync
   WaitForAsync(UserNotificationReadOnlyList)
   DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+7*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int*", count)   ; count
   loop % count
   {
      DllCall(NumGet(NumGet(UserNotificationReadOnlyList+0)+6*A_PtrSize), "ptr", UserNotificationReadOnlyList, "int", A_Index-1, "ptr*", UserNotification)   ; get_Item
      DllCall(NumGet(NumGet(UserNotification+0)+8*A_PtrSize), "ptr", UserNotification, "uint*", id)   ; get_Id
      if InStr(idList, "|" id "|")
      {
         ObjRelease(UserNotification)
         Continue
      }
      idList .= "|" id "|"
      if !DllCall(NumGet(NumGet(UserNotification+0)+7*A_PtrSize), "ptr", UserNotification, "ptr*", AppInfo)   ; get_AppInfo
      {
         DllCall(NumGet(NumGet(AppInfo+0)+8*A_PtrSize), "ptr", AppInfo, "ptr*", AppDisplayInfo)   ; get_DisplayInfo
         DllCall(NumGet(NumGet(AppDisplayInfo+0)+6*A_PtrSize), "ptr", AppDisplayInfo, "ptr*", hText)   ; get_DisplayName
         buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
         text := StrGet(buffer, "UTF-16")
         DeleteHString(hText)
         ObjRelease(AppDisplayInfo)
         ObjRelease(AppInfo)
         if (text != AppName)
         {
            ObjRelease(UserNotification)
            Continue
         }
      }
      else
      {
         ObjRelease(UserNotification)
         Continue
      }
      DllCall(NumGet(NumGet(UserNotification+0)+6*A_PtrSize), "ptr", UserNotification, "ptr*", Notification)   ; get_Notification
      DllCall(NumGet(NumGet(Notification+0)+8*A_PtrSize), "ptr", Notification, "ptr*", NotificationVisual)   ; get_Visual
      DllCall(NumGet(NumGet(NotificationVisual+0)+8*A_PtrSize), "ptr", NotificationVisual, "ptr*", NotificationBindingList)   ; get_Bindings
      DllCall(NumGet(NumGet(NotificationBindingList+0)+7*A_PtrSize), "ptr", NotificationBindingList, "int*", count)   ; count
      loop % count
      {
         DllCall(NumGet(NumGet(NotificationBindingList+0)+6*A_PtrSize), "ptr", NotificationBindingList, "int", A_Index-1, "ptr*", NotificationBinding)   ; get_Item
         DllCall(NumGet(NumGet(NotificationBinding+0)+11*A_PtrSize), "ptr", NotificationBinding, "ptr*", AdaptiveNotificationTextReadOnlyList)   ; GetTextElements
         DllCall(NumGet(NumGet(AdaptiveNotificationTextReadOnlyList+0)+7*A_PtrSize), "ptr", AdaptiveNotificationTextReadOnlyList, "int*", count)   ; count
         loop % count
         {
            DllCall(NumGet(NumGet(AdaptiveNotificationTextReadOnlyList+0)+6*A_PtrSize), "ptr", AdaptiveNotificationTextReadOnlyList, "int", A_Index-1, "ptr*", AdaptiveNotificationText)   ; get_Item
            DllCall(NumGet(NumGet(AdaptiveNotificationText+0)+6*A_PtrSize), "ptr", AdaptiveNotificationText, "ptr*", hText)   ; get_Text
            buffer := DllCall("Combase.dll\WindowsGetStringRawBuffer", "ptr", hText, "uint*", length, "ptr")
            if (A_Index = 1)
               text := StrGet(buffer, "UTF-16")
            else
               text .= "`n" StrGet(buffer, "UTF-16")
            DeleteHString(hText)
            ObjRelease(AdaptiveNotificationText)
         }
         ObjRelease(AdaptiveNotificationTextReadOnlyList)
         ObjRelease(NotificationBinding)
         msgbox % text
      }
      ObjRelease(NotificationBindingList)
      ObjRelease(NotificationVisual)
      ObjRelease(Notification)
      ObjRelease(UserNotification)
   }
   ObjRelease(UserNotificationReadOnlyList)
   DllCall("psapi.dll\EmptyWorkingSet", "ptr", -1)
   sleep 50
}


CreateClass(string, interface, ByRef Class)
{
   CreateHString(string, hString)
   VarSetCapacity(GUID, 16)
   DllCall("ole32\CLSIDFromString", "wstr", interface, "ptr", &GUID)
   result := DllCall("Combase.dll\RoGetActivationFactory", "ptr", hString, "ptr", &GUID, "ptr*", Class, "uint")
   if (result != 0)
   {
      if (result = 0x80004002)
         msgbox No such interface supported
      else if (result = 0x80040154)
         msgbox Class not registered
      else
         msgbox error: %result%
      ExitApp
   }
   DeleteHString(hString)
}

CreateHString(string, ByRef hString)
{
   DllCall("Combase.dll\WindowsCreateString", "wstr", string, "uint", StrLen(string), "ptr*", hString)
}

DeleteHString(hString)
{
   DllCall("Combase.dll\WindowsDeleteString", "ptr", hString)
}

WaitForAsync(ByRef Object)
{
   AsyncInfo := ComObjQuery(Object, IAsyncInfo := "{00000036-0000-0000-C000-000000000046}")
   loop
   {
      DllCall(NumGet(NumGet(AsyncInfo+0)+7*A_PtrSize), "ptr", AsyncInfo, "uint*", status)   ; IAsyncInfo.Status
      if (status != 0)
      {
         if (status != 1)
         {
            DllCall(NumGet(NumGet(AsyncInfo+0)+8*A_PtrSize), "ptr", AsyncInfo, "uint*", ErrorCode)   ; IAsyncInfo.ErrorCode
            msgbox AsyncInfo status error: %ErrorCode%
            ExitApp
         }
         ObjRelease(AsyncInfo)
         break
      }
      sleep 10
   }
   DllCall(NumGet(NumGet(Object+0)+8*A_PtrSize), "ptr", Object, "ptr*", ObjectResult)   ; GetResults
   ObjRelease(Object)
   Object := ObjectResult
}

malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Can AHK detect when Action Center notifications occur?

Post by malcev » 18 Oct 2022, 22:03

Code that You posted should show notification content.

Post Reply

Return to “Ask for Help (v1)”