Fix: Invisible overlay blocking mouse clicks

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.
This commit is contained in:
Your Name
2026-01-24 17:51:23 +02:00
parent 306bd075ed
commit f184eb0037
2 changed files with 45 additions and 7 deletions

42
main.py
View File

@@ -222,12 +222,23 @@ class WhisperApp(QObject):
self.settings_root.setVisible(False)
# Install Low-Level Window Hook for Transparent Hit Test
# We must keep a reference to 'self.hook' so it isn't GC'd
# scale = self.overlay_root.devicePixelRatio()
# self.hook = WindowHook(int(self.overlay_root.winId()), 500, 300, scale)
# self.hook.install()
try:
from src.utils.window_hook import WindowHook
hwnd = self.overlay_root.winId()
# Initial scale from config
scale = float(self.config.get("ui_scale"))
# NOTE: HitTest hook will be installed here later
# Current Overlay Dimensions
win_w = int(460 * scale)
win_h = int(180 * scale)
self.window_hook = WindowHook(hwnd, win_w, win_h, initial_scale=scale)
self.window_hook.install()
# Initial state: Disabled because we start inactive
self.window_hook.set_enabled(False)
except Exception as e:
logging.error(f"Failed to install WindowHook: {e}")
def center_overlay(self):
"""Calculates and sets the Overlay position above the taskbar."""
@@ -531,6 +542,25 @@ class WhisperApp(QObject):
self.bridge.update_status("Error")
logging.error(f"Download Error: {err}")
@Slot(bool)
def on_ui_toggle_request(self, is_recording):
"""Called when recording state changes."""
# Update Window Hook to allow clicking if active
is_active = is_recording or self.bridge.isProcessing
if hasattr(self, 'window_hook'):
self.window_hook.set_enabled(is_active)
@Slot(bool)
def on_processing_changed(self, is_processing):
is_active = self.bridge.isRecording or is_processing
if hasattr(self, 'window_hook'):
self.window_hook.set_enabled(is_active)
if __name__ == "__main__":
import sys
app = WhisperApp()
app.run()
# Connect extra signal for processing state
app.bridge.isProcessingChanged.connect(app.on_processing_changed)
sys.exit(app.run())

View File

@@ -65,6 +65,10 @@ class WindowHook:
# (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)
@@ -73,6 +77,10 @@ class WindowHook:
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