Web-based Winamp controller for CarPC � Go backend, mobile-first UI
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

240 Zeilen
7.5KB

  1. //go:build windows
  2. // Package volume controls the Windows system master volume via the
  3. // Core Audio API (IAudioEndpointVolume COM interface).
  4. // This is the modern replacement for the deprecated MMSystem mixer API.
  5. package volume
  6. import (
  7. "fmt"
  8. "syscall"
  9. "unsafe"
  10. "golang.org/x/sys/windows"
  11. )
  12. // ── COM GUIDs ─────────────────────────────────────────────────────────────────
  13. var (
  14. clsidMMDeviceEnumerator = windows.GUID{
  15. Data1: 0xBCDE0395, Data2: 0xE52F, Data3: 0x467C,
  16. Data4: [8]byte{0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E},
  17. }
  18. iidIMMDeviceEnumerator = windows.GUID{
  19. Data1: 0xA95664D2, Data2: 0x9614, Data3: 0x4F35,
  20. Data4: [8]byte{0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6},
  21. }
  22. iidIAudioEndpointVolume = windows.GUID{
  23. Data1: 0x5CDF2C82, Data2: 0x841E, Data3: 0x4546,
  24. Data4: [8]byte{0x97, 0x22, 0x0C, 0xF7, 0x40, 0x78, 0x22, 0x9A},
  25. }
  26. )
  27. const (
  28. eRender uintptr = 0
  29. eConsole uintptr = 0
  30. clsctxAll uintptr = 0x17
  31. )
  32. // ── Helper: call a COM vtable method ─────────────────────────────────────────
  33. // vtbl is a pointer to the vtable struct (first field of the COM object).
  34. // idx is the method index (0 = QueryInterface, 1 = AddRef, 2 = Release, …).
  35. func comCall3(vtbl uintptr, idx int, a1, a2, a3 uintptr) (uintptr, error) {
  36. proc := *(*uintptr)(unsafe.Pointer(vtbl + uintptr(idx)*unsafe.Sizeof(uintptr(0))))
  37. r, _, _ := syscall.Syscall6(proc, 4, a1, a2, a3, 0, 0, 0)
  38. if r != 0 {
  39. return 0, fmt.Errorf("COM call [%d]: HRESULT 0x%08X", idx, r)
  40. }
  41. return r, nil
  42. }
  43. func comCall4(vtbl uintptr, idx int, a1, a2, a3, a4 uintptr) (uintptr, error) {
  44. proc := *(*uintptr)(unsafe.Pointer(vtbl + uintptr(idx)*unsafe.Sizeof(uintptr(0))))
  45. r, _, _ := syscall.Syscall6(proc, 5, a1, a2, a3, a4, 0, 0)
  46. if r != 0 {
  47. return 0, fmt.Errorf("COM call [%d]: HRESULT 0x%08X", idx, r)
  48. }
  49. return r, nil
  50. }
  51. func comCall5(vtbl uintptr, idx int, a1, a2, a3, a4, a5 uintptr) (uintptr, error) {
  52. proc := *(*uintptr)(unsafe.Pointer(vtbl + uintptr(idx)*unsafe.Sizeof(uintptr(0))))
  53. r, _, _ := syscall.Syscall6(proc, 6, a1, a2, a3, a4, a5, 0)
  54. if r != 0 {
  55. return 0, fmt.Errorf("COM call [%d]: HRESULT 0x%08X", idx, r)
  56. }
  57. return r, nil
  58. }
  59. // vtblOf returns the vtable pointer for a COM object pointer.
  60. func vtblOf(p uintptr) uintptr {
  61. return *(*uintptr)(unsafe.Pointer(p))
  62. }
  63. // release calls IUnknown::Release (vtable index 2).
  64. func release(p uintptr) {
  65. proc := *(*uintptr)(unsafe.Pointer(vtblOf(p) + 2*unsafe.Sizeof(uintptr(0))))
  66. syscall.Syscall(proc, 1, p, 0, 0)
  67. }
  68. // ── DLL procedures ────────────────────────────────────────────────────────────
  69. var (
  70. ole32 = windows.NewLazySystemDLL("ole32.dll")
  71. coInitializeEx = ole32.NewProc("CoInitializeEx")
  72. coUninitialize = ole32.NewProc("CoUninitialize")
  73. coCreateInstance = ole32.NewProc("CoCreateInstance")
  74. )
  75. // ── withEndpointVolume ────────────────────────────────────────────────────────
  76. // withEndpointVolume acquires an IAudioEndpointVolume for the default render
  77. // device, calls fn with its pointer, and releases all COM objects.
  78. func withEndpointVolume(fn func(vol uintptr) error) error {
  79. coInitializeEx.Call(0, 0) // COINIT_MULTITHREADED
  80. defer coUninitialize.Call()
  81. // CoCreateInstance(CLSID_MMDeviceEnumerator) → IMMDeviceEnumerator
  82. var enumerator uintptr
  83. hr, _, _ := coCreateInstance.Call(
  84. uintptr(unsafe.Pointer(&clsidMMDeviceEnumerator)),
  85. 0,
  86. clsctxAll,
  87. uintptr(unsafe.Pointer(&iidIMMDeviceEnumerator)),
  88. uintptr(unsafe.Pointer(&enumerator)),
  89. )
  90. if hr != 0 {
  91. return fmt.Errorf("CoCreateInstance: HRESULT 0x%08X", hr)
  92. }
  93. defer release(enumerator)
  94. // IMMDeviceEnumerator::GetDefaultAudioEndpoint (vtable index 4)
  95. // (0=QI, 1=AddRef, 2=Release, 3=EnumAudioEndpoints, 4=GetDefaultAudioEndpoint)
  96. var device uintptr
  97. if _, err := comCall5(vtblOf(enumerator), 4,
  98. enumerator, eRender, eConsole,
  99. uintptr(unsafe.Pointer(&device)), 0); err != nil {
  100. return fmt.Errorf("GetDefaultAudioEndpoint: %w", err)
  101. }
  102. defer release(device)
  103. // IMMDevice::Activate(IID_IAudioEndpointVolume, CLSCTX_ALL, nil, &vol)
  104. // (0=QI, 1=AddRef, 2=Release, 3=Activate, …)
  105. var vol uintptr
  106. if _, err := comCall5(vtblOf(device), 3,
  107. device,
  108. uintptr(unsafe.Pointer(&iidIAudioEndpointVolume)),
  109. clsctxAll,
  110. 0,
  111. uintptr(unsafe.Pointer(&vol))); err != nil {
  112. return fmt.Errorf("IMMDevice::Activate: %w", err)
  113. }
  114. defer release(vol)
  115. return fn(vol)
  116. }
  117. // ── IAudioEndpointVolume vtable indices ───────────────────────────────────────
  118. //
  119. // 0 QueryInterface
  120. // 1 AddRef
  121. // 2 Release
  122. // 3 RegisterControlChangeNotify
  123. // 4 UnregisterControlChangeNotify
  124. // 5 GetChannelCount
  125. // 6 SetMasterVolumeLevel
  126. // 7 SetMasterVolumeLevelScalar
  127. // 8 GetMasterVolumeLevel
  128. // 9 GetMasterVolumeLevelScalar
  129. // 14 SetMute
  130. // 15 GetMute
  131. const (
  132. idxSetMasterScalar = 7
  133. idxGetMasterScalar = 9
  134. idxSetMute = 14
  135. idxGetMute = 15
  136. )
  137. // ── Public API ────────────────────────────────────────────────────────────────
  138. // Get returns the current Windows master volume as a percentage (0–100).
  139. func Get() (int, error) {
  140. var pct int
  141. err := withEndpointVolume(func(vol uintptr) error {
  142. var level float32
  143. proc := *(*uintptr)(unsafe.Pointer(
  144. vtblOf(vol) + idxGetMasterScalar*unsafe.Sizeof(uintptr(0)),
  145. ))
  146. r, _, _ := syscall.Syscall(proc, 2, vol, uintptr(unsafe.Pointer(&level)), 0)
  147. if r != 0 {
  148. return fmt.Errorf("GetMasterVolumeLevelScalar: 0x%08X", r)
  149. }
  150. pct = int(level * 100)
  151. return nil
  152. })
  153. return pct, err
  154. }
  155. // Set sets the Windows master volume to pct percent (0–100).
  156. func Set(pct int) error {
  157. if pct < 0 {
  158. pct = 0
  159. }
  160. if pct > 100 {
  161. pct = 100
  162. }
  163. level := float32(pct) / 100.0
  164. return withEndpointVolume(func(vol uintptr) error {
  165. var nullGUID windows.GUID
  166. proc := *(*uintptr)(unsafe.Pointer(
  167. vtblOf(vol) + idxSetMasterScalar*unsafe.Sizeof(uintptr(0)),
  168. ))
  169. // float32 must be widened to uintptr via bit-reinterpretation
  170. bits := *(*uint32)(unsafe.Pointer(&level))
  171. r, _, _ := syscall.Syscall(proc, 3, vol, uintptr(bits), uintptr(unsafe.Pointer(&nullGUID)))
  172. if r != 0 {
  173. return fmt.Errorf("SetMasterVolumeLevelScalar: 0x%08X", r)
  174. }
  175. return nil
  176. })
  177. }
  178. // GetMute reports whether the system audio is currently muted.
  179. func GetMute() (bool, error) {
  180. var muted bool
  181. err := withEndpointVolume(func(vol uintptr) error {
  182. var val int32
  183. proc := *(*uintptr)(unsafe.Pointer(
  184. vtblOf(vol) + idxGetMute*unsafe.Sizeof(uintptr(0)),
  185. ))
  186. r, _, _ := syscall.Syscall(proc, 2, vol, uintptr(unsafe.Pointer(&val)), 0)
  187. if r != 0 {
  188. return fmt.Errorf("GetMute: 0x%08X", r)
  189. }
  190. muted = val != 0
  191. return nil
  192. })
  193. return muted, err
  194. }
  195. // SetMute mutes or unmutes the system audio.
  196. func SetMute(muted bool) error {
  197. return withEndpointVolume(func(vol uintptr) error {
  198. var nullGUID windows.GUID
  199. val := uintptr(0)
  200. if muted {
  201. val = 1
  202. }
  203. proc := *(*uintptr)(unsafe.Pointer(
  204. vtblOf(vol) + idxSetMute*unsafe.Sizeof(uintptr(0)),
  205. ))
  206. r, _, _ := syscall.Syscall(proc, 3, vol, val, uintptr(unsafe.Pointer(&nullGUID)))
  207. if r != 0 {
  208. return fmt.Errorf("SetMute: 0x%08X", r)
  209. }
  210. return nil
  211. })
  212. }