Web-based Winamp controller for CarPC � Go backend, mobile-first UI
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

223 lignes
5.3KB

  1. //go:build windows
  2. // Package winamp provides IPC control of a running Winamp instance via
  3. // Windows messages (WM_COMMAND / WM_USER), mirroring the TWinampControl
  4. // Delphi component by SpECTre.
  5. package winamp
  6. import (
  7. "fmt"
  8. "strings"
  9. "syscall"
  10. "unsafe"
  11. )
  12. var (
  13. user32 = syscall.NewLazyDLL("user32.dll")
  14. findWindow = user32.NewProc("FindWindowW")
  15. sendMessage = user32.NewProc("SendMessageW")
  16. getWindowTextW = user32.NewProc("GetWindowTextW")
  17. )
  18. const (
  19. wmCommand = 0x0111
  20. wmUser = 0x0400
  21. // Winamp WM_COMMAND IDs
  22. cmdPrevTrack = 40044
  23. cmdPlay = 40045
  24. cmdPause = 40046
  25. cmdStop = 40047
  26. cmdNextTrack = 40048
  27. cmdFadeStop = 40147
  28. cmdStopAfter = 40157
  29. cmdToggleRepeat = 40022
  30. cmdToggleShuffle = 40023
  31. cmdClose = 40001
  32. cmdVolumeUp = 40058
  33. cmdVolumeDown = 40059
  34. // Winamp WM_USER lParam IDs
  35. userGetVersion = 0
  36. userGetPlayState = 104
  37. userGetPosition = 105
  38. userSeek = 106
  39. userSetVolume = 122
  40. userGetPlaylistPos = 125
  41. userGetPlaylistLen = 124
  42. userRestart = 135
  43. )
  44. // Controller talks to a running Winamp instance.
  45. type Controller struct{}
  46. func New() *Controller { return &Controller{} }
  47. func (c *Controller) handle() syscall.Handle {
  48. winampClass, _ := syscall.UTF16PtrFromString("Winamp v1.x")
  49. h, _, _ := findWindow.Call(
  50. uintptr(unsafe.Pointer(winampClass)),
  51. 0,
  52. )
  53. return syscall.Handle(h)
  54. }
  55. func (c *Controller) IsRunning() bool {
  56. return c.handle() != 0
  57. }
  58. func send(h syscall.Handle, msg, wparam, lparam uintptr) uintptr {
  59. r, _, _ := sendMessage.Call(uintptr(h), msg, wparam, lparam)
  60. return r
  61. }
  62. func (c *Controller) cmd(id uintptr) bool {
  63. h := c.handle()
  64. if h == 0 {
  65. return false
  66. }
  67. send(h, wmCommand, id, 0)
  68. return true
  69. }
  70. func (c *Controller) user(wparam, lparam uintptr) (uintptr, bool) {
  71. h := c.handle()
  72. if h == 0 {
  73. return 0, false
  74. }
  75. return send(h, wmUser, wparam, lparam), true
  76. }
  77. // Playback controls
  78. func (c *Controller) Play() bool { return c.cmd(cmdPlay) }
  79. func (c *Controller) Pause() bool { return c.cmd(cmdPause) }
  80. func (c *Controller) Stop() bool { return c.cmd(cmdStop) }
  81. func (c *Controller) NextTrack() bool { return c.cmd(cmdNextTrack) }
  82. func (c *Controller) PrevTrack() bool { return c.cmd(cmdPrevTrack) }
  83. func (c *Controller) Close() bool { return c.cmd(cmdClose) }
  84. // State: 0=stopped, 1=playing, 3=paused
  85. func (c *Controller) PlayState() int {
  86. v, ok := c.user(0, userGetPlayState)
  87. if !ok {
  88. return 0
  89. }
  90. return int(v)
  91. }
  92. func (c *Controller) IsPlaying() bool { return c.PlayState() == 1 }
  93. func (c *Controller) IsPaused() bool { return c.PlayState() == 3 }
  94. func (c *Controller) IsStopped() bool { return c.PlayState() == 0 }
  95. // GetPosition returns current playback offset in seconds.
  96. // Returns 0 when stopped (Winamp returns 0xFFFFFFFF in that state).
  97. func (c *Controller) GetPosition() int {
  98. v, ok := c.user(0, userGetPosition)
  99. if !ok || v > 0xF0000000 { // 0xFFFFFFFF = stopped/no track
  100. return 0
  101. }
  102. return int(v) / 1000
  103. }
  104. // GetLength returns total track length in seconds.
  105. func (c *Controller) GetLength() int {
  106. v, ok := c.user(1, userGetPosition)
  107. if !ok {
  108. return 0
  109. }
  110. return int(v)
  111. }
  112. // Seek sets the playback position to offsetSeconds.
  113. func (c *Controller) Seek(offsetSeconds int) bool {
  114. h := c.handle()
  115. if h == 0 {
  116. return false
  117. }
  118. send(h, wmUser, uintptr(offsetSeconds*1000), userSeek)
  119. return true
  120. }
  121. // SetVolume sets Winamp's internal volume (0–255).
  122. func (c *Controller) SetVolume(v int) bool {
  123. if v < 0 {
  124. v = 0
  125. }
  126. if v > 255 {
  127. v = 255
  128. }
  129. h := c.handle()
  130. if h == 0 {
  131. return false
  132. }
  133. send(h, wmUser, uintptr(v), userSetVolume)
  134. return true
  135. }
  136. // GetPlaylistPosition returns the 1-based current playlist index.
  137. func (c *Controller) GetPlaylistPosition() int {
  138. v, ok := c.user(0, userGetPlaylistPos)
  139. if !ok {
  140. return 0
  141. }
  142. return int(v) + 1
  143. }
  144. // GetPlaylistLength returns the total number of tracks in the playlist.
  145. func (c *Controller) GetPlaylistLength() int {
  146. v, ok := c.user(0, userGetPlaylistLen)
  147. if !ok {
  148. return 0
  149. }
  150. return int(v)
  151. }
  152. // GetVersion returns a human-readable Winamp version string (e.g. "5.66").
  153. func (c *Controller) GetVersion() string {
  154. v, ok := c.user(0, userGetVersion)
  155. if !ok {
  156. return ""
  157. }
  158. hex := fmt.Sprintf("%04X", v)
  159. if len(hex) < 3 {
  160. return ""
  161. }
  162. return string(hex[0]) + "." + hex[1:3]
  163. }
  164. // GetTitle returns the title of the currently playing track by reading
  165. // the Winamp window title.
  166. //
  167. // Winamp 5.x formats the window title as one of:
  168. //
  169. // "N. Artist - Title - Winamp" (playing)
  170. // "Winamp" (stopped, no playlist)
  171. //
  172. // We strip the " - Winamp" suffix and the leading "N. " playlist prefix.
  173. func (c *Controller) GetTitle() string {
  174. h := c.handle()
  175. if h == 0 {
  176. return ""
  177. }
  178. buf := make([]uint16, 512)
  179. getWindowTextW.Call(uintptr(h), uintptr(unsafe.Pointer(&buf[0])), 512)
  180. title := syscall.UTF16ToString(buf)
  181. // Strip " - Winamp" suffix (use last occurrence so track titles
  182. // containing " - Winamp" are handled correctly).
  183. const suffix = " - Winamp"
  184. if idx := strings.LastIndex(title, suffix); idx >= 0 {
  185. title = title[:idx]
  186. } else {
  187. // Title is just "Winamp" (stopped, empty playlist).
  188. return ""
  189. }
  190. // Strip leading playlist-number prefix: digits followed by ". "
  191. // e.g. "4. " or "12. "
  192. if dot := strings.Index(title, ". "); dot >= 0 && dot <= 4 {
  193. title = title[dot+2:]
  194. }
  195. return title
  196. }