|
|
|
@@ -7,6 +7,7 @@ package volume |
|
|
|
|
|
|
|
import ( |
|
|
|
"fmt" |
|
|
|
"runtime" |
|
|
|
"syscall" |
|
|
|
"unsafe" |
|
|
|
|
|
|
|
@@ -91,13 +92,26 @@ var ( |
|
|
|
|
|
|
|
// withEndpointVolume acquires an IAudioEndpointVolume for the default render |
|
|
|
// device, calls fn with its pointer, and releases all COM objects. |
|
|
|
// |
|
|
|
// COM apartments are thread-affine on Windows. LockOSThread pins this |
|
|
|
// goroutine to its current OS thread for the duration of the call so that |
|
|
|
// every COM call runs on the thread that initialized COM. |
|
|
|
func withEndpointVolume(fn func(vol uintptr) error) error { |
|
|
|
coInitializeEx.Call(0, 0) // COINIT_MULTITHREADED |
|
|
|
runtime.LockOSThread() |
|
|
|
defer runtime.UnlockOSThread() |
|
|
|
|
|
|
|
// CoInitializeEx returns S_OK (0) on first init, S_FALSE (1) if already |
|
|
|
// initialized on this thread. Both are success. Anything ≥ 2 is an error |
|
|
|
// (HRESULT error codes are 0x8xxxxxxx, always > 1 as unsigned). |
|
|
|
hr, _, _ := coInitializeEx.Call(0, 0) // COINIT_MULTITHREADED |
|
|
|
if hr > 1 { |
|
|
|
return fmt.Errorf("CoInitializeEx: HRESULT 0x%08X", hr) |
|
|
|
} |
|
|
|
defer coUninitialize.Call() |
|
|
|
|
|
|
|
// CoCreateInstance(CLSID_MMDeviceEnumerator) → IMMDeviceEnumerator |
|
|
|
var enumerator uintptr |
|
|
|
hr, _, _ := coCreateInstance.Call( |
|
|
|
hr, _, _ = coCreateInstance.Call( |
|
|
|
uintptr(unsafe.Pointer(&clsidMMDeviceEnumerator)), |
|
|
|
0, |
|
|
|
clsctxAll, |
|
|
|
|