Code: Select all
; -----------------------------------------------------------------------------
; Author: AndyMK
; Date : 02/02/2025
; PB 6.20 Beta 3 (x64)
; -----------------------------------------------------------------------------
EnableExplicit
#CLSCTX_ALL = $17
#DEVICE_STATE_ACTIVE = $1
; For roles in SetDefaultEndpoint
#ERole_Console = 0
#ERole_Multimedia = 1
#ERole_Communications = 2
; For IPropertyStore
#STGM_READ = $00000000
; -----------------------------------------------------------------------------
; Structures for property access
; -----------------------------------------------------------------------------
Structure PROPERTYKEY
Data1.l
Data2.w
Data3.w
Data4.b[8]
pid.l
EndStructure
Structure PROPVARIANT_CLIPDATA
cbSize.l
ulClipFmt.l
*pClipData.BYTE
EndStructure
Structure PROPVARIANT_BSTRBLOB
cbSize.l
*pData.BYTE
EndStructure
Structure PROPVARIANT_BLOB
cbSize.l
*pBlobData.BYTE
EndStructure
Structure PROPVARIANT_VERSIONEDSTREAM
guidVersion.GUID
*pStream.IStream
EndStructure
Structure PROPVARIANT_CAC
cElems.l
*pElems.BYTE
EndStructure
Structure PROPVARIANT_CAUB
cElems.l
*pElems.BYTE
EndStructure
Structure PROPVARIANT_CAI
cElems.l
*pElems.WORD
EndStructure
Structure PROPVARIANT_CAUI
cElems.l
*pElems.WORD
EndStructure
Structure PROPVARIANT_CAL
cElems.l
*pElems.LONG
EndStructure
Structure PROPVARIANT_CAUL
cElems.l
*pElems.LONG
EndStructure
Structure PROPVARIANT_CAFLT
cElems.l
*pElems.FLOAT
EndStructure
Structure PROPVARIANT_CADBL
cElems.l
*pElems.DOUBLE
EndStructure
Structure PROPVARIANT_CACY
cElems.l
*pElems.QUAD
EndStructure
Structure PROPVARIANT_CADATE
cElems.l
*pElems.DOUBLE
EndStructure
Structure PROPVARIANT_CABSTR
cElems.l
*pElems.INTEGER
EndStructure
Structure PROPVARIANT_CABSTRBLOB
cElems.l
*pElems.PROPVARIANT_BSTRBLOB
EndStructure
Structure PROPVARIANT_CABOOL
cElems.l
*pElems.WORD
EndStructure
Structure PROPVARIANT_CASCODE
cElems.l
*pElems.LONG
EndStructure
Structure PROPVARIANT_CAPROPVARIANT
cElems.l
*pElems.PROPVARIANT
EndStructure
Structure PROPVARIANT_CAH
cElems.l
*pElems.QUAD
EndStructure
Structure PROPVARIANT_CAUH
cElems.l
*pElems.QUAD
EndStructure
Structure PROPVARIANT_CALPSTR
cElems.l
*pElems.INTEGER
EndStructure
Structure PROPVARIANT_CALPWSTR
cElems.l
*pElems.INTEGER
EndStructure
Structure PROPVARIANT_CAFILETIME
cElems.l
*pElems.FILETIME
EndStructure
Structure PROPVARIANT_CACLIPDATA
cElems.l
*pElems.PROPVARIANT_CLIPDATA
EndStructure
Structure PROPVARIANT_CACLSID
cElems.l
*pElems.CLSID
EndStructure
Structure PROPVARIANT Align #PB_Structure_AlignC
vt.w
wReserved1.w
wReserved2.w
wReserved3.w
StructureUnion
cVal.b
bVal.b
iVal.w
uiVal.w
lVal.l
ulVal.l
intVal.l
uintVal.l
hVal.q
uhVal.q
fltVal.f
dblVal.d
boolVal.w
scode.l
cyVal.q
date.d
filetime.FILETIME
*puuid.CLSID
*pclipdata.PROPVARIANT_CLIPDATA
bstrVal.i
bstrblobVal.PROPVARIANT_BSTRBLOB
blob.PROPVARIANT_BLOB
*pszVal
*pwszVal
*punkVal.IUnknown
*pdispVal.IDispatch
*pStream.IStream
*pStorage.IStorage
*pVersionedStream.PROPVARIANT_VERSIONEDSTREAM
*parray.SAFEARRAY
cac.PROPVARIANT_CAC
caub.PROPVARIANT_CAUB
cai.PROPVARIANT_CAI
caui.PROPVARIANT_CAUI
cal.PROPVARIANT_CAL
caul.PROPVARIANT_CAUL
cah.PROPVARIANT_CAH
cauh.PROPVARIANT_CAUH
caflt.PROPVARIANT_CAFLT
cadbl.PROPVARIANT_CADBL
cabool.PROPVARIANT_CABOOL
cascode.PROPVARIANT_CASCODE
cacy.PROPVARIANT_CACY
cadate.PROPVARIANT_CADATE
cafiletime.PROPVARIANT_CAFILETIME
cauuid.PROPVARIANT_CACLSID
caclipdata.PROPVARIANT_CACLIPDATA
cabstr.PROPVARIANT_CABSTR
cabstrblob.PROPVARIANT_CABSTRBLOB
calpstr.PROPVARIANT_CALPSTR
calpwstr.PROPVARIANT_CALPWSTR
capropvar.PROPVARIANT_CAPROPVARIANT
*pcVal.BYTE
*pbVal.BYTE
*piVal.WORD
*puiVal.WORD
*plVal.LONG
*pulVal.LONG
*pintVal.LONG
*puintVal.LONG
*pfltVal.FLOAT
*pdblVal.DOUBLE
*pboolVal.WORD
*pdecVal.VARIANT_DECIMAL
*pscode.LONG
*pcyVal.QUAD
*pdate.DOUBLE
*pbstrVal.INTEGER
*ppunkVal.INTEGER
*ppdispVal.INTEGER
*pparray.INTEGER
*pvarVal.PROPVARIANT
EndStructureUnion
EndStructure
; -----------------------------------------------------------------------------
; COM Interfaces
; -----------------------------------------------------------------------------
Interface IMMDeviceEnumerator Extends IUnknown
EnumAudioEndpoints(dataFlow.l, dwStateMask.l, *ppDevices)
GetDefaultAudioEndpoint(dataFlow.l, role.l, *ppEndpoint)
GetDevice(pwstrId, ppDevice)
RegisterEndpointNotificationCallback(pClient)
UnregisterEndpointNotificationCallback(pClient)
EndInterface
Interface IMMDeviceCollection Extends IUnknown
GetCount(*pCount)
Item(index.l, *ppDevice)
EndInterface
Interface IMMDevice Extends IUnknown
Activate(iid.i, dwClsCtx.l, pActivationParams.i, ppInterface.i)
OpenPropertyStore(stgmAccess.l, ppProperties.i)
GetId(*ppstrId)
GetState(*pdwState)
EndInterface
Interface IPropertyStore Extends IUnknown
GetCount(*cProps.l)
GetAt(i.l, *pKey.PROPERTYKEY)
GetValue(*key.PROPERTYKEY, *pv.PROPVARIANT)
SetValue(*key.PROPERTYKEY, *pv.PROPVARIANT)
Commit()
EndInterface
; Our undocumented IPolicyConfig with placeholders
; Make sure SetDefaultEndpoint is at the correct vtable slot for your system
Interface IPolicyConfig Extends IUnknown
Unused1()
Unused2()
Unused3()
Unused4()
Unused5()
Unused6()
Unused7()
Unused8()
Unused9()
Unused10()
SetDefaultEndpoint(pwstrId, role.l)
EndInterface
; -----------------------------------------------------------------------------
; Procedure: GetFriendlyName(device.IMMDevice)
; - Opens property store + retrieves PKEY_Device_FriendlyName
; -----------------------------------------------------------------------------
Procedure.s GetFriendlyName(device.IMMDevice)
Protected props.IPropertyStore
Protected propVariant.PROPVARIANT
Protected hr.l
Protected result.s = ""
hr = device\OpenPropertyStore(#STGM_READ, @props)
If hr = #S_OK And props
propVariant\vt = 0
hr = props\GetValue(?PKEY_Device_FriendlyName, @propVariant)
If hr = #S_OK
If propVariant\vt = 31 ; VT_LPWSTR (0x1F)
Protected *wideName = propVariant\pwszVal
If *wideName
result = PeekS(*wideName, -1, #PB_Unicode)
EndIf
EndIf
EndIf
props\Release()
EndIf
ProcedureReturn result
EndProcedure
; -----------------------------------------------------------------------------
; Procedure: ListRenderDevices
; - Enumerates active render endpoints
; - Prints each device ID + friendly name
; -----------------------------------------------------------------------------
Procedure ListRenderDevices()
Protected hr.l, deviceEnumerator.IMMDeviceEnumerator, deviceCollection.IMMDeviceCollection
Protected count.l, i.l
Protected device.IMMDevice
Protected deviceId
Protected name.s
CoInitialize_(#Null)
hr = CoCreateInstance_(?CLSID_MMDeviceEnumerator, #Null, #CLSCTX_ALL, ?IID_IMMDeviceEnumerator, @deviceEnumerator)
If hr <> #S_OK
Debug "CoCreateInstance(IMMDeviceEnumerator) failed hr=" + Hex(hr)
ProcedureReturn
EndIf
; eRender=0, #DEVICE_STATE_ACTIVE=1 => get active render endpoints
hr = deviceEnumerator\EnumAudioEndpoints(0, #DEVICE_STATE_ACTIVE, @deviceCollection)
If hr <> #S_OK
Debug "EnumAudioEndpoints() failed hr=" + Hex(hr)
ProcedureReturn
EndIf
deviceCollection\GetCount(@count)
Debug "Active Render Devices found: " + Str(count)
For i=0 To count-1
deviceCollection\Item(i, @device)
If device
; Retrieve device ID
device\GetId(@deviceId)
; Retrieve friendly name
name = GetFriendlyName(device)
Debug "Index=" + Str(i)
Debug " ID = " + PeekS(deviceId, -1, #PB_Unicode)
Debug " FriendlyName = " + name
device\Release()
EndIf
Next
deviceCollection\Release()
deviceEnumerator\Release()
CoUninitialize_()
EndProcedure
; -----------------------------------------------------------------------------
; Procedure: SetDefaultRenderDeviceByIndex(index.l, role.l)
; - Sets the device at 'index' as default for role (eConsole, eMultimedia, etc.)
; -----------------------------------------------------------------------------
Procedure SetDefaultRenderDeviceByIndex(index.l, role.l)
Protected hr.l
Protected deviceEnumerator.IMMDeviceEnumerator
Protected deviceCollection.IMMDeviceCollection
Protected count.l
Protected device.IMMDevice
Protected deviceId
Protected name.s
Protected policyConfig.IPolicyConfig
CoInitialize_(#Null)
hr = CoCreateInstance_(?CLSID_MMDeviceEnumerator, #Null, #CLSCTX_ALL, ?IID_IMMDeviceEnumerator, @deviceEnumerator)
If hr <> #S_OK
Debug "CoCreateInstance(IMMDeviceEnumerator) failed hr=" + Hex(hr)
Goto Cleanup
EndIf
hr = deviceEnumerator\EnumAudioEndpoints(0, #DEVICE_STATE_ACTIVE, @deviceCollection) ; eRender=0
If hr <> #S_OK
Debug "EnumAudioEndpoints() failed hr=" + Hex(hr)
Goto Cleanup
EndIf
deviceCollection\GetCount(@count)
If index < 0 Or index >= count
Debug "Invalid device index: " + Str(index)
Goto Cleanup
EndIf
hr = deviceCollection\Item(index, @device)
If hr <> #S_OK Or device=#Null
Debug "Item(" + Str(index) + ") failed, hr=" + Hex(hr)
Goto Cleanup
EndIf
hr = device\GetId(@deviceId)
If hr <> #S_OK
Debug "GetId() failed hr=" + Hex(hr)
Goto Cleanup
EndIf
name = GetFriendlyName(device)
Debug "Device ID to set as default: " + name
; Create IPolicyConfig
hr = CoCreateInstance_(?CLSID_CPolicyConfigClient, #Null, #CLSCTX_ALL, ?IID_IPolicyConfig, @policyConfig)
If hr <> #S_OK
Debug "CoCreateInstance(CPolicyConfigClient) failed hr=" + Hex(hr)
Goto Cleanup
EndIf
hr = policyConfig\SetDefaultEndpoint(deviceId, role)
If hr = #S_OK
Debug "Successfully set default device for role "+Str(role)
Else
Debug "SetDefaultEndpoint failed, hr="+Hex(hr)
EndIf
; Cleanup
Cleanup:
If policyConfig
policyConfig\Release()
EndIf
If device
device\Release()
EndIf
If deviceCollection
deviceCollection\Release()
EndIf
If deviceEnumerator
deviceEnumerator\Release()
EndIf
CoUninitialize_()
EndProcedure
; -----------------------------------------------------------------------------
; DataSection
; -----------------------------------------------------------------------------
DataSection
; CLSID_MMDeviceEnumerator {BCDE0395-E52F-467C-8E3D-C4579291692E}
CLSID_MMDeviceEnumerator:
Data.l $BCDE0395
Data.w $E52F,$467C
Data.b $8E,$3D,$C4,$57,$92,$91,$69,$2E
IID_IMMDeviceEnumerator:
Data.l $A95664D2
Data.w $9614,$4F35
Data.b $A7,$46,$DE,$8D,$B6,$36,$17,$E6
; class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9") CPolicyConfigClient;
CLSID_CPolicyConfigClient:
Data.l $870AF99C
Data.w $171D,$4F9E
Data.b $AF,$0D,$E6,$3D,$F4,$0C,$2B,$C9
; interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8") IPolicyConfig;
IID_IPolicyConfig:
Data.l $F8679F50
Data.w $850A,$41CF
Data.b $9C,$72,$43,$0F,$29,$02,$90,$C8
; PKEY_Device_FriendlyName = {A45C254E-DF1C-4EFD-8020-67D146A850E0}, PID=14
PKEY_Device_FriendlyName:
Data.l $A45C254E
Data.w $DF1C,$4EFD
Data.b $80,$20,$67,$D1,$46,$A8,$50,$E0
Data.l 14
EndDataSection
; -----------------------------------------------------------------------------
; Demo
; -----------------------------------------------------------------------------
Procedure Main()
Debug "=== Listing Devices with Friendly Names ==="
ListRenderDevices()
Debug "=== Set device with index 1 as default (Console) ==="
SetDefaultRenderDeviceByIndex(1, #ERole_Console)
; You can also do:
; SetDefaultRenderDeviceByIndex(1, #ERole_Multimedia)
; SetDefaultRenderDeviceByIndex(1, #ERole_Communications)
EndProcedure
Main()
End