Can an AutoHotkey script be notified of volume changing and muting/unmuting?

Get help with using AutoHotkey and its commands and hotkeys
teadrinker
Posts: 1316
Joined: 29 Mar 2015, 09:41
Contact:

Re: Can an AutoHotkey script be notified of volume changing and muting/unmuting?

13 Dec 2019, 23:08

teadrinker wrote: The problem is that IAudioEndpointVolumeCallback works in another thread.
lexikos' RegisterSyncCallback() solves that problem. This code works well without dll and another script:

Code: Select all

SetBatchLines, -1

Gui, +AlwaysOnTop
Gui, Font, s16
Gui, Add, Text, y13, Volume:
Gui, Add, Text, xp y+5 wp right, Muted:
Gui, Add, Text, x+3 y13 left, 100
Gui, Add, Text, xp y+5, 0
GuiControl,, Static3, % Round( VA_GetMasterVolume() )
GuiControl,, Static4, % VA_GetMasterMute()
Gui, Show

IAudioEndpointVolume := VA_GetAudioEndpointVolume("playback")
IAudioEndpointVolumeCallback := IAudioEndpointVolumeCallback_Create(IAudioEndpointVolume)
VA_IAudioEndpointVolume_RegisterControlChangeNotify(IAudioEndpointVolume, IAudioEndpointVolumeCallback)
OnExit( Func("Clear").Bind(IAudioEndpointVolume, IAudioEndpointVolumeCallback) )
Return

GuiClose() {
   ExitApp
}

SetInfo(muted, volume) {
   GuiControl,, Static3, % Round( volume*100 )
   GuiControl,, Static4, % muted
}

Clear(IAudioEndpointVolume, IAudioEndpointVolumeCallback) {
   VA_IAudioEndpointVolume_UnregisterControlChangeNotify(IAudioEndpointVolume, IAudioEndpointVolumeCallback)
   ObjRelease(IAudioEndpointVolumeCallback)
   ObjRelease(IAudioEndpointVolume)
}

IAudioEndpointVolumeCallback_Create(aev) {
   static VTBL := [ "QueryInterface"
                  , "AddRef"
                  , "Release"
                  , "OnNotify" ]
                  
        , heapSize := A_PtrSize*10
        , heapOffset := A_PtrSize*9
        
        , flags := (HEAP_GENERATE_EXCEPTIONS := 0x4) | (HEAP_NO_SERIALIZE := 0x1)
        , HEAP_ZERO_MEMORY := 0x8
   
   hHeap := DllCall("HeapCreate", "UInt", flags, "Ptr", 0, "Ptr", 0, "Ptr")
   addr := IAudioEndpointVolumeCallback := DllCall("HeapAlloc", "Ptr", hHeap, "UInt", HEAP_ZERO_MEMORY, "Ptr", heapSize, "Ptr")
   addr := NumPut(addr + A_PtrSize, addr + 0)
   for k, v in VTBL
      addr := NumPut( RegisterSyncCallback("IAudioEndpointVolumeCallback_" . v), addr + 0 )
   NumPut(hHeap, IAudioEndpointVolumeCallback + heapOffset)
   Return IAudioEndpointVolumeCallback
}

IAudioEndpointVolumeCallback_QueryInterface(this, riid, ppvObject) {
   Return 0 ; S_OK
}

IAudioEndpointVolumeCallback_AddRef(this) {
   static refOffset := A_PtrSize*8
   NumPut(refCount := NumGet(this + refOffset, "UInt") + 1, this + refOffset, "UInt")
   Return refCount
}

IAudioEndpointVolumeCallback_Release(this) {
   static refOffset := A_PtrSize*8
        , heapOffset := A_PtrSize*9
   NumPut(refCount := NumGet(this + refOffset, "UInt") - 1, this + refOffset, "UInt")
   if (refCount = 0) {
      hHeap := NumGet(this + heapOffset)
      DllCall("HeapDestroy", "Ptr", hHeap)
   }
   Return refCount
}

IAudioEndpointVolumeCallback_OnNotify(this, pNotify) {
   timer := Func("SetInfo").Bind(NumGet(pNotify + 16, "UInt"), NumGet(pNotify + 20, "Float"))
   SetTimer, % timer, -10
   Return 0
}

