I have AutoHotkey script that can switch between multiple sound devices with a single keystroke.
Everything works fine, I am using nircmd utility to activate the device (Set as Default Device)
Run, Tools\nircmd.exe setdefaultsounddevice "%playback%", where %playback% is the actual sound device name.
So my script basically loops through 3 devices (Headset, Speakers, TV) that I have in Sound Panel.
But, when my TV is OFF (Disconnected), it still loops through all 3 devices.
What I need is to be able to check if the device is disconnected in my script.
I couldn't find any command in nircmd that can do that.
Please let me know if you have any ideas.
Thanks.

Surely very doable, but be warned, the code in this answer gets quite advanced.
This is without using any external utilities.
So we're interested in the
EnumAudioEndpointsmethod of theIMMDeviceEnumeratorinterface.With this method we can list desired audio devices based on some criteria.
Problem:
How do we make use of such a method in AHK?
By
DllCalling it. And to to that, we need its address in memory (pointer), sinceDllCallis able to call functions/methods by address.So we start off by getting the
IMMDeviceEnumeratorinterface's pointer.For that we require its CLSID and in this case also its IID. I found them via a Google Search.
Then we make use of AHK's
ComObjCreatefunction (and to make it even more complicated, yes, we're working with ComObjects)And as the AHK documention specifies, we do indeed get a pointer instead of an object from the function, because we specified an IID.
Now that we have the pointer to the interface
pDeviceEnumerator, we need a pointer to theEnumAudioEndpointsmethod of the interface.So that's our next problem.
Firstly we need to understand, that our desired method is the first method of the interface.
But because the interface inherits from
IUnknownthe interface's first three methods are actuallyAddRef,QueryInterface, andRelease.Therefore, our desired method of the interface is actually the fourth method of the interface.
Ok, so we want to get the pointer to 4th method of this interface.
To do this, we first want to get the pointer to the interface's virtual table. The vtable contains every method's pointer. And after we have the pointer the the vtable, we can get our desired method's pointer from that vtable.
To get these pointers, we're going to use AHK's
NumGetfunction.The vtable is usually located at the beginning of the ComObject (at offset 0), so lets
NumGetourpDeviceEnumeratorpointer at offset 0 to get to the vtable:vtable := NumGet(pDeviceEnumerator+0)+0is specified so AHK doesn't treat the variablepDeviceEnumeratoras a ByRef variable, and instead operates at our desired address in memory. And we're omitting the second and third parameters to use the default values, offset 0 (which is exactly what we want) and also the UPtr type is fine for us.Now that we have the memory address of the vtable, lets finally get the pointer of the
EnumAudioEndpointsmethod.Now remember how it's the first (but actually fourth) method in the vtable (offset 3).
So we want to get the address in memory which is offset 3 methods from the vtable's memory address.
And now remember how the vtable contained pointers, so we want to go forward the size of 3 pointers in memory. The size of a pointer is 4 bytes on a 32bit machine, and 8 bytes on a 64bit machine.
So it's pretty safe to say that nowadays the size of a pointer is always 8 bytes, when our program is ran on a modern desktop computer. We can also make use of the built in AHK variable
A_PtrSize. It'll contain 4 or 8.Visual representation of the pointers being stored in the vtable:
So we want to
NumGetat offset 24 bytes:pEnumAudioEndpoints := NumGet(vtable+0, 3*A_PtrSize)(Demonstrating the usage of
A_PtrSizeto make your script compatible on 32bit machines as well, but you could just as well not have that and specify 24)Ok, phew, now we finally have the pointer to the
IMMDeviceEnumerator::EnumAudioEndpointsmethod, which means we can finally use it.So the next problem is, how do we use it?
Firstly we need to decide how we want to use it. I can think of two ways we'd want to use it.
The first being list all active & plugged in devices and doing desired stuff with them and ditching the usage of nircmd altogether, and the second being a little simplification that'll work just for your specific case.
So, the second way, the simplification. For this what I thought of, was listing the unplugged devices and if there is one, you'll know what in your specific case the TV is unplugged.
If there isn't one, you'll know that the TV is plugged in.
Ok, so onto using the method. It expects three arguments:
dataFlowFor this parameter we specify a value from the
EDataFlowenum. Our desired value iseRender, which is the first member of the enum, so 0.dwStateMaskFor this we specify desired bitwise flags. We want only unplugged devices, so we'll be fine with just the
DEVICE_STATE_UNPLUGGEDflag (0x00000008).**ppDevicesHere we specify a pointer to a variable, which is going to receive the pointer to the memory address where the resulting
IMMDeviceCollectioninterface is located.And now onto
DllCalling. The way to call a method withDllCallis even more AHK magic, you'll hardly even find it from the documentation even, but it's kind of there.Methods are called on instances, so for the first parameter of the
DllCallwe'll pass the pointer of the method, which we have stored inpEnumAudioEndpoints, and for the second parameter we want to pass the pointer of the object (instance of interface) we're acting on, which we have stored inpDeviceEnumerator.After that, we normally pass arguments to the method.
The syntax of
DllCallis Type followed by Argument.First we pass a pointer, Ptr.
Then we pass two non-negative numbers, the type unsigned integer, UInt, will do fine.
Then we pass a PtrP to pass the pointer of the variable
pDeviceCollection.You'll notice that this variable was never even declared, but that's fine, AHK is such a forgiving language so it'll automatically create the variable for us.
Ok, now the
DllCallis all done and we have a pointer to a resultingIMMDeviceCollectioninterface.You'll notice how the interface includes two methods,
GetCountandItem.For the simplified way I'm demonstrating for you, we're interested in the
GetCountmethod.So once again, we'll get the address of that interface's vtable:
vtable := NumGet(pDeviceCollection+0)And again, we're interested in the first (but actually fourth) method of the interface (offset 3):
pGetCount := NumGet(vtable+0, 3*A_PtrSize)And then we can already use the method, so lets
DllCallagain.And this time the object we're acting upon is
IMMDeviceCollection, and we have its pointer stored in thepDeviceCollectionvariable.The functions expects just one argument
*pcDevices, a pointer to a variable which is going to receive the number of devices there is on our device collection.DllCall(pGetCount, Ptr, pDeviceCollection, UIntP, DeviceCount)And there we go, the simplified way is all done.
We successfully received the number of unplugged, but enabled, audio playback devices.
Now at the end when we know we're done with the ComObjects, we should release them (as the documentation specifies). This is necessarily not 100% required, but is definitely good practice, so lets release the ComObjects:
And here's a full example script for the simplified way:
If this seems like it isvery complex/hard, that's because it kind of is.
I'd say this is almost as complicated as AHK
DllCalling gets.But well, that's the way it is when you don't use an external utility that does all the cool stuff for you.
If you decide to implement the proper solution for handling your audio devices I talked about, this might be a good library you can use, or take reference from. I haven't used it myself, so can't say if some stuff there is outdated.
It's made by Lexikos himself.