From 56856c7490d44beab065fdc2510cae33b973ccbc Mon Sep 17 00:00:00 2001 From: Ken D'Ambrosio Date: Sat, 11 Jul 2026 23:03:10 -0400 Subject: Fix taskbar detection and off-screen floor landing; add periodic bath MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The taskbar (run, run_begin, jump, walk_task2, and everything gated on only="taskbar") was completely unreachable: WayfireWindows never populated self._taskbar, it was hardcoded to None. Detect it for real by unioning any wide, thin desktop-environment layer-shell surface (waybar, a top/bottom panel) anchored to top or bottom. That surfaced two more bugs once sheep actually started landing on it: - The taskbar floor was placed at the panel's top edge unconditionally, which is correct for a bottom-anchored bar but puts the sheep above y=0 (off-screen) for a top-anchored one like this desktop's waybar. Now it stands on whichever edge faces the screen interior. - Toplevel windows were used as landing floors regardless of whether their reported y was actually on the visible screen. This compositor's wide-viewport layout returns raw/absolute geometry for windows on other workspaces, so a window can have x within screen bounds while y sits off the top entirely — landing there also put sheep above y=0, invisible. Windows are now only used as floors when their top edge is within [0, screenH]. Also added a periodic timer (mirroring the existing eat/hop timers) to trigger the batha→bathb→bathc→bathd bath sequence on an idle sheep every 90-180s. It was only ever reachable via a ~3% spawn-time roll, so in practice it was almost never seen. --- esheep.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/esheep.py b/esheep.py index 5bf2328..b86fc90 100644 --- a/esheep.py +++ b/esheep.py @@ -204,6 +204,9 @@ class WayfireWindows: self._taskbar: Optional[Tuple[int,int,int,int]] = None self._lock = threading.Lock() self._available = False + # Unknown until SheepOverlay reports real geometry; keep permissive + # (no filtering) until then rather than wrongly excluding everything. + self._screen_h = 1_000_000 socket_path = os.environ.get('WAYFIRE_SOCKET', '') if socket_path and os.path.exists(socket_path): @@ -222,16 +225,39 @@ class WayfireWindows: try: views = sock.list_views() wins = [] - taskbar = None + panel_rects = [] for v in views: g = v.get('geometry', {}) x, y = g.get('x', 0), g.get('y', 0) w, h = g.get('width', 0), g.get('height', 0) role = v.get('role', '') - # Only toplevel windows count as landing surfaces; - # skip layer-shell panels/backgrounds (they can hide the sheep) - if role == 'toplevel' and w > 10 and h > 10: + layer = v.get('layer', '') + # Toplevel windows count as landing surfaces — but only + # if their top edge is actually on the visible screen. + # This compositor's wide-viewport layout reports windows + # from other workspaces with raw/absolute geometry, so a + # window can have x within [0, screenW] while its y sits + # off the top of the current monitor entirely; landing a + # sheep on such a "floor" puts it above y=0 — invisible. + if (role == 'toplevel' and w > 10 and h > 10 + and 0 <= y <= self._screen_h): wins.append((x, y, w, h)) + # Desktop panels/bars (waybar, etc.) anchored to a + # top/bottom layer act as the "taskbar" landing surface. + # Filter out tall/narrow docks — a taskbar is a wide + # horizontal strip, not a sidebar. + elif (role == 'desktop-environment' and + layer in ('top', 'bottom') and w > h): + panel_rects.append((x, y, w, h)) + + taskbar = None + if panel_rects: + px0 = min(r[0] for r in panel_rects) + py0 = min(r[1] for r in panel_rects) + px1 = max(r[0] + r[2] for r in panel_rects) + py1 = max(r[1] + r[3] for r in panel_rects) + taskbar = (px0, py0, px1 - px0, py1 - py0) + with self._lock: self._windows = wins self._taskbar = taskbar @@ -251,6 +277,9 @@ class WayfireWindows: with self._lock: return self._taskbar + def set_screen_size(self, screen_h: int): + self._screen_h = screen_h + # ─────────────────────── Sheep state machine ─────────────────────── class Sheep: @@ -384,11 +413,16 @@ class Sheep: # Collect all candidate floors (surfaces the sheep could land on) all_floors = [(float(screen_h), COND_NONE)] - # Taskbar (treat as window) + # Taskbar (treat as window) — stand on whichever edge faces the + # screen interior. A bottom-anchored bar: stand on its top edge + # (ty), same as a window. A top-anchored bar (as on this desktop): + # standing on its "top" would be above y=0, off-screen — instead + # stand just below it, on its bottom edge (ty + th). if taskbar: tx, ty, tw, th = taskbar if tx < self.x + self.tile_w - 4 and tx + tw > self.x + 4: - all_floors.append((float(ty), COND_TASKBAR)) + floor_y = (ty + th) if ty <= 2 else ty + all_floors.append((float(floor_y), COND_TASKBAR)) # Window tops – only windows that horizontally overlap the sheep for (wx, wy, ww, wh) in windows: @@ -738,6 +772,7 @@ class SheepOverlay: self._flowers: List[Flower] = [] self._eat_countdown = random.uniform(30, 90) # seconds self._hop_countdown = random.uniform(45, 90) # seconds + self._bath_countdown = random.uniform(60, 150) # seconds self._interact_cooldowns: Dict[Tuple[int,int], float] = {} # ms self._frozen_sheep: Dict[int, float] = {} # id→remaining ms @@ -817,6 +852,7 @@ class SheepOverlay: geo = mon.get_geometry() self.screen_w = geo.width self.screen_h = geo.height + self.wayfire.set_screen_size(self.screen_h) print(f"Screen: {self.screen_w}×{self.screen_h}", flush=True) # Spawn initial sheep @@ -1023,6 +1059,22 @@ class SheepOverlay: chosen._init_anim(5) print("Sheep small hop!", flush=True) + # ── bath (batha→bathb→bathc→bathd→rotate1b→walk) ── + # Normally only reachable via a ~3% spawn roll; trigger it + # periodically too so the sequence is actually seen in practice. + self._bath_countdown -= dt / 1000.0 + if self._bath_countdown <= 0: + self._bath_countdown = random.uniform(90, 180) + bath_candidates = [s for s in self.sheep_list + if s is not self._drag_sheep + and s.floor_cond == COND_NONE + and s.anim_id in (1, 7, 35, 36, 49, 50) + and not any(u.sheep is s for u in self.ufos)] + if bath_candidates: + chosen = random.choice(bath_candidates) + chosen._init_anim(21) + print("Sheep is taking a bath!", flush=True) + # ── bumps & greetings ── self._check_interactions(dt) -- cgit v1.2.3