Tentativa de capturar scroll via mini janela sempre-no-topo. Inclui: MoveWindow/GetMonitorWorkArea via Win32, park consistente em wx+150/wy+50, ShowCursor loop no goroutine pós-resize. Revertível: git checkout HEAD~1 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
259 lines
6.3 KiB
Go
259 lines
6.3 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")
|
|
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,
|
|
)
|
|
}
|