//go:build windows package main import ( "fmt" "log" "os/exec" "syscall" "unsafe" ) const ( wmUser = 0x0400 wmTray = wmUser + 1 wmCommand = 0x0111 wmDestroy = 0x0002 wmClose = 0x0010 wmRButtonUp = 0x0205 wmLButtonUp = 0x0202 wmCreate = 0x0001 wmQuit = 0x0012 niAdd = 0x00000000 niModify = 0x00000001 niDelete = 0x00000002 nifMessage = 0x00000001 nifIcon = 0x00000002 nifTip = 0x00000004 nifInfo = 0x00000010 nimPopup = 0x00000000 miString = 0x00000000 miSeparator = 0x00000800 miDefault = 0x00001000 swHide = 0 idOpen = 1001 idExit = 1002 maxTip = 128 maxClassName = 64 ) type trayApp struct { uiURL string open func() } func newTray(uiURL string, open func()) *trayApp { return &trayApp{uiURL: uiURL, open: open} } func (t *trayApp) Run() error { return runTray(t.uiURL, t.open) } func openBrowser(url string) { _ = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() } func runTray(uiURL string, open func()) error { user32 := syscall.NewLazyDLL("user32.dll") shell32 := syscall.NewLazyDLL("shell32.dll") kernel32 := syscall.NewLazyDLL("kernel32.dll") registerClass := user32.NewProc("RegisterClassW") createWindowEx := user32.NewProc("CreateWindowExW") defWindowProc := user32.NewProc("DefWindowProcW") getMessage := user32.NewProc("GetMessageW") translateMessage := user32.NewProc("TranslateMessage") dispatchMessage := user32.NewProc("DispatchMessageW") postQuitMessage := user32.NewProc("PostQuitMessage") createPopupMenu := user32.NewProc("CreatePopupMenu") appendMenu := user32.NewProc("AppendMenuW") setForegroundWindow := user32.NewProc("SetForegroundWindow") trackPopupMenu := user32.NewProc("TrackPopupMenu") showWindow := user32.NewProc("ShowWindow") loadIcon := user32.NewProc("LoadIconW") shellNotifyIcon := shell32.NewProc("Shell_NotifyIconW") getModuleHandle := kernel32.NewProc("GetModuleHandleW") type wndClassEx struct { cbSize uint32 style uint32 lpfnWndProc uintptr cbClsExtra int32 cbWndExtra int32 hInstance uintptr hIcon uintptr hCursor uintptr hbrBackground uintptr lpszMenuName *uint16 lpszClassName *uint16 hIconSm uintptr } type point struct{ X, Y int32 } type msg struct { hwnd uintptr message uint32 wParam uintptr lParam uintptr time uint32 pt point } type notifyIconData struct { cbSize uint32 hWnd uintptr uID uint32 uFlags uint32 uCallbackMessage uint32 hIcon uintptr szTip [maxTip]uint16 dwState uint32 dwStateMask uint32 szInfo [256]uint16 uTimeoutVersion uint32 szInfoTitle [64]uint16 dwInfoFlags uint32 guidItem [16]byte hBalloonIcon uintptr } className, _ := syscall.UTF16PtrFromString("VRC_OSC_TRAY") windowTitle, _ := syscall.UTF16PtrFromString("VRC OSC Tray") icon, _, _ := loadIcon.Call(0, 32512) // IDI_APPLICATION var hwnd uintptr var menu uintptr var nid notifyIconData wndProc := syscall.NewCallback(func(hwnd uintptr, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case wmCreate: return 0 case wmTray: switch lParam { case wmLButtonUp: if open != nil { open() } case wmRButtonUp: if menu == 0 { m, _, _ := createPopupMenu.Call() menu = m openText, _ := syscall.UTF16PtrFromString("Open UI") exitText, _ := syscall.UTF16PtrFromString("Exit") appendMenu.Call(menu, miString|miDefault, idOpen, uintptr(unsafe.Pointer(openText))) appendMenu.Call(menu, miString, idExit, uintptr(unsafe.Pointer(exitText))) } setForegroundWindow.Call(hwnd) var p point user32.NewProc("GetCursorPos").Call(uintptr(unsafe.Pointer(&p))) trackPopupMenu.Call(menu, nimPopup, uintptr(p.X), uintptr(p.Y), 0, hwnd, 0) } return 0 case wmCommand: switch uint16(wParam & 0xffff) { case idOpen: if open != nil { open() } case idExit: user32.NewProc("DestroyWindow").Call(hwnd) } return 0 case wmDestroy: nid.cbSize = uint32(unsafe.Sizeof(nid)) nid.hWnd = hwnd nid.uID = 1 shellNotifyIcon.Call(niDelete, uintptr(unsafe.Pointer(&nid))) postQuitMessage.Call(0) return 0 } ret, _, _ := defWindowProc.Call(hwnd, uintptr(msg), wParam, lParam) return ret }) hInstance, _, _ := getModuleHandle.Call(0) _, _, regErr := registerClass.Call(uintptr(unsafe.Pointer(&wndClassEx{ cbSize: uint32(unsafe.Sizeof(wndClassEx{})), lpfnWndProc: wndProc, hInstance: hInstance, hIcon: icon, lpszClassName: className, }))) if regErr != nil { return fmt.Errorf("RegisterClassW failed: %v", regErr) } hwnd, _, err := createWindowEx.Call(0, uintptr(unsafe.Pointer(className)), uintptr(unsafe.Pointer(windowTitle)), 0, 0, 0, 0, 0, 0, 0, hInstance, 0) if hwnd == 0 { return fmt.Errorf("CreateWindowExW failed: %v", err) } showWindow.Call(hwnd, swHide) nid.cbSize = uint32(unsafe.Sizeof(nid)) nid.hWnd = hwnd nid.uID = 1 nid.uFlags = nifMessage | nifIcon | nifTip nid.uCallbackMessage = wmTray nid.hIcon = icon tip := syscall.StringToUTF16("VRC OSC") copy(nid.szTip[:], tip) if ok, _, _ := shellNotifyIcon.Call(niAdd, uintptr(unsafe.Pointer(&nid))); ok == 0 { return fmt.Errorf("Shell_NotifyIconW add failed") } log.Printf("tray started") var m msg for { r, _, _ := getMessage.Call(uintptr(unsafe.Pointer(&m)), 0, 0, 0) if int32(r) <= 0 { break } translateMessage.Call(uintptr(unsafe.Pointer(&m))) dispatchMessage.Call(uintptr(unsafe.Pointer(&m))) } return nil }