Initial commit of WhisperVoice

This commit is contained in:
Your Name
2026-01-24 17:03:52 +02:00
commit 9ff0e8d108
118 changed files with 6102 additions and 0 deletions

116
src/utils/window_hook.py Normal file
View 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