KVMote.go/internal/input/input_windows.go

172 lines
4.1 KiB
Go

//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")
)
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 // Mantemos uint32 mas vamos converter no callback
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
}
func NewInputHandler() InputHandler {
return &windowsInputHandler{}
}
func (h *windowsInputHandler) Install(ctx context.Context, onMouse func(MouseEvent) bool, onKey func(KeyboardEvent) bool) error {
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, não passamos para o próximo hook
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
}
// Vamos usar uma abordagem mais direta se necessário, mas por enquanto:
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)
}