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:
parent
3c8cf641a6
commit
5feecaca14
15
core/main.py
15
core/main.py
|
|
@ -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}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue