From 8aeb5360f6d2e71fecd6e4f9e4244ca5d770e3c2 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 8 Sep 2026 23:47:22 +0200 Subject: [PATCH] feat(controller): add refresh_display_bounds and dynamic resolution and monitor change detection --- catser/cat_controller.py | 83 +++++++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 18 deletions(-) diff --git a/catser/cat_controller.py b/catser/cat_controller.py index c617ca7..52a0aca 100644 --- a/catser/cat_controller.py +++ b/catser/cat_controller.py @@ -84,24 +84,10 @@ class CatController: self.config = config self.assets = assets - # Primary monitor work area (excludes taskbar) - work_area = WindowManager.get_work_area() - self.screen_left = work_area[0] - self.screen_top = work_area[1] - self.screen_right = work_area[2] - self.screen_bottom = work_area[3] - - # Full virtual desktop spanning ALL monitors – used for roaming, platforming, - # and dragging so the cat can freely explore secondary displays. - vd = WindowManager.get_virtual_desktop_bounds() - self.vd_left = vd[0] - self.vd_top = vd[1] - self.vd_right = vd[2] - self.vd_bottom = vd[3] - - # Horizontal roaming boundaries across all monitors - self.min_x = float(self.vd_left + 15) - self.max_x = float(self.vd_right - self.config.cat_width - 15) + # Dynamic display topology signature & periodic checker + self._current_display_signature = None + self.next_display_check_time = time.time() + 1.5 + self.refresh_display_bounds(force=True) # Position & motion self.x = float(self.screen_left + 100) @@ -155,6 +141,62 @@ class CatController: self.walk_speed_px = (WALK_SPEED / config.cat_width) * (config.cat_width * 5.0) self.run_speed_px = (RUN_SPEED / config.cat_width) * (config.cat_width * 4.0) + def refresh_display_bounds(self, force: bool = False) -> bool: + """ + Dynamically refreshes monitor geometries, virtual desktop boundaries, + and clamps cat position if display topology or resolution changed. + Returns True if a display change occurred, False otherwise. + """ + new_signature = WindowManager.get_display_topology_signature() + if not force and new_signature == self._current_display_signature: + return False + + self._current_display_signature = new_signature + + # Update primary monitor work area (excludes taskbar) + work_area = WindowManager.get_work_area() + self.screen_left = work_area[0] + self.screen_top = work_area[1] + self.screen_right = work_area[2] + self.screen_bottom = work_area[3] + + # Update full virtual desktop spanning all monitors + vd = WindowManager.get_virtual_desktop_bounds() + self.vd_left = vd[0] + self.vd_top = vd[1] + self.vd_right = vd[2] + self.vd_bottom = vd[3] + + # Update horizontal roaming boundaries + self.min_x = float(self.vd_left + 15) + self.max_x = float(self.vd_right - self.config.cat_width - 15) + + monitors = WindowManager.get_monitors() + logger.info( + f"Dynamic display configuration updated: {len(monitors)} monitor(s) detected, " + f"virtual desktop=({self.vd_left}, {self.vd_top}, {self.vd_right}, {self.vd_bottom}), " + f"primary work area={work_area}" + ) + + # Ensure the cat is not stranded outside newly active bounds + if hasattr(self, "x") and hasattr(self, "y"): + prev_x = self.x + self.x = max(self.min_x, min(self.max_x, self.x)) + + # Re-evaluate floor at current position + new_floor = WindowManager.get_floor_y_at(self.x + self.config.cat_width * 0.5, self.config.cat_height) + self.floor_y = new_floor + + # If walking or resting on floor, adjust Y to new floor + if hasattr(self, "current_ledge") and not self.current_ledge: + if hasattr(self, "state") and self.state in (CatState.WALK, CatState.SIT, CatState.SLEEP, CatState.RELEASE): + self.y = new_floor + + if prev_x != self.x: + logger.info(f"Clamped cat X from {prev_x} to {self.x} due to display boundary change") + + return True + # ========================================================================== # State Transitions & Targeting # ========================================================================== @@ -348,6 +390,11 @@ class CatController: face_type = "happy" if self.state == CatState.HAPPY else ("blink" if self.is_blinking else "open") + # Dynamic check for monitor addition/removal, resolution or work-area change + if now >= self.next_display_check_time: + self.next_display_check_time = now + 1.5 + self.refresh_display_bounds() + # ---------------------------------------------------------------------- # State: WALK (roaming desktop floors and window ledges) # ----------------------------------------------------------------------