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:
42
main.py
42
main.py
@@ -222,12 +222,23 @@ class WhisperApp(QObject):
|
|||||||
self.settings_root.setVisible(False)
|
self.settings_root.setVisible(False)
|
||||||
|
|
||||||
# Install Low-Level Window Hook for Transparent Hit Test
|
# Install Low-Level Window Hook for Transparent Hit Test
|
||||||
# We must keep a reference to 'self.hook' so it isn't GC'd
|
try:
|
||||||
# scale = self.overlay_root.devicePixelRatio()
|
from src.utils.window_hook import WindowHook
|
||||||
# self.hook = WindowHook(int(self.overlay_root.winId()), 500, 300, scale)
|
hwnd = self.overlay_root.winId()
|
||||||
# self.hook.install()
|
# 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):
|
def center_overlay(self):
|
||||||
"""Calculates and sets the Overlay position above the taskbar."""
|
"""Calculates and sets the Overlay position above the taskbar."""
|
||||||
@@ -531,6 +542,25 @@ class WhisperApp(QObject):
|
|||||||
self.bridge.update_status("Error")
|
self.bridge.update_status("Error")
|
||||||
logging.error(f"Download Error: {err}")
|
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__":
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
app = WhisperApp()
|
app = WhisperApp()
|
||||||
app.run()
|
|
||||||
|
# Connect extra signal for processing state
|
||||||
|
app.bridge.isProcessingChanged.connect(app.on_processing_changed)
|
||||||
|
|
||||||
|
sys.exit(app.run())
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ class WindowHook:
|
|||||||
# (Window 420x140, Pill 380x100)
|
# (Window 420x140, Pill 380x100)
|
||||||
self.logical_rect = [20, 20, 20+380, 20+100]
|
self.logical_rect = [20, 20, 20+380, 20+100]
|
||||||
self.current_scale = initial_scale
|
self.current_scale = initial_scale
|
||||||
|
self.enabled = True # New flag
|
||||||
|
|
||||||
|
def set_enabled(self, enabled):
|
||||||
|
self.enabled = enabled
|
||||||
|
|
||||||
def install(self):
|
def install(self):
|
||||||
proc_address = ctypes.cast(self.new_wnd_proc, ctypes.c_void_p)
|
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):
|
def wnd_proc_callback(self, hwnd, msg, wParam, lParam):
|
||||||
try:
|
try:
|
||||||
if msg == WM_NCHITTEST:
|
if msg == WM_NCHITTEST:
|
||||||
|
# If disabled (invisible/inactive), let clicks pass through (HTTRANSPARENT)
|
||||||
|
if not self.enabled:
|
||||||
|
return HTTRANSPARENT
|
||||||
|
|
||||||
res = self.on_nchittest(lParam)
|
res = self.on_nchittest(lParam)
|
||||||
if res != 0:
|
if res != 0:
|
||||||
return res
|
return res
|
||||||
|
|||||||
Reference in New Issue
Block a user