I believe I've solved this (Windows hasn't shuffled the positions of the list entries yet, but I can see the mechanism working).
The key was finding
ControlGet, OutputVar, List, Col1, SysListView321, A ; get column 1, all rows
Which actually does retrieve a list of all the playback devices. I had tried to use
ControlGet, OutputVar, FindString, "Speakers / HP", SysListView321, A
but it returns position 1 and no error no matter what I search for. I didn't manage to figure out what it really does, so I tried alternatives. This steps through all of the "List" items found by ControlGet, and lets me react to each one if appropriate:
Loop, Parse, OutputVar, `n ; Rows are delimited by linefeeds (`n)
There are lots of msgbox debugging steps commented out of this code. If you want to see the mechanism work you can restore some of them. The "sleep, 500" delays are so I can watch the action graphically while the code runs.
#+A::
Run,mmsys.cpl
WinWait,Sound
IfWinNotActive,Sound WinActivate,Sound
WinWaitActive,Sound
ControlSend,SysListView321,{PgUp} ; Click the first item so we know where we are
Sleep, 500
SpdifAvail := 0
SpdifSteps := 0
SpeakerAvail := 0
SpeakerSteps := 0
; Following works for testing, commented out for normal use
;ControlGet, count, List, count, SysListView321, A
;msgbox lines = %count%
; Following returns position 1, error 0, regardless of string searched:
; ControlGet, OutputVar, FindString, "Speakers / HP", SysListView321, A
; msgbox "Speakers / HP" position is %outputvar%
; msgbox "Speakers / HP" error is %ErrorLevel%
ControlGet, OutputVar, List, Col1, SysListView321, A ; get column 1, all rows
; Following several msgboxes for testing, commented out for normal use
;msgbox %outputvar%
Loop, Parse, OutputVar, `n ; Rows are delimited by linefeeds (`n)
{
RowNumber := A_Index
Loop, Parse, A_LoopField, %A_Tab% ; Fields (columns) in each row are delimited by tabs (A_Tab)
{
; MsgBox Row #%RowNumber% Col #%A_Index% is %A_LoopField%.
if InStr(A_LoopField, "Speakers") ; save Speakers position
{
; MsgBox, found: Speakers(Steps) row: %RowNumber% ; Show selection
SpeakerAvail := 1
SpeakerSteps := RowNumber -1
; MsgBox, "SpeakerSteps" = %SpeakerSteps%
}
if InStr(A_LoopField, "S/PDIF") ; use S/PDIF if available
{
; MsgBox, found: S/PDIF(Steps) row: %RowNumber% ; Show selection
SpdifAvail := 1
SpdifSteps := RowNumber -1
; MsgBox, "SpdifSteps" = %SpdifSteps%
}
}
Sleep, 50
}
if SpdifAvail
{
Loop, %SpdifSteps%
{
; MsgBox, SpdifStep %A_Index%
ControlSend,SysListView321,{Down} ; click down to SPDIF0
Sleep, 50
}
Sleep, 500
ControlClick,&Properties
Send, ^{Tab} ; S/PDIF has four tabs, we want the last one
Sleep, 50
Send, ^{Tab}
Sleep, 50
Send, ^{Tab}
Sleep, 500 ; now on Advanced tab
ControlClick,&Default Format
Send, {PgUp} ; choose first item in list, 16,44
Sleep, 500
Send, {Enter}
Sleep, 500
ControlClick,&OK
WinWaitActive,Sound ; back on Playback tab
; S/PDIF shows as active but isn't - must re-select it
; setting an unavailable Bluetooth device doesn't work; move to speakers:
Sleep, 500
ControlSend,SysListView321,{PgUp} ; Click the first item so we know where we are
Loop, %SpeakerSteps%
{
ControlSend,SysListView321,{Down} ; click down to Speakers
Sleep, 50
}
Sleep, 500
ControlSend,SysListView321,{PgUp} ; Click the first item so we know where we are
Loop, %SpdifSteps%
{
ControlSend,SysListView321,{Down} ; click down to SPDIF0
Sleep, 50
}
ControlClick,&Set Default
Sleep, 500
}
else if SpeakerAvail ; use Speakers if available and S/PDIF is not available
{
MsgBox, found: Speakers(NoSpdif) row: %RowNumber% ; Show selection
Loop, %SpeakerSteps%
{
ControlSend,SysListView321,{Down} ; click down to Speakers
Sleep, 50
}
ControlClick,&Set Default
Sleep, 500
}
; ControlClick,&OK ; doesn't work...
Send, {Tab} ; make Configure active
Send, {Tab} ; make Properties active
Send, {Tab} ; make OK active
Send {Enter}
return
Hopefully this will work no matter how Windows re-arranges the list items.