Clear Win10 notification alert history from v1 to v2.

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
Marco Gatti
Posts: 20
Joined: 11 Dec 2023, 15:10

Clear Win10 notification alert history from v1 to v2.

14 May 2024, 02:59

First of all I wish a good morning to all users and moderators.

With this post I am asking for support to overcome a difficult problem.

I would need to update the following script to make it compatible with version 2.

Thanks in advance for your help.

Regards.
Marco Gatti

Code: Select all

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
}

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
   DllCall(NumGet(NumGet(listener+0)+13*A_PtrSize), "ptr", listener, "uint", id)   ; RemoveNotification
   ObjRelease(UserNotification)
}
ObjRelease(UserNotificationReadOnlyList)
DllCall("psapi.dll\EmptyWorkingSet", "ptr", -1)
return

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
}
teadrinker
Posts: 4387
Joined: 29 Mar 2015, 09:41
Contact:

Re: Clear Win10 notification alert history from v1 to v2.

14 May 2024, 12:55

Code: Select all

#Requires AutoHotkey v2

ClearNotifications()

ClearNotifications() {
    static COMClass := 'Windows.UI.Notifications.Management.UserNotificationListener'
         , IID_IUserNotificationListenerStatics := '{FF6123CF-4386-4AA3-B73D-B804E5B63B23}'
         , UserNotificationListenerAccessStatus := ['Unspecified', 'Allowed', 'Denied'], VT_UNKNOWN := 13, Toast := 1
         , _ := ({}.DefineProp)(ComValue.Prototype, '__Call', { Call: (s, n, p) => (p.InsertAt(2, s), ComCall(p*)) })

    IUserNotificationListenerStatics := ComValue(VT_UNKNOWN, WrtString(COMClass).GetFactory(IID_IUserNotificationListenerStatics))
    IUserNotificationListenerStatics.get_Current(6, 'PtrP', IUserNotificationListener := ComValue(VT_UNKNOWN, 0))
    IUserNotificationListener.RequestAccessAsync(6, 'PtrP', IAsyncOperation := ComValue(VT_UNKNOWN, 0))
    if (status := UserNotificationListenerAccessStatus[Await(IAsyncOperation) + 1]) != 'Allowed' {
        throw OSError('UserNotificationListenerAccessStatus: "' . status . '"')
    }
    IUserNotificationListener.GetNotificationsAsync(10, 'UInt', Toast, 'PtrP', IAsyncOperation := ComValue(VT_UNKNOWN, 0))
    IReadOnlyList := ComValue(VT_UNKNOWN, Await(IAsyncOperation))
    IReadOnlyList.get_Count(7, 'UIntP', &count := 0)
    Loop count {
        IReadOnlyList.get_Item(6, 'Int', A_Index - 1, 'PtrP', UserNotification := ComValue(VT_UNKNOWN, 0))
        UserNotification.get_Id(8, 'UIntP', &id := 0)
        IUserNotificationListener.RemoveNotification(13, 'UInt', id)
    }
    
    static Await(IAsyncOperation) {
        static IID_IAsyncInfo := '{00000036-0000-0000-C000-000000000046}'
             , AsyncStatus := ['Started', 'Completed', 'Canceled', 'Error']
        IAsyncInfo := ComObjQuery(IAsyncOperation, IID_IAsyncInfo)
        Loop {
            Sleep 10
            IAsyncInfo.get_Status(7, 'UIntP', &status := 0)
        } until (statusValue := AsyncStatus[status + 1]) != 'Started'
        if statusValue != 'Completed' {
            throw OSError('AsyncInfo error, status: "' . statusValue . '"')
        }
        IAsyncOperation.GetResults(8, 'PtrP', &result := 0)
        return result
    }
}

class WrtString
{
    __New(stringOrHandle) {
        if Type(stringOrHandle) = 'Integer' {
            this.ptr := stringOrHandle
        } else {
            DllCall('Combase\WindowsCreateString', 'Str', stringOrHandle, 'UInt', StrLen(stringOrHandle), 'PtrP', &HSTRING := 0)
            this.ptr := HSTRING
        }
    }
    GetText() => DllCall('Combase\WindowsGetStringRawBuffer', 'Ptr', this, 'Ptr', 0, 'Str')

    GetFactory(IID) {
        DllCall('Ole32\IIDFromString', 'Str', IID, 'Ptr', riid := Buffer(16))
        hr := DllCall('Combase\RoGetActivationFactory', 'Ptr', this, 'Ptr', riid, 'PtrP', &pInterface := 0)
        if (hr != 0)
            throw OSError(hr, A_ThisFunc)
        return pInterface
    }
    __Delete() => DllCall('Combase\WindowsDeleteString', 'Ptr', this)
}
iPhilip
Posts: 827
Joined: 02 Oct 2013, 12:21

