eventloop.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //go:build windows
  2. package wintray
  3. import (
  4. "fmt"
  5. "log/slog"
  6. "sync"
  7. "unsafe"
  8. "golang.org/x/sys/windows"
  9. )
  10. var (
  11. quitOnce sync.Once
  12. )
  13. func (t *winTray) Run() {
  14. nativeLoop()
  15. }
  16. func nativeLoop() {
  17. // Main message pump.
  18. slog.Debug("starting event handling loop")
  19. m := &struct {
  20. WindowHandle windows.Handle
  21. Message uint32
  22. Wparam uintptr
  23. Lparam uintptr
  24. Time uint32
  25. Pt point
  26. LPrivate uint32
  27. }{}
  28. for {
  29. ret, _, err := pGetMessage.Call(uintptr(unsafe.Pointer(m)), 0, 0, 0)
  30. // If the function retrieves a message other than WM_QUIT, the return value is nonzero.
  31. // If the function retrieves the WM_QUIT message, the return value is zero.
  32. // If there is an error, the return value is -1
  33. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936(v=vs.85).aspx
  34. switch int32(ret) {
  35. case -1:
  36. slog.Error(fmt.Sprintf("get message failure: %v", err))
  37. return
  38. case 0:
  39. return
  40. default:
  41. // slog.Debug(fmt.Sprintf("XXX dispatching message from run loop 0x%x", m.Message))
  42. pTranslateMessage.Call(uintptr(unsafe.Pointer(m))) //nolint:errcheck
  43. pDispatchMessage.Call(uintptr(unsafe.Pointer(m))) //nolint:errcheck
  44. }
  45. }
  46. }
  47. // WindowProc callback function that processes messages sent to a window.
  48. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633573(v=vs.85).aspx
  49. func (t *winTray) wndProc(hWnd windows.Handle, message uint32, wParam, lParam uintptr) (lResult uintptr) {
  50. const (
  51. WM_RBUTTONUP = 0x0205
  52. WM_LBUTTONUP = 0x0202
  53. WM_COMMAND = 0x0111
  54. WM_ENDSESSION = 0x0016
  55. WM_CLOSE = 0x0010
  56. WM_DESTROY = 0x0002
  57. WM_MOUSEMOVE = 0x0200
  58. WM_LBUTTONDOWN = 0x0201
  59. )
  60. // slog.Debug(fmt.Sprintf("XXX in wndProc: 0x%x", message))
  61. switch message {
  62. case WM_COMMAND:
  63. menuItemId := int32(wParam)
  64. // slog.Debug(fmt.Sprintf("XXX Menu Click: %d", menuItemId))
  65. // https://docs.microsoft.com/en-us/windows/win32/menurc/wm-command#menus
  66. switch menuItemId {
  67. case quitMenuID:
  68. select {
  69. case t.callbacks.Quit <- struct{}{}:
  70. // should not happen but in case not listening
  71. default:
  72. slog.Error("no listener on Quit")
  73. }
  74. case updateMenuID:
  75. select {
  76. case t.callbacks.Update <- struct{}{}:
  77. // should not happen but in case not listening
  78. default:
  79. slog.Error("no listener on Update")
  80. }
  81. case diagLogsMenuID:
  82. select {
  83. case t.callbacks.ShowLogs <- struct{}{}:
  84. // should not happen but in case not listening
  85. default:
  86. slog.Error("no listener on ShowLogs")
  87. }
  88. default:
  89. slog.Debug(fmt.Sprintf("Unexpected menu item id: %d", menuItemId))
  90. }
  91. case WM_CLOSE:
  92. boolRet, _, err := pDestroyWindow.Call(uintptr(t.window))
  93. if boolRet == 0 {
  94. slog.Error(fmt.Sprintf("failed to destroy window: %s", err))
  95. }
  96. err = t.wcex.unregister()
  97. if err != nil {
  98. slog.Error(fmt.Sprintf("failed to uregister windo %s", err))
  99. }
  100. case WM_DESTROY:
  101. // same as WM_ENDSESSION, but throws 0 exit code after all
  102. defer pPostQuitMessage.Call(uintptr(int32(0))) //nolint:errcheck
  103. fallthrough
  104. case WM_ENDSESSION:
  105. t.muNID.Lock()
  106. if t.nid != nil {
  107. err := t.nid.delete()
  108. if err != nil {
  109. slog.Error(fmt.Sprintf("failed to delete nid: %s", err))
  110. }
  111. }
  112. t.muNID.Unlock()
  113. case t.wmSystrayMessage:
  114. switch lParam {
  115. case WM_MOUSEMOVE, WM_LBUTTONDOWN:
  116. // Ignore these...
  117. case WM_RBUTTONUP, WM_LBUTTONUP:
  118. err := t.showMenu()
  119. if err != nil {
  120. slog.Error(fmt.Sprintf("failed to show menu: %s", err))
  121. }
  122. case 0x405: // TODO - how is this magic value derived for the notification left click
  123. if t.pendingUpdate {
  124. select {
  125. case t.callbacks.Update <- struct{}{}:
  126. // should not happen but in case not listening
  127. default:
  128. slog.Error("no listener on Update")
  129. }
  130. } else {
  131. select {
  132. case t.callbacks.DoFirstUse <- struct{}{}:
  133. // should not happen but in case not listening
  134. default:
  135. slog.Error("no listener on DoFirstUse")
  136. }
  137. }
  138. case 0x404: // Middle click or close notification
  139. // slog.Debug("doing nothing on close of first time notification")
  140. default:
  141. // 0x402 also seems common - what is it?
  142. slog.Debug(fmt.Sprintf("unmanaged app message, lParm: 0x%x", lParam))
  143. }
  144. case t.wmTaskbarCreated: // on explorer.exe restarts
  145. slog.Debug("XXX got taskbar created event")
  146. t.muNID.Lock()
  147. err := t.nid.add()
  148. if err != nil {
  149. slog.Error(fmt.Sprintf("failed to refresh the taskbar on explorer restart: %s", err))
  150. }
  151. t.muNID.Unlock()
  152. default:
  153. // Calls the default window procedure to provide default processing for any window messages that an application does not process.
  154. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms633572(v=vs.85).aspx
  155. // slog.Debug(fmt.Sprintf("XXX default wndProc handler 0x%x", message))
  156. lResult, _, _ = pDefWindowProc.Call(
  157. uintptr(hWnd),
  158. uintptr(message),
  159. uintptr(wParam),
  160. uintptr(lParam),
  161. )
  162. }
  163. return
  164. }
  165. func (t *winTray) Quit() {
  166. quitOnce.Do(quit)
  167. }
  168. func quit() {
  169. boolRet, _, err := pPostMessage.Call(
  170. uintptr(wt.window),
  171. WM_CLOSE,
  172. 0,
  173. 0,
  174. )
  175. if boolRet == 0 {
  176. slog.Error(fmt.Sprintf("failed to post close message on shutdown %s", err))
  177. }
  178. }