Trigger Event When Default Audio Changes

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
zZB
Posts: 20
Joined: 13 Feb 2021, 20:02

Trigger Event When Default Audio Changes

Post by zZB » 07 Oct 2022, 01:46

I have been searching for an answer but I can't seem to find a solution. I was wondering if there was an easy way of triggering an event when the default audio output changes. I couldn't find this logged in the event viewer so I couldn't use the task scheduler.

Right now I am using VA.ahk and running VA_GetDeviceName(VA_GetDevice("playback")) and looping it every second so when it returns something different from the previous loop, it triggers the event. Is there a better way of going about this? It just seems like there would be a better solution than just running this loop constantly. Thank you

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Trigger Event When Default Audio Changes

Post by lexikos » 07 Oct 2022, 02:31

It could probably be done through IMMDeviceEnumerator::RegisterEndpointNotificationCall.
The IMMNotificationClient interface provides notifications when an audio endpoint device is added or removed, when the state or properties of an endpoint device change, or when there is a change in the default role assigned to an endpoint device.
...
IMMNotificationClient::OnDefaultDeviceChanged
The OnDefaultDeviceChanged method notifies the client that the default audio endpoint device for a particular device role has changed.
Source: IMMNotificationClient (mmdeviceapi.h) - Win32 apps | Microsoft Learn

zZB
Posts: 20
Joined: 13 Feb 2021, 20:02

Re: Trigger Event When Default Audio Changes

Post by zZB » 07 Oct 2022, 03:21

lexikos wrote:
07 Oct 2022, 02:31
It could probably be done through IMMDeviceEnumerator::RegisterEndpointNotificationCall.
The IMMNotificationClient interface provides notifications when an audio endpoint device is added or removed, when the state or properties of an endpoint device change, or when there is a change in the default role assigned to an endpoint device.
...
IMMNotificationClient::OnDefaultDeviceChanged
The OnDefaultDeviceChanged method notifies the client that the default audio endpoint device for a particular device role has changed.
Source: IMMNotificationClient (mmdeviceapi.h) - Win32 apps | Microsoft Learn
Thank you, lexikos. This definitely seems like the solution. I'm not talented enough to know how to really use it but it at least I know what to look into. I found this post viewtopic.php?f=76&t=68846 and i'm trying to decipher what is going on.

zZB
Posts: 20
Joined: 13 Feb 2021, 20:02

Re: Trigger Event When Default Audio Changes

Post by zZB » 07 Oct 2022, 05:41

I'm so confused, I don't understand any of this stuff. But I did find a post that was more related to what I wanted to accomplish
qwerty12 wrote:
14 Mar 2016, 19:34
Thanks to Coco's CWebView and Lexikos' ActiveScript, I was able to create a (very) poor solution to receiving audio notifications in AutoHotkey. However, I'm facing the following problems and I would very much appreciate some help:
  • Is the manner in which I'm using VarSetCapacity in sIMMNotificationClient() safe? Rather, will the memory allocated to create an instance of the IMMNotificationClient interface/struct last the lifetime of AutoHotkey.exe or will it be freed whenever when client := sIMMNotificationClient() goes out of scope?
  • Can I force callback functions registered with RegisterCallback to run on the same thread as the rest of the script? I'm not sure but I'm assuming a new thread is used, because I must run CoInitalize again inside the callback function to be able to use the functions from the Vista Audio Control library. I've tried all the variations of setting "Fast" in RegisterCallback() and using Critical in the sIMMNotificationClient_OnDefaultDeviceChanged() function to no avail. What I have now just seems like a disaster waiting to happen. :)

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.
#Persistent
#SingleInstance force

sIMMNotificationClient_GUID2String(pGUID)
{
	VarSetCapacity(string, 38*2)
	DllCall("ole32\StringFromGUID2", "Ptr", pGUID, "Str", string, "Int", 39)
	return string
}

sIMMNotificationClient_QueryInterface(this_, riid, ppvObject)
{
	static IID_IUnknown              := "{00000000-0000-0000-C000-000000000046}"
		 , IID_IMMNotificationClient := "{7991EEC9-7E89-4D85-8390-6C703CEC60C0}"

	iid := sIMMNotificationClient_GUID2String(riid)
	if (iid = IID_IMMNotificationClient || iid = IID_IUnknown)
	{
		NumPut(this_, ppvObject+0)
		return 0 ;// S_OK
	}

	NumPut(0, ppvObject+0)
	return 0x80004002 ;// E_NOINTERFACE
}

sIMMNotificationClient_AddRef(this_)
{
	return 1
}

sIMMNotificationClient_Release(this_)
{
	return 1
}

sIMMNotificationClient_OnDeviceStateChanged(this_, pwstrDeviceId, dwNewState)
{
	return 0
}

sIMMNotificationClient_OnDeviceAdded(this_, pwstrDeviceId)
{
	return 0
}