Re: Clear Win10 notification alert history from v1 to v2.

16 May 2024, 11:08

@teadrinker, Thank you for posting that function. I took a close look at it as I am interested in this topic and I have a couple of questions:

1. Can you help me understand the syntax of this line?

Code: Select all

_ := ({}.DefineProp)(ComValue.Prototype, '__Call', { Call: (s, n, p) => (p.InsertAt(2, s), ComCall(p*)) })
It looks like you are defining a meta-function that gets called when a ComValue is called in order to implement a ComCall. I am familiar with the DefineProp method but your construct is new to me and I would like to understand it so that I can use it in my scripts.

2. It seems that there is a memory leak in the function. I tested this by creating an infinite loop that calls the function and observing the script's memory usage in the Task Manager. There is nothing obvious to indicate where the memory leak might be. @malcev faced the same issue in one of his scripts. His solution was to use the EmptyWorkingSet function to free as much memory as possible. Below is a version of your function that includes that as well as a line to call the IAsyncInfo.Close method in the Await function. If you are aware of a better solution to the memory leak problem, I would be interested in that.

Code: Select all

#Requires AutoHotkey v2

ClearNotifications()

ClearNotifications() {
    static COMClass := 'Windows.UI.Notifications.Management.UserNotificationListener'
         , IID_IUserNotificationListenerStatics := '{FF6123CF-4386-4AA3-B73D-B804E5B63B23}'
         , UserNotificationListenerAccessStatus := ['Unspecified', 'Allowed', 'Denied'], VT_UNKNOWN := 13, Toast := 1
         , _ := ({}.DefineProp)(ComValue.Prototype, '__Call', { Call: (s, n, p) => (p.InsertAt(2, s), ComCall(p*)) })

    IUserNotificationListenerStatics := ComValue(VT_UNKNOWN, WrtString(COMClass).GetFactory(IID_IUserNotificationListenerStatics))
    IUserNotificationListenerStatics.get_Current(6, 'PtrP', IUserNotificationListener := ComValue(VT_UNKNOWN, 0))
    IUserNotificationListener.RequestAccessAsync(6, 'PtrP', IAsyncOperation := ComValue(VT_UNKNOWN, 0))
    if (status := UserNotificationListenerAccessStatus[Await(IAsyncOperation) + 1]) != 'Allowed' {
        throw OSError('UserNotificationListenerAccessStatus: "' . status . '"')
    }
    IUserNotificationListener.GetNotificationsAsync(10, 'UInt', Toast, 'PtrP', IAsyncOperation := ComValue(VT_UNKNOWN, 0))
    IReadOnlyList := ComValue(VT_UNKNOWN, Await(IAsyncOperation))
    IReadOnlyList.get_Count(7, 'UIntP', &count := 0)
    Loop count {
        IReadOnlyList.get_Item(6, 'Int', A_Index - 1, 'PtrP', UserNotification := ComValue(VT_UNKNOWN, 0))
        UserNotification.get_Id(8, 'UIntP', &id := 0)
        IUserNotificationListener.RemoveNotification(13, 'UInt', id)
    }
    DllCall('Psapi.dll\EmptyWorkingSet', 'Ptr', -1, 'Int')  ; <---
    
    static Await(IAsyncOperation) {
        static IID_IAsyncInfo := '{00000036-0000-0000-C000-000000000046}'
             , AsyncStatus := ['Started', 'Completed', 'Canceled', 'Error']
        IAsyncInfo := ComObjQuery(IAsyncOperation, IID_IAsyncInfo)
        Loop {
            Sleep 10
            IAsyncInfo.get_Status(7, 'UIntP', &status := 0)
        } until (statusValue := AsyncStatus[status + 1]) != 'Started'
        if statusValue != 'Completed' {
            throw OSError('AsyncInfo error, status: "' . statusValue . '"')
        }
        IAsyncOperation.GetResults(8, 'PtrP', &result := 0)
        IAsyncInfo.Close(10)  ; <---
        return result
    }
}

class WrtString
{
    __New(stringOrHandle) {
        if Type(stringOrHandle) = 'Integer' {
            this.ptr := stringOrHandle
        } else {
            DllCall('Combase\WindowsCreateString', 'Str', stringOrHandle, 'UInt', StrLen(stringOrHandle), 'PtrP', &HSTRING := 0)
            this.ptr := HSTRING
        }
    }
    GetText() => DllCall('Combase\WindowsGetStringRawBuffer', 'Ptr', this, 'Ptr', 0, 'Str')