/*
    RegisterSyncCallback

    A replacement for RegisterCallback for use with APIs that will call
    the callback on the wrong thread.  Synchronizes with the script's main
    thread via a window message.

    This version tries to emulate RegisterCallback as much as possible
    without using RegisterCallback, so shares most of its limitations,
    and some enhancements that could be made are not.

    Other differences from v1 RegisterCallback:
      - Variadic mode can't be emulated exactly, so is not supported.
      - A_EventInfo can't be set in v1, so is not supported.
      - Fast mode is not supported (the option is ignored).
      - ByRef parameters are allowed (but ByRef is ignored).
      - Throws instead of returning "" on failure.
*/
RegisterSyncCallback(FunctionName, Options:="", ParamCount:="")
{
    if !(fn := Func(FunctionName)) || fn.IsBuiltIn
        throw Exception("Bad function", -1, FunctionName)
    if (ParamCount == "")
        ParamCount := fn.MinParams
    if (ParamCount > fn.MaxParams && !fn.IsVariadic || ParamCount+0 < fn.MinParams)
        throw Exception("Bad param count", -1, ParamCount)

    static sHwnd := 0, sMsg, sSendMessageW
    if !sHwnd
    {
        Gui RegisterSyncCallback: +Parent%A_ScriptHwnd% +hwndsHwnd
        OnMessage(sMsg := 0x8000, Func("RegisterSyncCallback_Msg"))
        sSendMessageW := DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32.dll", "ptr"), "astr", "SendMessageW", "ptr")
    }

    if !(pcb := DllCall("GlobalAlloc", "uint", 0, "ptr", 96, "ptr"))
        throw
    DllCall("VirtualProtect", "ptr", pcb, "ptr", 96, "uint", 0x40, "uint*", 0)

    p := pcb
    if (A_PtrSize = 8)
    {
        /*
        48 89 4c 24 08  ; mov [rsp+8], rcx
        48 89 54'24 10  ; mov [rsp+16], rdx
        4c 89 44 24 18  ; mov [rsp+24], r8
        4c'89 4c 24 20  ; mov [rsp+32], r9
        48 83 ec 28'    ; sub rsp, 40
        4c 8d 44 24 30  ; lea r8, [rsp+48]  (arg 3, &params)
        49 b9 ..        ; mov r9, .. (arg 4, operand to follow)
        */
        p := NumPut(0x54894808244c8948, p+0)
        p := NumPut(0x4c182444894c1024, p+0)
        p := NumPut(0x28ec834820244c89, p+0)
        p := NumPut(  0xb9493024448d4c, p+0) - 1
        lParamPtr := p, p += 8

        p := NumPut(0xba, p+0, "char") ; mov edx, nmsg
        p := NumPut(sMsg, p+0, "int")
        p := NumPut(0xb9, p+0, "char") ; mov ecx, hwnd
        p := NumPut(sHwnd, p+0, "int")
        p := NumPut(0xb848, p+0, "short") ; mov rax, SendMessageW
        p := NumPut(sSendMessageW, p+0)
        /*
        ff d0        ; call rax
        48 83 c4 28  ; add rsp, 40
        c3           ; ret
        */
        p := NumPut(0x00c328c48348d0ff, p+0)
    }
    else ;(A_PtrSize = 4)
    {
        p := NumPut(0x68, p+0, "char")      ; push ... (lParam data)
        lParamPtr := p, p += 4
        p := NumPut(0x0824448d, p+0, "int") ; lea eax, [esp+8]
        p := NumPut(0x50, p+0, "char")      ; push eax
        p := NumPut(0x68, p+0, "char")      ; push nmsg
        p := NumPut(sMsg, p+0, "int")
        p := NumPut(0x68, p+0, "char")      ; push hwnd
        p := NumPut(sHwnd, p+0, "int")
        p := NumPut(0xb8, p+0, "char")      ; mov eax, &SendMessageW
        p := NumPut(sSendMessageW, p+0, "int")
        p := NumPut(0xd0ff, p+0, "short")   ; call eax
        p := NumPut(0xc2, p+0, "char")      ; ret argsize
        p := NumPut((InStr(Options, "C") ? 0 : ParamCount*4), p+0, "short")
    }
    NumPut(p, lParamPtr+0) ; To be passed as lParam.
    p := NumPut(&fn, p+0)
    p := NumPut(ParamCount, p+0, "int")
    return pcb
}

RegisterSyncCallback_Msg(wParam, lParam)
{
    if (A_Gui != "RegisterSyncCallback")
        return
    fn := Object(NumGet(lParam + 0))
    paramCount := NumGet(lParam + A_PtrSize, "int")
    params := []
    Loop % paramCount
        params.Push(NumGet(wParam + A_PtrSize * (A_Index-1)))
    return %fn%(params*)
}
User avatar
JoeWinograd
Posts: 1411
Joined: 10 Feb 2014, 20:00

Re: Can an AutoHotkey script be notified of volume changing and muting/unmuting?

14 Dec 2019, 10:25

Hi teadrinker,
Thank you for your continuing efforts on this...very much appreciated! I'll give the new code a spin this weekend and will let you know how it goes. Regards, Joe
User avatar
JoeWinograd
Posts: 1411
Joined: 10 Feb 2014, 20:00

Re: Can an AutoHotkey script be notified of volume changing and muting/unmuting?

18 Dec 2019, 12:09

Hi teadrinker,
A quick note to let you know that your new code is working perfectly here on a W10/64-bit system with 1.1.32.00/U64...no crashes...no freezes. Next task is to integrate your code into mine. Will let you know how that goes. Thanks for your ongoing help, Joe
User avatar
JoeWinograd
Posts: 1411
Joined: 10 Feb 2014, 20:00

Re: Can an AutoHotkey script be notified of volume changing and muting/unmuting?

19 Dec 2019, 15:03

Hi teadrinker,
Following up on my last post, I integrated your new code into my horizontal volume slider script...tested on W7 and W10...works great! :bravo:

A question for you. I'm not very knowledgeable with call-backs or DLLs, both of which you use heavily. I'm wondering if anything in your code survives termination of the script. In other words, after an ExitApp, is anything that you deployed via a call-back or DLL (or anything else) still active in Windows? If so, what would I have to do before an ExitApp to close/delete/remove any remnants of the script? Thanks, Joe
teadrinker
Posts: 1316
Joined: 29 Mar 2015, 09:41
Contact:

Re: Can an AutoHotkey script be notified of volume changing and muting/unmuting?

19 Dec 2019, 15:45

No, after the process is closed, all its resources are relised.
User avatar
JoeWinograd
Posts: 1411
Joined: 10 Feb 2014, 20:00

Re: Can an AutoHotkey script be notified of volume changing and muting/unmuting?

19 Dec 2019, 16:09

> after the process is closed, all its resources are released

Great news! Excellent!

Return to “Ask For Help”

Who is online

Users browsing this forum: Derpderpingtonishere, Google [Bot], NaNoMister, vsub and 102 guests