//go:build windows package input import ( "runtime" "sync" "unsafe" "golang.org/x/sys/windows" ) var ( gdi32 = windows.NewLazySystemDLL("gdi32.dll") procCreateSolidBrush = gdi32.NewProc("CreateSolidBrush") procGdiDeleteObject = gdi32.NewProc("DeleteObject") procGetStockObject = gdi32.NewProc("GetStockObject") procGdiSelectObject = gdi32.NewProc("SelectObject") procSetBkMode = gdi32.NewProc("SetBkMode") procSetTextColor = gdi32.NewProc("SetTextColor") procBeginPaint = user32.NewProc("BeginPaint") procEndPaint = user32.NewProc("EndPaint") procGetClientRect = user32.NewProc("GetClientRect") procFillRect = user32.NewProc("FillRect") procDrawTextW = user32.NewProc("DrawTextW") procRegisterClassExW = user32.NewProc("RegisterClassExW") procDefWindowProcW = user32.NewProc("DefWindowProcW") procUpdateWindow = user32.NewProc("UpdateWindow") procShowWindowFn = user32.NewProc("ShowWindow") procTranslateMessage = user32.NewProc("TranslateMessage") procDispatchMessageW = user32.NewProc("DispatchMessageW") ) // WNDCLASSEXW layout: 80 bytes on 64-bit type WNDCLASSEXW 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 } // PAINTSTRUCT layout: 72 bytes on 64-bit (includes trailing padding) type PAINTSTRUCT struct { Hdc uintptr FErase int32 RcPaint RECT FRestore int32 FIncUpdate int32 RgbReserved [32]byte } var ( overlayOnce sync.Once overlayCallback uintptr // prevent GC of callback ) // OverlayWindow é uma janela Win32 nativa independente do Wails. // Exibe indicador neon "KVMote" no canto da tela durante modo cliente. // Não interfere com WebView2, Raw Input, nem coordenadas do Wails. type OverlayWindow struct { hwnd uintptr ready chan struct{} } func NewOverlayWindow() *OverlayWindow { ov := &OverlayWindow{ready: make(chan struct{})} go ov.run() <-ov.ready return ov } func (ov *OverlayWindow) run() { runtime.LockOSThread() className, _ := windows.UTF16PtrFromString("KVMoteOverlay") overlayOnce.Do(func() { cb := windows.NewCallback(func(hwnd, msg, wParam, lParam uintptr) uintptr { const ( wmPaint = 0x000F wmEraseBkgnd = 0x0014 dtCenter = 0x00000001 dtVCenter = 0x00000004 dtSingleLine = 0x00000020 transparent = 1 neonMagenta = 0x00FF00FF // #FF00FF em COLORREF (0x00BBGGRR) defaultFont = 17 // DEFAULT_GUI_FONT ) switch msg { case wmEraseBkgnd: return 1 case wmPaint: var ps PAINTSTRUCT hdc, _, _ := procBeginPaint.Call(hwnd, uintptr(unsafe.Pointer(&ps))) var rc RECT procGetClientRect.Call(hwnd, uintptr(unsafe.Pointer(&rc))) brush, _, _ := procCreateSolidBrush.Call(0) // preto procFillRect.Call(hdc, uintptr(unsafe.Pointer(&rc)), brush) procGdiDeleteObject.Call(brush) font, _, _ := procGetStockObject.Call(defaultFont) old, _, _ := procGdiSelectObject.Call(hdc, font) procSetBkMode.Call(hdc, transparent) procSetTextColor.Call(hdc, neonMagenta) text, _ := windows.UTF16PtrFromString("KVMote") procDrawTextW.Call(hdc, uintptr(unsafe.Pointer(text)), ^uintptr(0), uintptr(unsafe.Pointer(&rc)), dtCenter|dtVCenter|dtSingleLine) procGdiSelectObject.Call(hdc, old) procEndPaint.Call(hwnd, uintptr(unsafe.Pointer(&ps))) return 0 } r, _, _ := procDefWindowProcW.Call(hwnd, msg, wParam, lParam) return r }) overlayCallback = cb hMod, _, _ := procGetModuleHandleW.Call(0) wc := WNDCLASSEXW{ LpfnWndProc: cb, HInstance: hMod, LpszClassName: className, } wc.CbSize = uint32(unsafe.Sizeof(wc)) procRegisterClassExW.Call(uintptr(unsafe.Pointer(&wc))) }) const ( wsPopup = 0x80000000 wsExTopmost = 0x00000008 wsExNoActivate = 0x08000000 wsExToolWindow = 0x00000080 ) hMod, _, _ := procGetModuleHandleW.Call(0) hwnd, _, _ := procCreateWindowExW.Call( wsExTopmost|wsExNoActivate|wsExToolWindow, uintptr(unsafe.Pointer(className)), 0, wsPopup, 0, 0, 200, 60, 0, 0, hMod, 0, ) ov.hwnd = hwnd close(ov.ready) if hwnd == 0 { return } var msg struct { Hwnd uintptr Message uint32 WParam uintptr LParam uintptr Time uint32 Pt Point } for { ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(&msg)), hwnd, 0, 0) if ret == 0 { break } procTranslateMessage.Call(uintptr(unsafe.Pointer(&msg))) procDispatchMessageW.Call(uintptr(unsafe.Pointer(&msg))) } } // Show exibe o overlay no canto superior esquerdo do monitor especificado. // mx, my, mw, mh: work area do monitor em pixels físicos. func (ov *OverlayWindow) Show(mx, my, mw, mh int32) { if ov.hwnd == 0 { return } _ = mw _ = mh const overlayW, overlayH = int32(150), int32(40) const swpNoActivate = 0x0010 // Canto superior esquerdo do monitor — sem depender de taskbar x := mx + 10 y := my + 10 procSetWindowPos.Call(ov.hwnd, ^uintptr(0), // HWND_TOPMOST uintptr(x), uintptr(y), uintptr(overlayW), uintptr(overlayH), swpNoActivate) procShowWindowFn.Call(ov.hwnd, 5) // SW_SHOW procUpdateWindow.Call(ov.hwnd) } // Hide esconde o overlay. func (ov *OverlayWindow) Hide() { if ov.hwnd == 0 { return } procShowWindowFn.Call(ov.hwnd, 0) // SW_HIDE }