diff --git a/core/main.py b/core/main.py index 4fbc9e2..f032ccd 100644 --- a/core/main.py +++ b/core/main.py @@ -526,9 +526,24 @@ class EUUtilityApp: debug_logger.info("MAIN", "EU unfocused - Activity Bar hidden") except Exception as 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: 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: debug_logger.error("MAIN", f"Error in EU focus check: {e}") diff --git a/core/window_manager.py b/core/window_manager.py index 1fbd79a..7e7a09b 100644 --- a/core/window_manager.py +++ b/core/window_manager.py @@ -289,21 +289,38 @@ class WindowManager: self._last_update = current_time 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] + start_time = time.time() + window_count = [0] + MAX_WINDOWS = 500 # Limit windows to check + MAX_TIME = 0.1 # 100ms timeout 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): return True - # Get window text + # Quick check - skip windows with no title text = ctypes.create_unicode_buffer(256) self.user32.GetWindowTextW(hwnd, text, 256) window_title = text.value + if not window_title: # Skip empty titles + return True + + # Fast case-insensitive check if title.lower() in window_title.lower(): found_hwnd[0] = hwnd - return False # Stop enumeration + return False # Stop enumeration - found! return True @@ -313,10 +330,23 @@ class WindowManager: return found_hwnd[0] 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] + 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): + # 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): return True @@ -324,11 +354,17 @@ class WindowManager: pid = wintypes.DWORD() self.user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) - # Check process name - 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 + if pid.value == 0: + return True + + # Check process name (this is slower, so we limit more) + 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