//go:build windows package trayhost import ( "runtime" "unsafe" "golang.org/x/sys/windows" ) var ( shell32 = windows.NewLazySystemDLL("shell32.dll") user32 = windows.NewLazySystemDLL("user32.dll") procShellNotify = shell32.NewProc("Shell_NotifyIconW") procLoadIcon = user32.NewProc("LoadIconW") procCreatePopup = user32.NewProc("CreatePopupMenu") procAppendMenu = user32.NewProc("AppendMenuW") procTrackPopup = user32.NewProc("TrackPopupMenu") procDestroyMenu = user32.NewProc("DestroyMenu") procDefWindowProc = user32.NewProc("DefWindowProcW") procRegisterClass = user32.NewProc("RegisterClassExW") procCreateWindow = user32.NewProc("CreateWindowExW") procGetMessage = user32.NewProc("GetMessageW") procTranslate = user32.NewProc("TranslateMessage") procDispatch = user32.NewProc("DispatchMessageW") procPostQuit = user32.NewProc("PostQuitMessage") procSetForeground = user32.NewProc("SetForegroundWindow") procGetCursorPos = user32.NewProc("GetCursorPos") ) const ( nimAdd = 0x00000000 nimModify = 0x00000001 nimDelete = 0x00000002 nifMessage = 0x00000001 nifIcon = 0x00000002 nifTip = 0x00000004 wmApp = 0x8000 wmTray = wmApp + 1 wmDestroy = 0x0002 wmCommand = 0x0111 wmRButtonUp = 0x0205 wmLButtonUp = 0x0202 mfString = 0x00000000 mfSeparator = 0x00000800 tpmRight = 0x0020 tpmBottom = 0x0020 idiApplication = 32512 idConnect = 1001 idDisconnect = 1002 idQuit = 1003 ) type notifyIconData struct { CbSize uint32 Hwnd windows.Handle UID uint32 UFlags uint32 UCallbackMessage uint32 HIcon windows.Handle SzTip [128]uint16 } type wndClassEx struct { CbSize uint32 Style uint32 LpfnWndProc uintptr CbClsExtra int32 CbWndExtra int32 HInstance windows.Handle HIcon windows.Handle HCursor windows.Handle HbrBackground windows.Handle LpszMenuName *uint16 LpszClassName *uint16 HIconSm windows.Handle } type point struct{ X, Y int32 } type msg struct { Hwnd windows.Handle Message uint32 WParam uintptr LParam uintptr Time uint32 Pt point } func start(appName string, h Hooks) bool { if h.Quit == nil { return false } go func() { runtime.LockOSThread() defer runtime.UnlockOSThread() runTray(appName, h) }() return true } func runTray(appName string, h Hooks) { className, _ := windows.UTF16PtrFromString("NavisTrayClass") title, _ := windows.UTF16PtrFromString(appName) var wndProc = windows.NewCallback(func(hwnd windows.Handle, msgU uint32, wParam, lParam uintptr) uintptr { switch msgU { case wmTray: if lParam == wmRButtonUp || lParam == wmLButtonUp { showMenu(hwnd, h) } return 0 case wmCommand: switch int(wParam & 0xffff) { case idConnect: if h.Connect != nil { go h.Connect() } case idDisconnect: if h.Disconnect != nil { go h.Disconnect() } case idQuit: go h.Quit() } return 0 case wmDestroy: procPostQuit.Call(0) return 0 } r, _, _ := procDefWindowProc.Call(uintptr(hwnd), uintptr(msgU), wParam, lParam) return r }) wc := wndClassEx{ CbSize: uint32(unsafe.Sizeof(wndClassEx{})), LpfnWndProc: wndProc, LpszClassName: className, } procRegisterClass.Call(uintptr(unsafe.Pointer(&wc))) hwnd, _, _ := procCreateWindow.Call(0, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(title)), 0, 0, 0, 0, 0, 0, 0, 0, 0) if hwnd == 0 { return } icon, _, _ := procLoadIcon.Call(0, uintptr(idiApplication)) var nid notifyIconData nid.CbSize = uint32(unsafe.Sizeof(nid)) nid.Hwnd = windows.Handle(hwnd) nid.UID = 1 nid.UFlags = nifMessage | nifIcon | nifTip nid.UCallbackMessage = wmTray nid.HIcon = windows.Handle(icon) tip, _ := windows.UTF16FromString(appName) copy(nid.SzTip[:], tip) procShellNotify.Call(nimAdd, uintptr(unsafe.Pointer(&nid))) var m msg for { ret, _, _ := procGetMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0) if int32(ret) <= 0 { break } procTranslate.Call(uintptr(unsafe.Pointer(&m))) procDispatch.Call(uintptr(unsafe.Pointer(&m))) } procShellNotify.Call(nimDelete, uintptr(unsafe.Pointer(&nid))) } func showMenu(hwnd windows.Handle, h Hooks) { menu, _, _ := procCreatePopup.Call() if menu == 0 { return } defer procDestroyMenu.Call(menu) add := func(id int, text string) { p, _ := windows.UTF16PtrFromString(text) procAppendMenu.Call(menu, mfString, uintptr(id), uintptr(unsafe.Pointer(p))) } up := h.IsUp != nil && h.IsUp() if up { add(idDisconnect, "Отключить") } else { add(idConnect, "Подключить") } procAppendMenu.Call(menu, mfSeparator, 0, 0) add(idQuit, "Выход") var pt point procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt))) procSetForeground.Call(uintptr(hwnd)) procTrackPopup.Call(menu, tpmRight|tpmBottom, uintptr(pt.X), uintptr(pt.Y), 0, uintptr(hwnd), 0) }