//go:build windows package input import ( "context" "fmt" "runtime" "unsafe" "golang.org/x/sys/windows" ) var ( user32 = windows.NewLazySystemDLL("user32.dll") kernel32 = windows.NewLazySystemDLL("kernel32.dll") procSetWindowsHookExW = user32.NewProc("SetWindowsHookExW") procUnhookWindowsHookEx = user32.NewProc("UnhookWindowsHookEx") procCallNextHookEx = user32.NewProc("CallNextHookEx") procGetMessageW = user32.NewProc("GetMessageW") procSetCursorPos = user32.NewProc("SetCursorPos") procShowCursor = user32.NewProc("ShowCursor") procGetModuleHandleW = kernel32.NewProc("GetModuleHandleW") procGetSystemMetrics = user32.NewProc("GetSystemMetrics") procSetProcessDPIAware = user32.NewProc("SetProcessDPIAware") procPostThreadMessageW = user32.NewProc("PostThreadMessageW") procGetActiveWindow = user32.NewProc("GetActiveWindow") procSetForegroundWindow = user32.NewProc("SetForegroundWindow") procGetWindowRect = user32.NewProc("GetWindowRect") procClipCursor = user32.NewProc("ClipCursor") procSetWindowPos = user32.NewProc("SetWindowPos") procMonitorFromWindow = user32.NewProc("MonitorFromWindow") procGetMonitorInfoW = user32.NewProc("GetMonitorInfoW") ) type RECT struct { Left, Top, Right, Bottom int32 } type MONITORINFO struct { CbSize uint32 RcMonitor RECT RcWork RECT DwFlags uint32 } const ( WH_KEYBOARD_LL = 13 WH_MOUSE_LL = 14 SM_CXSCREEN = 0 SM_CYSCREEN = 1 WM_QUIT = 0x0012 ) func init() { procSetProcessDPIAware.Call() } type MSLLHOOKSTRUCT struct { Pt Point MouseData uint32 Flags uint32 Time uint32 DwExtraInfo uintptr } type KBDLLHOOKSTRUCT struct { VkCode uint32 ScanCode uint32 Flags uint32 Time uint32 DwExtraInfo uintptr } type windowsInputHandler struct { mouseHook uintptr keyHook uintptr tid uint32 hwnd uintptr } func NewInputHandler() InputHandler { return &windowsInputHandler{} } func (h *windowsInputHandler) Install(ctx context.Context, onMouse func(MouseEvent) bool, onKey func(KeyboardEvent) bool) error { // Captura o HWND da janela ativa (que deve ser o nosso App Wails chamando Install) hwnd, _, _ := procGetActiveWindow.Call() h.hwnd = hwnd ready := make(chan error, 1) go func() { runtime.LockOSThread() h.tid = windows.GetCurrentThreadId() hMod, _, _ := procGetModuleHandleW.Call(0) mouseCallback := windows.NewCallback(func(nCode int, wParam uintptr, lParam uintptr) uintptr { if nCode >= 0 { info := (*MSLLHOOKSTRUCT)(unsafe.Pointer(lParam)) ev := MouseEvent{ Message: uint32(wParam), Point: info.Pt, Data: info.MouseData, } // Se a engine tratar o evento (retornar true), bloqueamos o Windows if onMouse(ev) { return 1 } } ret, _, _ := procCallNextHookEx.Call(h.mouseHook, uintptr(nCode), wParam, lParam) return ret }) keyCallback := windows.NewCallback(func(nCode int, wParam uintptr, lParam uintptr) uintptr { if nCode >= 0 { info := (*KBDLLHOOKSTRUCT)(unsafe.Pointer(lParam)) ev := KeyboardEvent{ Message: uint32(wParam), VKCode: info.VkCode, ScanCode: info.ScanCode, Flags: info.Flags, } if onKey(ev) { return 1 } } ret, _, _ := procCallNextHookEx.Call(h.keyHook, uintptr(nCode), wParam, lParam) return ret }) mh, _, _ := procSetWindowsHookExW.Call(WH_MOUSE_LL, mouseCallback, hMod, 0) if mh == 0 { ready <- fmt.Errorf("failed mouse hook") return } h.mouseHook = mh kh, _, _ := procSetWindowsHookExW.Call(WH_KEYBOARD_LL, keyCallback, hMod, 0) if kh == 0 { ready <- fmt.Errorf("failed key hook") return } h.keyHook = kh ready <- nil var msg struct { Hwnd windows.Handle Message uint32 WParam uintptr LParam uintptr Time uint32 Pt Point } for { ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0) if ret == 0 || msg.Message == WM_QUIT { break } } procUnhookWindowsHookEx.Call(h.mouseHook) procUnhookWindowsHookEx.Call(h.keyHook) }() return <-ready } func (h *windowsInputHandler) Uninstall() { if h.tid != 0 { procPostThreadMessageW.Call(uintptr(h.tid), WM_QUIT, 0, 0) } } func (h *windowsInputHandler) SetCursorPos(x, y int32) bool { ret, _, _ := procSetCursorPos.Call(uintptr(x), uintptr(y)) return ret != 0 } func (h *windowsInputHandler) ShowCursor(show bool) { s := -1 // No Windows, ShowCursor(FALSE) decrementa um contador if show { s = 1 } procShowCursor.Call(uintptr(s)) } func (h *windowsInputHandler) GetScreenResolution() (int32, int32) { w, _, _ := procGetSystemMetrics.Call(SM_CXSCREEN) h_res, _, _ := procGetSystemMetrics.Call(SM_CYSCREEN) return int32(w), int32(h_res) } func (h *windowsInputHandler) RequestFocus() { if h.hwnd != 0 { procSetForegroundWindow.Call(h.hwnd) } } func (h *windowsInputHandler) SetCursorClip(clip bool) { // Removido temporariamente para testes if !clip { procClipCursor.Call(0) } } func (h *windowsInputHandler) GetWindowPos() (int32, int32) { if h.hwnd != 0 { var rect RECT procGetWindowRect.Call(h.hwnd, uintptr(unsafe.Pointer(&rect))) return rect.Left, rect.Top } return 0, 0 } func (h *windowsInputHandler) GetWindowSize() (int32, int32) { if h.hwnd != 0 { var rect RECT procGetWindowRect.Call(h.hwnd, uintptr(unsafe.Pointer(&rect))) return rect.Right - rect.Left, rect.Bottom - rect.Top } return 0, 0 } func (h *windowsInputHandler) GetMonitorWorkArea() (x, y, w, ht int32) { if h.hwnd == 0 { return 0, 0, 1920, 1080 } monitor, _, _ := procMonitorFromWindow.Call(h.hwnd, 2) // MONITOR_DEFAULTTONEAREST if monitor == 0 { return 0, 0, 1920, 1080 } var mi MONITORINFO mi.CbSize = uint32(unsafe.Sizeof(mi)) procGetMonitorInfoW.Call(monitor, uintptr(unsafe.Pointer(&mi))) return mi.RcWork.Left, mi.RcWork.Top, mi.RcWork.Right - mi.RcWork.Left, mi.RcWork.Bottom - mi.RcWork.Top } func (h *windowsInputHandler) MoveWindow(x, y, w, ht int32, topmost bool) { if h.hwnd == 0 { return } // HWND_TOPMOST = -1, HWND_NOTOPMOST = -2 zorder := ^uintptr(1) // HWND_NOTOPMOST if topmost { zorder = ^uintptr(0) // HWND_TOPMOST } const SWP_NOACTIVATE = 0x0010 procSetWindowPos.Call( h.hwnd, zorder, uintptr(x), uintptr(y), uintptr(w), uintptr(ht), SWP_NOACTIVATE, ) }