Clear sysproxy on core death; probe+reconnect; subscription prune; Best ignores soft UDP; Windows tray; Android protocol gate; CI workflows. Co-authored-by: Cursor <cursoragent@cursor.com>
194 lines
4.9 KiB
Go
194 lines
4.9 KiB
Go
//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)
|
|
} |