    GetFactory(IID) {
        DllCall('Ole32\IIDFromString', 'Str', IID, 'Ptr', riid := Buffer(16))
        hr := DllCall('Combase\RoGetActivationFactory', 'Ptr', this, 'Ptr', riid, 'PtrP', &pInterface := 0)
        if (hr != 0)
            throw OSError(hr, A_ThisFunc)
        return pInterface
    }
    __Delete() => DllCall('Combase\WindowsDeleteString', 'Ptr', this)
}
Thank you.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
iPhilip
Posts: 827
Joined: 02 Oct 2013, 12:21

Re: Clear Win10 notification alert history from v1 to v2.

16 May 2024, 11:34

iPhilip wrote:
16 May 2024, 11:08
1. Can you help me understand the syntax of this line?

Code: Select all

_ := ({}.DefineProp)(ComValue.Prototype, '__Call', { Call: (s, n, p) => (p.InsertAt(2, s), ComCall(p*)) })
It looks like you are defining a meta-function that gets called when a ComValue is called in order to implement a ComCall. I am familiar with the DefineProp method but your construct is new to me and I would like to understand it so that I can use it in my scripts.
I think I figured out the answer to the first question. This section of the documentation describes a way to add properties and methods to prototype objects. Thus, a way to implement a method for a ComValue is as follows:

Code: Select all

DefProp := {}.DefineProp
DefProp(ComValue.Prototype, '__Call', {Call: ComValueCall})
ComValueCall(ComObj, MethodName, Params) => MsgBox(Type(ComObj) '`n' MethodName '`n' Params[1])

o := ComValue(VT_UNKNOWN := 0xD, 0)
o.SomeMethod(3)
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
teadrinker
Posts: 4387
Joined: 29 Mar 2015, 09:41
Contact:

Re: Clear Win10 notification alert history from v1 to v2.

16 May 2024, 14:23

iPhilip wrote: It looks like you are defining a meta-function that gets called when a ComValue is called in order to implement a ComCall.
Yeah, that's right.
When we use the ComCall() function, we need to specify the method offset in the vtable in the first parameter. To avoid using "magic numbers", I wrote the code this way:

Code: Select all

ComCall(GetResults := 8, IAsyncOperation, 'PtrP', &result := 0)
Implementing the __Call() meta-function allows you to specify the name of the method without unnecessary assignment and to shorten the code a bit.
Here is my code with parameter interpretation:

Code: Select all

({}.DefineProp)(ComValue.Prototype, '__Call', { Call: (
    thisComValue, methodName, parameterArray ; the first element of the parameter array is the offset of the method in the vtable,
                                             ; the rest of the elements are parameters when ComCall() is called,
                                             ; excluding ComValue, which will be added in the next step via parameterArray.InsertAt(2, thisComValue)
) => (parameterArray.InsertAt(2, thisComValue), ComCall(parameterArray*)) })
As you can see, methodName is not used in the final parameter list, so you can specify any.
Below is a version of your function that includes that as well as a line to call the IAsyncInfo.Close method in the Await function.
This is probably a right addition, however, memory leaks from my tests exist in both cases and are roughly equal.
iPhilip
Posts: 827
Joined: 02 Oct 2013, 12:21

Re: Clear Win10 notification alert history from v1 to v2.

17 May 2024, 12:26

teadrinker wrote:
16 May 2024, 14:23
Implementing the __Call() meta-function allows you to specify the name of the method without unnecessary assignment and to shorten the code a bit.
Here is my code with parameter interpretation:

Code: Select all

({}.DefineProp)(ComValue.Prototype, '__Call', { Call: (
    thisComValue, methodName, parameterArray ; the first element of the parameter array is the offset of the method in the vtable,
                                             ; the rest of the elements are parameters when ComCall() is called,
                                             ; excluding ComValue, which will be added in the next step via parameterArray.InsertAt(2, thisComValue)
) => (parameterArray.InsertAt(2, thisComValue), ComCall(parameterArray*)) })
As you can see, methodName is not used in the final parameter list, so you can specify any.
Thank you. That's very clear. In essence then, methodName acts as an in-line comment.

To shorten the code even further, one can use methodName as a property name in an object literal to look up the "magic numbers" as follows:

Code: Select all

#Requires AutoHotkey v2

ClearNotifications()

