feat: Janela sobre o relogio

This commit is contained in:
Ricardo Carneiro 2026-04-27 10:12:06 -03:00
parent 1de5a60843
commit 879ae24ca6
4 changed files with 56 additions and 10 deletions

View File

@ -38,5 +38,6 @@ type InputHandler interface {
IsMinimized(hwnd uintptr) bool IsMinimized(hwnd uintptr) bool
RestoreWindow(hwnd uintptr) RestoreWindow(hwnd uintptr)
MinimizeWindow(hwnd uintptr) MinimizeWindow(hwnd uintptr)
GetRestoredRect() (x, y, w, h int32)
RegisterRawScrollSink(callback func(delta int16)) error RegisterRawScrollSink(callback func(delta int16)) error
} }

View File

@ -39,6 +39,7 @@ var (
procGetForegroundWindow = user32.NewProc("GetForegroundWindow") procGetForegroundWindow = user32.NewProc("GetForegroundWindow")
procIsIconic = user32.NewProc("IsIconic") procIsIconic = user32.NewProc("IsIconic")
procShowWindowAsync = user32.NewProc("ShowWindowAsync") procShowWindowAsync = user32.NewProc("ShowWindowAsync")
procGetWindowPlacement = user32.NewProc("GetWindowPlacement")
procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId") procGetWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId")
procAttachThreadInput = user32.NewProc("AttachThreadInput") procAttachThreadInput = user32.NewProc("AttachThreadInput")
procBringWindowToTop = user32.NewProc("BringWindowToTop") procBringWindowToTop = user32.NewProc("BringWindowToTop")
@ -48,6 +49,16 @@ type RECT struct {
Left, Top, Right, Bottom int32 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 { type MONITORINFO struct {
CbSize uint32 CbSize uint32
RcMonitor RECT RcMonitor RECT
@ -357,6 +368,19 @@ func (h *windowsInputHandler) GetAppHwnd() uintptr {
return h.hwnd 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 { func (h *windowsInputHandler) IsMinimized(hwnd uintptr) bool {
ret, _, _ := procIsIconic.Call(hwnd) ret, _, _ := procIsIconic.Call(hwnd)
return ret != 0 return ret != 0

View File

@ -93,7 +93,7 @@ func (ov *OverlayWindow) run() {
dtVCenter = 0x00000004 dtVCenter = 0x00000004
dtSingleLine = 0x00000020 dtSingleLine = 0x00000020
transparent = 1 transparent = 1
neonGreen = 0x0014FF39 // #39FF14 em COLORREF (0x00BBGGRR) neonMagenta = 0x00FF00FF // #FF00FF em COLORREF (0x00BBGGRR)
defaultFont = 17 // DEFAULT_GUI_FONT defaultFont = 17 // DEFAULT_GUI_FONT
) )
switch msg { switch msg {
@ -112,7 +112,7 @@ func (ov *OverlayWindow) run() {
font, _, _ := procGetStockObject.Call(defaultFont) font, _, _ := procGetStockObject.Call(defaultFont)
old, _, _ := procGdiSelectObject.Call(hdc, font) old, _, _ := procGdiSelectObject.Call(hdc, font)
procSetBkMode.Call(hdc, transparent) procSetBkMode.Call(hdc, transparent)
procSetTextColor.Call(hdc, neonGreen) procSetTextColor.Call(hdc, neonMagenta)
text, _ := windows.UTF16PtrFromString("KVMote") text, _ := windows.UTF16PtrFromString("KVMote")
procDrawTextW.Call(hdc, uintptr(unsafe.Pointer(text)), ^uintptr(0), procDrawTextW.Call(hdc, uintptr(unsafe.Pointer(text)), ^uintptr(0),
uintptr(unsafe.Pointer(&rc)), dtCenter|dtVCenter|dtSingleLine) uintptr(unsafe.Pointer(&rc)), dtCenter|dtVCenter|dtSingleLine)

View File

@ -76,6 +76,10 @@ type Engine struct {
prevForegroundHwnd uintptr // janela que estava em foco antes de entrar em client mode prevForegroundHwnd uintptr // janela que estava em foco antes de entrar em client mode
prevWasActive bool // true se salvamos uma janela diferente do KVMote 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 { 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() w, h := e.inputHandler.GetScreenResolution()
cx, cy := w/2, h/2 cx, cy := w-150, h-160
e.isWarping = true e.isWarping = true
e.inputHandler.SetCursorPos(cx, cy) e.inputHandler.SetCursorPos(cx, cy)
e.lastRawPos = input.Point{X: cx, Y: cy} e.lastRawPos = input.Point{X: cx, Y: cy}
@ -270,15 +274,22 @@ func (e *Engine) enterClientMode(p input.Point) {
e.wheelAccum = 0 e.wheelAccum = 0
e.mouseThrottle = time.Now() 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() 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() fgHwnd := e.inputHandler.GetForegroundWindow()
if fgHwnd != kvHwnd { if fgHwnd != kvHwnd {
e.prevForegroundHwnd = fgHwnd e.prevForegroundHwnd = fgHwnd
e.prevWasActive = true e.prevWasActive = true
if e.inputHandler.IsMinimized(kvHwnd) {
e.inputHandler.RestoreWindow(kvHwnd)
}
e.inputHandler.RequestFocus() e.inputHandler.RequestFocus()
LogDebug(fmt.Sprintf("Janela anterior salva: 0x%X, KVMote trazido ao foco", fgHwnd)) LogDebug(fmt.Sprintf("Janela anterior salva: 0x%X, KVMote trazido ao foco", fgHwnd))
} else { } else {
@ -291,9 +302,9 @@ func (e *Engine) enterClientMode(p input.Point) {
e.winManager.SetMiniMode() 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() w, h := e.inputHandler.GetScreenResolution()
cx, cy := w/2, h/2 cx, cy := w-150, h-160
e.isWarping = true e.isWarping = true
e.inputHandler.SetCursorPos(cx, cy) e.inputHandler.SetCursorPos(cx, cy)
e.lastRawPos = input.Point{X: cx, Y: cy} e.lastRawPos = input.Point{X: cx, Y: cy}
@ -313,6 +324,16 @@ func (e *Engine) exitClientMode() {
e.inputHandler.ShowCursor(true) 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 // Restaura janela anterior ao foco
if e.prevWasActive && e.prevForegroundHwnd != 0 { if e.prevWasActive && e.prevForegroundHwnd != 0 {
e.inputHandler.SetForegroundTo(e.prevForegroundHwnd) e.inputHandler.SetForegroundTo(e.prevForegroundHwnd)