fix: Add timeout protection to window manager - fixes 4.5s UI freeze

Problem: EnumWindows was iterating through ALL windows on system
when EU wasn't running, causing 4.5s blocking delays every 5s.

Changes:
- _find_window_by_title: 100ms timeout, 500 window limit
- _find_window_by_process: 150ms timeout, 300 window limit
- Skip empty window titles (performance)
- Slow down focus detection to 60s when EU not found 3x

This should eliminate the 2s menu + 7s click delays.
This commit is contained in:
LemonNexus 2026-02-16 00:06:43 +00:00
parent 3c8cf641a6
commit 5feecaca14
2 changed files with 60 additions and 9 deletions

View File

@ -526,9 +526,24 @@ class EUUtilityApp:
debug_logger.info("MAIN", "EU unfocused - Activity Bar hidden") debug_logger.info("MAIN", "EU unfocused - Activity Bar hidden")
except Exception as e: except Exception as e:
debug_logger.error("MAIN", f"Error hiding activity bar: {e}") debug_logger.error("MAIN", f"Error hiding activity bar: {e}")
# Reset fail count since we found EU
self._eu_not_found_count = 0
else: else:
debug_logger.debug("MAIN", "EU window not found") debug_logger.debug("MAIN", "EU window not found")
# Track consecutive failures
self._eu_not_found_count = getattr(self, '_eu_not_found_count', 0) + 1
# If EU not found for 3 consecutive checks (15 seconds), slow down polling
if self._eu_not_found_count >= 3:
debug_logger.warn("MAIN", f"EU not found {self._eu_not_found_count} times - slowing down focus detection")
# Slow down to once per minute when EU isn't running
if hasattr(self, 'eu_focus_timer'):
self.eu_focus_timer.stop()
self.eu_focus_timer.start(60000) # 60 seconds
debug_logger.info("MAIN", "EU focus detection slowed to 60s interval")
except Exception as e: except Exception as e:
debug_logger.error("MAIN", f"Error in EU focus check: {e}") debug_logger.error("MAIN", f"Error in EU focus check: {e}")

View File

@ -289,21 +289,38 @@ class WindowManager:
self._last_update = current_time self._last_update = current_time
def _find_window_by_title(self, title: str) -> Optional[int]: def _find_window_by_title(self, title: str) -> Optional[int]:
"""Find window by title (partial match).""" """Find window by title (partial match) - with timeout protection."""
found_hwnd = [None] found_hwnd = [None]
start_time = time.time()
window_count = [0]
MAX_WINDOWS = 500 # Limit windows to check
MAX_TIME = 0.1 # 100ms timeout
def callback(hwnd, extra): def callback(hwnd, extra):
# Check timeout
if time.time() - start_time > MAX_TIME:
return False # Stop enumeration - timeout
# Check window limit
window_count[0] += 1
if window_count[0] > MAX_WINDOWS:
return False # Stop enumeration - too many windows
if not self.user32.IsWindowVisible(hwnd): if not self.user32.IsWindowVisible(hwnd):
return True return True
# Get window text # Quick check - skip windows with no title
text = ctypes.create_unicode_buffer(256) text = ctypes.create_unicode_buffer(256)
self.user32.GetWindowTextW(hwnd, text, 256) self.user32.GetWindowTextW(hwnd, text, 256)
window_title = text.value window_title = text.value
if not window_title: # Skip empty titles
return True
# Fast case-insensitive check
if title.lower() in window_title.lower(): if title.lower() in window_title.lower():
found_hwnd[0] = hwnd found_hwnd[0] = hwnd
return False # Stop enumeration return False # Stop enumeration - found!
return True return True
@ -313,10 +330,23 @@ class WindowManager:
return found_hwnd[0] return found_hwnd[0]
def _find_window_by_process(self, process_name: str) -> Optional[int]: def _find_window_by_process(self, process_name: str) -> Optional[int]:
"""Find window by process name.""" """Find window by process name - with timeout protection."""
found_hwnd = [None] found_hwnd = [None]
start_time = time.time()
window_count = [0]
MAX_WINDOWS = 300 # Lower limit for process check (slower)
MAX_TIME = 0.15 # 150ms timeout
def callback(hwnd, extra): def callback(hwnd, extra):
# Check timeout
if time.time() - start_time > MAX_TIME:
return False # Stop enumeration - timeout
# Check window limit
window_count[0] += 1
if window_count[0] > MAX_WINDOWS:
return False # Stop enumeration - too many windows
if not self.user32.IsWindowVisible(hwnd): if not self.user32.IsWindowVisible(hwnd):
return True return True
@ -324,11 +354,17 @@ class WindowManager:
pid = wintypes.DWORD() pid = wintypes.DWORD()
self.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) self.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid))
# Check process name if pid.value == 0:
proc_info = self._get_process_info(pid.value) return True
if proc_info and process_name.lower() in proc_info.name.lower():
found_hwnd[0] = hwnd # Check process name (this is slower, so we limit more)
return False # Stop enumeration try:
proc_info = self._get_process_info(pid.value)
if proc_info and process_name.lower() in proc_info.name.lower():
found_hwnd[0] = hwnd
return False # Stop enumeration - found!
except Exception:
pass # Skip on error
return True return True