ClearNotifications() {
    static COMClass := 'Windows.UI.Notifications.Management.UserNotificationListener'
         , IID_IUserNotificationListenerStatics := '{FF6123CF-4386-4AA3-B73D-B804E5B63B23}'
         , UserNotificationListenerAccessStatus := ['Unspecified', 'Allowed', 'Denied'], VT_UNKNOWN := 13, Toast := 1
         , f := {Close:10, get_Count:7, get_Current:6, get_Id:8, get_Item:6, get_Status:7, GetNotificationsAsync:10, GetResults:8, RemoveNotification:13, RequestAccessAsync:6}
         , _ := ({}.DefineProp)(ComValue.Prototype, '__Call', { Call: (s, n, p) => ComCall([f.%n%, s, p*]*) })

    IUserNotificationListenerStatics := ComValue(VT_UNKNOWN, WrtString(COMClass).GetFactory(IID_IUserNotificationListenerStatics))
    IUserNotificationListenerStatics.get_Current('PtrP', IUserNotificationListener := ComValue(VT_UNKNOWN, 0))
    IUserNotificationListener.RequestAccessAsync('PtrP', IAsyncOperation := ComValue(VT_UNKNOWN, 0))
    if (status := UserNotificationListenerAccessStatus[Await(IAsyncOperation) + 1]) != 'Allowed' {
        throw OSError('UserNotificationListenerAccessStatus: "' . status . '"')
    }
    IUserNotificationListener.GetNotificationsAsync('UInt', Toast, 'PtrP', IAsyncOperation := ComValue(VT_UNKNOWN, 0))
    IReadOnlyList := ComValue(VT_UNKNOWN, Await(IAsyncOperation))
    IReadOnlyList.get_Count('UIntP', &count := 0)
    Loop count {
        IReadOnlyList.get_Item('Int', A_Index - 1, 'PtrP', UserNotification := ComValue(VT_UNKNOWN, 0))
        UserNotification.get_Id('UIntP', &id := 0)
        IUserNotificationListener.RemoveNotification('UInt', id)
    }
    
    static Await(IAsyncOperation) {
        static IID_IAsyncInfo := '{00000036-0000-0000-C000-000000000046}'
             , AsyncStatus := ['Started', 'Completed', 'Canceled', 'Error']
        IAsyncInfo := ComObjQuery(IAsyncOperation, IID_IAsyncInfo)
        Loop {
            Sleep 10
            IAsyncInfo.get_Status('UIntP', &status := 0)
        } until (statusValue := AsyncStatus[status + 1]) != 'Started'
        if statusValue != 'Completed' {
            throw OSError('AsyncInfo error, status: "' . statusValue . '"')
        }
        IAsyncOperation.GetResults('PtrP', &result := 0)
        IAsyncInfo.Close()
        return result
    }
}

class WrtString
{
    __New(stringOrHandle) {
        if Type(stringOrHandle) = 'Integer' {
            this.ptr := stringOrHandle
        } else {
            DllCall('Combase\WindowsCreateString', 'Str', stringOrHandle, 'UInt', StrLen(stringOrHandle), 'PtrP', &HSTRING := 0)
            this.ptr := HSTRING
        }
    }
    GetText() => DllCall('Combase\WindowsGetStringRawBuffer', 'Ptr', this, 'Ptr', 0, 'Str')

    GetFactory(IID) {
        DllCall('Ole32\IIDFromString', 'Str', IID, 'Ptr', riid := Buffer(16))
        hr := DllCall('Combase\RoGetActivationFactory', 'Ptr', this, 'Ptr', riid, 'PtrP', &pInterface := 0)
        if (hr != 0)
            throw OSError(hr, A_ThisFunc)
        return pInterface
    }
    __Delete() => DllCall('Combase\WindowsDeleteString', 'Ptr', this)
}

teadrinker wrote:
16 May 2024, 14:23
This is probably a right addition, however, memory leaks from my tests exist in both cases and are roughly equal.
I agree with you (after more testing). The good thing is that this function is not likely to be called more than a few dozen times.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)
teadrinker
Posts: 4387
Joined: 29 Mar 2015, 09:41
Contact:

Re: Clear Win10 notification alert history from v1 to v2.

17 May 2024, 18:52

iPhilip wrote:

Code: Select all

f := {Close:10, get_Count:7, get_Current:6, get_Id:8, get_Item:6, get_Status:7, GetNotificationsAsync:10, GetResults:8, RemoveNotification:13, RequestAccessAsync:6}
This approach won't always work because code may use interfaces whose some methods have the same names but different offsets, for example:
IWICBitmapScaler::Initialize — offset 8
IWICBitmapEncoder::Initialize — offset 3

IWICBitmapFrameEncode::Commit — offset 12
IWICBitmapEncoder::Commit — offset 11
iPhilip
Posts: 827
Joined: 02 Oct 2013, 12:21

Re: Clear Win10 notification alert history from v1 to v2.

17 May 2024, 21:03

I agree. When names of interface methods are the same, the property names will need to be differentiated, e.g. Scaler_Initialize vs Encoder_Initialize.
Windows 10 Pro (64 bit) - AutoHotkey v2.0+ (Unicode 64-bit)

Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: sofista, teadrinker and 61 guests