Problem: The overlay window, even when fully transparent or visually hidden (opacity 0), was still intercepting mouse events. This created a 'dead zone' on the screen where users could not click through to applications behind the overlay. This occurred because the low-level window hook was answering 'HTCAPTION' to hit tests regardless of the UI state. Solution: 1. Modified 'WindowHook' to accept an 'enabled' state. 2. When disabled, 'WM_NCHITTEST' now returns 'HTTRANSPARENT', allowing the OS to pass the click to the window underneath. 3. Updated 'main.py' to toggle this hook state dynamically: - ENABLED when Recording or Processing (UI is visible/active). - DISABLED when Idling (UI is hidden/transparent). Result: The overlay is now completely non-intrusive when not in use.
125 lines
3.8 KiB
Python
125 lines
3.8 KiB
Python
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
|
|
self.enabled = True # New flag
|
|
|
|
def set_enabled(self, enabled):
|
|
self.enabled = enabled
|
|
|
|
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:
|
|
# If disabled (invisible/inactive), let clicks pass through (HTTRANSPARENT)
|
|
if not self.enabled:
|
|
return HTTRANSPARENT
|
|
|
|
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
|