diff options
| author | Ken D'Ambrosio <ken.dambrosio@constantcontact.com> | 2026-07-11 23:03:10 -0400 |
|---|---|---|
| committer | Ken D'Ambrosio <ken.dambrosio@constantcontact.com> | 2026-07-11 23:03:10 -0400 |
| commit | 56856c7490d44beab065fdc2510cae33b973ccbc (patch) | |
| tree | 4b37b4c1991d08ba01f52a0f8dc7dbe2177df99b /esheep.py | |
| parent | 6f5864ae666f80641c22cebe274802648b12d7f4 (diff) | |
Fix taskbar detection and off-screen floor landing; add periodic bathmaster
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.
Diffstat (limited to 'esheep.py')
| -rw-r--r-- | esheep.py | 64 |
1 files changed, 58 insertions, 6 deletions
@@ -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) |
