217 lines
5.6 KiB
Go
217 lines
5.6 KiB
Go
//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
|
|
}
|