sIMMNotificationClient_OnDeviceRemoved(this_, pwstrDeviceId)
{
	return 0
}

sIMMNotificationClient_OnDefaultDeviceChanged(this_, flow, role, pwstrDeviceId)
{
	Critical
	
	Static comInitialised := false
	if (!comInitialised) {
		DllCall("ole32\CoInitialize", "UInt", 0)
		comInitialised := true
	}
	endpointId := StrGet(pwstrDeviceId, "UTF-16")
	device := VA_GetDevice(endpointId)
	MsgBox % VA_GetDeviceName(device)
	ObjRelease(device)
	return 0
}

sIMMNotificationClient_OnPropertyValueChanged(this_, pwstrDeviceId, key)
{
	return 0
}

sIMMNotificationClient_Vtbl()
{
	static vtable
	if !VarSetCapacity(vtable) {
		funcs := ["QueryInterface", "AddRef", "Release", "OnDeviceStateChanged"
				 ,"OnDeviceAdded", "OnDeviceRemoved", "OnDefaultDeviceChanged", "OnPropertyValueChanged"]

		VarSetCapacity(vtable, funcs.Length() * A_PtrSize)

		for i, vtEntry in funcs
			NumPut(RegisterCallback("sIMMNotificationClient_" vtEntry, "Fast"), vtable, (i-1) * A_PtrSize)			
	}
	return &vtable
}

sIMMNotificationClient()
{
	static vtable := sIMMNotificationClient_Vtbl()
	VarSetCapacity(client, A_PtrSize)
	NumPut(vtable, client)
	return client
}

CLSID_MMDeviceEnumerator := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
IID_IMMDeviceEnumerator := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
client := sIMMNotificationClient()
deviceEnumerator := ComObjCreate(CLSID_MMDeviceEnumerator, IID_IMMDeviceEnumerator)
VA_IMMDeviceEnumerator_RegisterEndpointNotificationCallback(deviceEnumerator, &client) ; VA.ahk needed

So, even though I don't know what most of it means, I run it and it pops up a message box whenever i change the audio and that is really all i need. however, it does pop up with 2 message boxes everytime. is it because it is notifying me the change for both the "eConsole = 0" "eMultimedia = 1" devices? I came to this conclusion because i changed the communication device and it only popped up once.

The script will eventually error out as well so i don't know how to fix that. It points at a line from VA.ahk:
if !(deviceEnumerator := ComObjCreate(CLSID_MMDeviceEnumerator, IID_IMMDeviceEnumerator))
you gave the guy advice in the original post but i couldn't really follow what you were telling him and he never posted any modifications to his code.

lexikos
Posts: 9583
Joined: 30 Sep 2013, 04:07
Contact:

Re: Trigger Event When Default Audio Changes

Post by lexikos » 07 Oct 2022, 19:24

zZB wrote:
07 Oct 2022, 05:41
however, it does pop up with 2 message boxes everytime. is it because it is notifying me the change for both the "eConsole = 0" "eMultimedia = 1" devices?
Yes. This behaviour is documented here: IMMNotificationClient::OnDefaultDeviceChanged (mmdeviceapi.h) - Win32 apps | Microsoft Learn

The script will eventually error out as well so i don't know how to fix that.
qwerty12's post indicates that the callback method is called on a thread which is not AutoHotkey's main thread (but I never confirmed). As I wrote in the other topic, AutoHotkey is not thread-safe. This translates to undefined behaviour; generally intermittent crashes and behaviour that doesn't seem to make sense.

RegisterSyncCallback is one implementation of the "create some machine code" method that I wrote about in the other topic, but it isn't intended for implementing COM interface methods and might not be safe to use for that purpose.

zZB
Posts: 20
Joined: 13 Feb 2021, 20:02

Re: Trigger Event When Default Audio Changes

Post by zZB » 08 Oct 2022, 03:23

lexikos wrote:
07 Oct 2022, 19:24
zZB wrote:
07 Oct 2022, 05:41
however, it does pop up with 2 message boxes everytime. is it because it is notifying me the change for both the "eConsole = 0" "eMultimedia = 1" devices?
Yes. This behaviour is documented here: IMMNotificationClient::OnDefaultDeviceChanged (mmdeviceapi.h) - Win32 apps | Microsoft Learn

The script will eventually error out as well so i don't know how to fix that.
qwerty12's post indicates that the callback method is called on a thread which is not AutoHotkey's main thread (but I never confirmed). As I wrote in the other topic, AutoHotkey is not thread-safe. This translates to undefined behaviour; generally intermittent crashes and behaviour that doesn't seem to make sense.

RegisterSyncCallback is one implementation of the "create some machine code" method that I wrote about in the other topic, but it isn't intended for implementing COM interface methods and might not be safe to use for that purpose.
I do appreciate your help, lexikos. I'll likely stick to my loop though because it is all flying over my head. This is all too high level for me. thank you, though

Post Reply

Return to “Ask for Help (v1)”