diff --git a/internal/input/input.go b/internal/input/input.go index 3e70f73..6241d43 100644 --- a/internal/input/input.go +++ b/internal/input/input.go @@ -38,5 +38,6 @@ type InputHandler interface { IsMinimized(hwnd uintptr) bool RestoreWindow(hwnd uintptr) MinimizeWindow(hwnd uintptr) + GetRestoredRect() (x, y, w, h int32) RegisterRawScrollSink(callback func(delta int16)) error } diff --git a/internal/input/input_windows.go b/internal/input/input_windows.go index 6346426..40b90e3 100644 --- a/internal/input/input_windows.go +++ b/internal/input/input_windows.go @@ -39,6 +39,7 @@ var ( procGetForegroundWindow = user32.NewProc("GetForegroundWindow") procIsIconic = user32.NewProc("IsIconic") procShowWindowAsync = user32.NewProc("ShowWindowAsync") + procGetWindowPlacement = user32.NewProc("GetWindowPlacement") procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId") procAttachThreadInput = user32.NewProc("AttachThreadInput") procBringWindowToTop = user32.NewProc("BringWindowToTop") @@ -48,6 +49,16 @@ type RECT struct { Left, Top, Right, Bottom int32 } +type WINDOWPLACEMENT struct { + Length uint32 + Flags uint32 + ShowCmd uint32 + PtMinPosition Point + PtMaxPosition Point + RcNormalPosition RECT + RcDevice RECT +} + type MONITORINFO struct { CbSize uint32 RcMonitor RECT @@ -357,6 +368,19 @@ func (h *windowsInputHandler) GetAppHwnd() uintptr { return h.hwnd } +// GetRestoredRect retorna posição e tamanho do KVMote no estado restaurado, +// mesmo que a janela esteja minimizada (GetWindowRect falha nesse caso). +func (h *windowsInputHandler) GetRestoredRect() (x, y, w, ht int32) { + if h.hwnd == 0 { + return 0, 0, 800, 600 + } + var wp WINDOWPLACEMENT + wp.Length = uint32(unsafe.Sizeof(wp)) + procGetWindowPlacement.Call(h.hwnd, uintptr(unsafe.Pointer(&wp))) + r := wp.RcNormalPosition + return r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top +} + func (h *windowsInputHandler) IsMinimized(hwnd uintptr) bool { ret, _, _ := procIsIconic.Call(hwnd) return ret != 0 diff --git a/internal/input/overlay_windows.go b/internal/input/overlay_windows.go index 655ebea..0fcdd12 100644 --- a/internal/input/overlay_windows.go +++ b/internal/input/overlay_windows.go @@ -93,7 +93,7 @@ func (ov *OverlayWindow) run() { dtVCenter = 0x00000004 dtSingleLine = 0x00000020 transparent = 1 - neonGreen = 0x0014FF39 // #39FF14 em COLORREF (0x00BBGGRR) + neonMagenta = 0x00FF00FF // #FF00FF em COLORREF (0x00BBGGRR) defaultFont = 17 // DEFAULT_GUI_FONT ) switch msg { @@ -112,7 +112,7 @@ func (ov *OverlayWindow) run() { font, _, _ := procGetStockObject.Call(defaultFont) old, _, _ := procGdiSelectObject.Call(hdc, font) procSetBkMode.Call(hdc, transparent) - procSetTextColor.Call(hdc, neonGreen) + procSetTextColor.Call(hdc, neonMagenta) text, _ := windows.UTF16PtrFromString("KVMote") procDrawTextW.Call(hdc, uintptr(unsafe.Pointer(text)), ^uintptr(0), uintptr(unsafe.Pointer(&rc)), dtCenter|dtVCenter|dtSingleLine) diff --git a/internal/kvm/engine.go b/internal/kvm/engine.go index 85c04e9..261da07 100644 --- a/internal/kvm/engine.go +++ b/internal/kvm/engine.go @@ -76,6 +76,10 @@ type Engine struct { prevForegroundHwnd uintptr // janela que estava em foco antes de entrar em client mode prevWasActive bool // true se salvamos uma janela diferente do KVMote + + kvmoteOrigX, kvmoteOrigY int32 // posição original da janela KVMote + kvmoteOrigW, kvmoteOrigH int32 // tamanho original da janela KVMote + kvmoteWasMinimized bool // se estava minimizado ao entrar em client mode } func NewEngine(t transport.Transport, h input.InputHandler) *Engine { @@ -221,9 +225,9 @@ func (e *Engine) onMouse(ev input.MouseEvent) bool { } } - // Park no centro da tela (cursor oculto, só para manter delta consistente) + // Re-parka cursor no canto inferior direito (perto do relógio/bateria) w, h := e.inputHandler.GetScreenResolution() - cx, cy := w/2, h/2 + cx, cy := w-150, h-160 e.isWarping = true e.inputHandler.SetCursorPos(cx, cy) e.lastRawPos = input.Point{X: cx, Y: cy} @@ -270,15 +274,22 @@ func (e *Engine) enterClientMode(p input.Point) { e.wheelAccum = 0 e.mouseThrottle = time.Now() - // Salva janela em foco e traz KVMote pra frente, se necessário + // Salva estado original do KVMote (posição/tamanho/minimizado) e move pra direita kvHwnd := e.inputHandler.GetAppHwnd() + e.kvmoteWasMinimized = e.inputHandler.IsMinimized(kvHwnd) + e.kvmoteOrigX, e.kvmoteOrigY, e.kvmoteOrigW, e.kvmoteOrigH = e.inputHandler.GetRestoredRect() + if e.kvmoteWasMinimized { + e.inputHandler.RestoreWindow(kvHwnd) + } + sw, sh := e.inputHandler.GetScreenResolution() + e.inputHandler.MoveWindow(sw-e.kvmoteOrigW, sh-350, e.kvmoteOrigW, 280, false) + LogDebug(fmt.Sprintf("KVMote movido para direita: x=%d y=%d w=%d h=300", sw-e.kvmoteOrigW, sh-350, e.kvmoteOrigW)) + + // Salva janela em foco e traz KVMote pra frente, se necessário fgHwnd := e.inputHandler.GetForegroundWindow() if fgHwnd != kvHwnd { e.prevForegroundHwnd = fgHwnd e.prevWasActive = true - if e.inputHandler.IsMinimized(kvHwnd) { - e.inputHandler.RestoreWindow(kvHwnd) - } e.inputHandler.RequestFocus() LogDebug(fmt.Sprintf("Janela anterior salva: 0x%X, KVMote trazido ao foco", fgHwnd)) } else { @@ -291,9 +302,9 @@ func (e *Engine) enterClientMode(p input.Point) { e.winManager.SetMiniMode() } - // Parka cursor no centro — scroll via Raw Input, cursor pode ficar visível + // Parka cursor no canto inferior direito (perto do relógio/bateria) w, h := e.inputHandler.GetScreenResolution() - cx, cy := w/2, h/2 + cx, cy := w-150, h-160 e.isWarping = true e.inputHandler.SetCursorPos(cx, cy) e.lastRawPos = input.Point{X: cx, Y: cy} @@ -313,6 +324,16 @@ func (e *Engine) exitClientMode() { e.inputHandler.ShowCursor(true) + // Restaura posição/tamanho original do KVMote + if e.kvmoteOrigW > 0 { + e.inputHandler.MoveWindow(e.kvmoteOrigX, e.kvmoteOrigY, e.kvmoteOrigW, e.kvmoteOrigH, false) + LogDebug(fmt.Sprintf("KVMote restaurado: x=%d y=%d w=%d h=%d", e.kvmoteOrigX, e.kvmoteOrigY, e.kvmoteOrigW, e.kvmoteOrigH)) + } + if e.kvmoteWasMinimized { + e.inputHandler.MinimizeWindow(e.inputHandler.GetAppHwnd()) + e.kvmoteWasMinimized = false + } + // Restaura janela anterior ao foco if e.prevWasActive && e.prevForegroundHwnd != 0 { e.inputHandler.SetForegroundTo(e.prevForegroundHwnd)