Initial commit of WhisperVoice
This commit is contained in:
116
src/utils/window_hook.py
Normal file
116
src/utils/window_hook.py
Normal file
@@ -0,0 +1,116 @@
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
import logging
|
||||
|
||||
# Win32 Types & Constants
|
||||
# Use standard wintypes to ensure architecture safety (32-bit vs 64-bit)
|
||||
WPARAM = wintypes.WPARAM
|
||||
LPARAM = wintypes.LPARAM
|
||||
HWND = wintypes.HWND
|
||||
UINT = wintypes.UINT
|
||||
|
||||
# LRESULT is technically LONG_PTR (signed)
|
||||
# In 64-bit, c_longlong. In 32-bit, c_long.
|
||||
if ctypes.sizeof(ctypes.c_void_p) == 8:
|
||||
LRESULT = ctypes.c_longlong
|
||||
else:
|
||||
LRESULT = ctypes.c_long
|
||||
|
||||
# Callback Type
|
||||
WNDPROC = ctypes.WINFUNCTYPE(LRESULT, HWND, UINT, WPARAM, LPARAM)
|
||||
|
||||
GWLP_WNDPROC = -4
|
||||
WM_NCHITTEST = 0x0084
|
||||
HTTRANSPARENT = -1
|
||||
HTCLIENT = 1
|
||||
HTCAPTION = 2
|
||||
|
||||
user32 = ctypes.windll.user32
|
||||
|
||||
# SetWindowLongPtr Check
|
||||
if hasattr(user32, "SetWindowLongPtrW"):
|
||||
SetWindowLongPtr = user32.SetWindowLongPtrW
|
||||
SetWindowLongPtr.argtypes = [HWND, ctypes.c_int, ctypes.c_void_p]
|
||||
SetWindowLongPtr.restype = ctypes.c_void_p
|
||||
else:
|
||||
SetWindowLongPtr = user32.SetWindowLongW
|
||||
SetWindowLongPtr.argtypes = [HWND, ctypes.c_int, ctypes.c_long]
|
||||
SetWindowLongPtr.restype = ctypes.c_long
|
||||
|
||||
# Define ArgTypes to prevent truncation/stack corruption
|
||||
user32.CallWindowProcW.argtypes = [ctypes.c_void_p, HWND, UINT, WPARAM, LPARAM]
|
||||
user32.CallWindowProcW.restype = LRESULT
|
||||
|
||||
user32.ScreenToClient.argtypes = [HWND, ctypes.POINTER(wintypes.POINT)]
|
||||
user32.ScreenToClient.restype = ctypes.c_bool
|
||||
|
||||
# DPI API
|
||||
try:
|
||||
GetDpiForWindow = user32.GetDpiForWindow
|
||||
GetDpiForWindow.argtypes = [HWND]
|
||||
GetDpiForWindow.restype = UINT
|
||||
except AttributeError:
|
||||
GetDpiForWindow = None
|
||||
|
||||
def LOWORD(l): return l & 0xffff
|
||||
def HIWORD(l): return (l >> 16) & 0xffff
|
||||
|
||||
class WindowHook:
|
||||
def __init__(self, hwnd, width, height, initial_scale=1.0):
|
||||
self.hwnd = hwnd
|
||||
self.old_wnd_proc = None
|
||||
self.new_wnd_proc = WNDPROC(self.wnd_proc_callback)
|
||||
|
||||
# TIGHT FIT: [20, 20, 400, 120]
|
||||
# (Window 420x140, Pill 380x100)
|
||||
self.logical_rect = [20, 20, 20+380, 20+100]
|
||||
self.current_scale = initial_scale
|
||||
|
||||
def install(self):
|
||||
proc_address = ctypes.cast(self.new_wnd_proc, ctypes.c_void_p)
|
||||
self.old_wnd_proc = SetWindowLongPtr(self.hwnd, GWLP_WNDPROC, proc_address)
|
||||
|
||||
def wnd_proc_callback(self, hwnd, msg, wParam, lParam):
|
||||
try:
|
||||
if msg == WM_NCHITTEST:
|
||||
res = self.on_nchittest(lParam)
|
||||
if res != 0:
|
||||
return res
|
||||
|
||||
return user32.CallWindowProcW(self.old_wnd_proc, hwnd, msg, wParam, lParam)
|
||||
except Exception:
|
||||
# Swallow exceptions in callback to prevent recursive crash loops
|
||||
return 0
|
||||
|
||||
def on_nchittest(self, lParam):
|
||||
if GetDpiForWindow:
|
||||
dpi = GetDpiForWindow(self.hwnd)
|
||||
if dpi > 0:
|
||||
self.current_scale = dpi / 96.0
|
||||
|
||||
# Physics
|
||||
s = self.current_scale
|
||||
phys_rect = [
|
||||
self.logical_rect[0] * s,
|
||||
self.logical_rect[1] * s,
|
||||
self.logical_rect[2] * s,
|
||||
self.logical_rect[3] * s
|
||||
]
|
||||
|
||||
x_screen = LOWORD(lParam)
|
||||
if x_screen > 32767: x_screen -= 65536
|
||||
y_screen = HIWORD(lParam)
|
||||
if y_screen > 32767: y_screen -= 65536
|
||||
|
||||
pt = wintypes.POINT(x_screen, y_screen)
|
||||
user32.ScreenToClient(self.hwnd, ctypes.byref(pt))
|
||||
lx = pt.x
|
||||
ly = pt.y
|
||||
|
||||
inside = (lx >= phys_rect[0] and lx <= phys_rect[2] and
|
||||
ly >= phys_rect[1] and ly <= phys_rect[3])
|
||||
|
||||
if inside:
|
||||
return HTCAPTION
|
||||
else:
|
||||
return HTTRANSPARENT
|
||||
Reference in New Issue
Block a user