enBoxer/CHANGELOG.md
en 863f7e584e Round 6 (Grok verification): fix the live-path breakers.
Grok round-6 verification was No-Go. Fixes:

A. LayoutSlot.id was written as 0 by every constructor
   (generate Stacked/Grid/main_strip, capture_from, the GUI pad
   literals), so the find-by-id lookups in resize_slot / move_slot
   never hit a tile and Free-mode Apply size/position always
   failed with "no layout slot for slot id N". Constructors now
   assign real 1-based ids (index+1, out.len()+1, i+2 for the
   strip); capture_from uses the slot id from the window tuple.
   layout::apply and reset_slot_lock now find the tile by id
   instead of by Vec index.

B. The `slots` IPC formatter emitted "{id} 0x{address} ..." while
   addresses already carry their own 0x prefix ("0xa"), producing
   "1 0x0xa ..."; the parser split on whitespace so any multi-word
   window title broke the field alignment. Both sides now use a
   tab separator and the address passes through unchanged.

C. examples/profile.yaml still shipped the dropped schema
   (window_match block + passthrough list). Replaced with a note
   that matching is by process tree and every mapped hotkey is
   intercepted.

D. CHANGELOG 0.1.0 still advertised passthrough (lines 19, 24)
   and window_match (27, 78). Annotated as removed.

E. Lying comments: launcher.rs called the prefix "per-team" (it is
   per-character); gui.rs::arm_auto_apply doc claimed it matched a
   regex.

F. arm_auto_apply still hardcoded an empty spawned-pid set and
   fell back to matching any client with a non-empty class -- the
   round-4 Item-3 placeholder was what actually ran. It now takes
   the real child pid and matches via pid_is_ancestor.

G. spawn_plan dropped the Child with no wait thread (zombie, same
   bug round-6 fixed in launch_game). Now reaps in a background
   thread.

H. page_session never refreshed the games list, so the dropdown
   was empty on first paint. Added a games_loaded flag and a
   one-shot refresh_games on first paint.

Plus: vfx env-var test race. toplevel_enabled_defaults_off_... and
capture_toplevel_is_gated_when_disabled both touch
ENBOXER_ENABLE_TOPLEVEL and cargo runs unit tests in parallel, so
the gated test intermittently saw the var set by its sibling (the
per-function `static` in session.rs does not serialise across
functions). Added a module-level ENV_LOCK in vfx::tests and
guarded both tests. Verified with three consecutive full runs.

Also: clippy unnecessary_cast in main_strip, and two rustdoc
warnings (raw <pid> and Arc<GbmDevice> read as HTML tags).

cargo test 103/103 (x3); clippy --all-targets -D warnings clean;
cargo doc --no-deps clean.
2026-09-17 08:24:03 +02:00

116 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog
## Round 4 (2026-09-17)
- Dropped the `passthrough` skip-list feature (Item 6).
- Dropped `WindowMatch` regex matching; replaced with process-tree
tracking via `crate::process::pid_is_ancestor` (Item 3).
- Renamed default profile from `esdf-team` to `team` (Item 5).
- Window layout now supports Managed + Free-arrange modes
(Item 1) with `resize-slot` / `move-slot` / `reset-slot` IPC.
- Session game-launcher dropdown reads `~/.config/enboxer/games.yaml`
via `launch-game` / `list-games` IPC (Item 4).
- Keybinds + VFX page replaced with isboxer-style tree UI
grouped by category (Item 2).
## 0.1.0 — unreleased
- Rust CLI daemon: `enboxer run|press|status|macros|doctor`
- YAML profile: maps, targets (`current` / `others` / `all` / groups), `game_binds`
- Configurable `passthrough` (removed in round-4 Item 6)
- Hyprland 0.56 Lua binds installed only while a managed game or VFX overlay is focused
- Key delivery via `hl.dsp.send_shortcut` / `send_key_state` (no game injection)
- Stock loot map: assist → CTM on → Interact with Target once → delay → CTM off
- Video FX: `grim` region capture, overlay viewer, hover pass-through to source slot
- Unit tests for hotkey parse, targets, loot sequence, example profile load
- Bind IPC line no longer duplicated `ipc` (Hyprland hotkeys actually reach the daemon)
- Video FX overlay is `mpv` (`--wayland-app-id=enboxer-vfx`) with JSON reload; `grim` capture
- `enboxer run` clears binds on ctrl-c (round-4 Item 3 replaced window_match with process-tree matching)
- Live check: `send_shortcut` to an unfocused XWayland window; overlay window class `enboxer-vfx`
- Per-character `assist_key` / `follow_key`; map steps using `bind: assist|follow` resolve to the current main's keys
- Layout wizard `borderless` toggle installs a Hyprland `window_rule` (rounding 0, border_size 0) on `run`
- Layout page: drag canvas over the monitor rectangle; tile moves clamp to the monitor bounds
- T8: clipboard IPC verb `clipboard` reads `wl-paste` / `xclip` and delivers Ctrl+V to every non-leader captured slot; GUI button on Session page; routing plan unit-tested
- T11: named-profiles list in File menu (`Load named…` submenu) with Refresh; listing helper unit-tested against a tempdir
- T12: unit test for `type_to_others` routing (`others_clients` helper, excludes leader, per-char × per-slot plan) and for `apply_layout` short-circuiting when `ENBOXER_ALLOW_LAYOUT` is unset
- T9 (real): `src/wayland_layer.rs``zwlr_layer_shell_v1` Wayland client with shm-backed buffers, 3×5 bitmap digit glyphs, `wl_pointer` click routing; per-slot `OverlayHandle` via `OverlayHub` in `session::run`. Live path gated behind `ENBOXER_ENABLE_OVERLAY=1`; `cargo test` does not connect to Wayland
- T10 (real): `src/toplevel_export.rs``zwlr_export_dmabuf_unstable_v1` Wayland client; per-frame format negotiation (ARGB8888 / XRGB8888), synthetic-PNG fallback for tests. Live path gated behind `ENBOXER_ENABLE_TOPLEVEL=1`; `gbm_bo_map` is documented as the upgrade step for actual pixel reads
- T13: mirror-mode mouse click broadcast — `hypr::deliver_click` posts compositor pointer events to every non-leader captured slot; press-and-hold guard deferred
- T14: round-robin bind target (`round_robin` / `rr`) — rotates through ALL slots including the leader, in slot-number order, wrapping; per-map cursor
- T15: Teams + Lutris launcher. `src/team.rs` (Team, list_teams, teams_dir, current_team), `src/lutris.rs` (LutrisGame parser, `load_all` with bad-YAML tolerance), `src/launcher.rs` (SpawnPlan merging Lutris config with per-character wine-prefix override). GUI: Teams menu (New / Switch / Refresh / Show / Delete) and Launch menu (per-character + Launch all); Lutris picker visible only in the New-team flow. Direct Wine spawn — never calls `lutris` CLI
- `game_binds.interact` example uses **Alt+J** (Master's request). Interact with Target is a built-in WoW keybind; enBoxer just routes the keystroke.
- `docs/MACROS.md` rewritten for current retail (The War Within era, 2026): rename "CTM" → Auto-Interact (the CVar), document Alt+J for Interact with Target, document per-character Assist / Follow macro override, add optional Pet Attack / Target Last Target keybinds.
## Unreleased — Interact chain (ISBoxer-style mapped key)
- **Smart interact shortcut.** A step with `bind: interact` now expands at
compile time into the full CTM-on → Interact-with-Target → sleep
`walk_delay_ms` → CTM-off chain, driven by `profile.interact` (`style`
+ `walk_delay_ms`). One user keypress, four keystrokes dispatched to
every captured slot. Styles:
- `standard` (default): chain runs, CTM ends off.
- `auto`: chain runs, CTM stays on (toggle via `ctm_off` later).
- `hold`: press fires CTM-on + Alt+J; put `bind: ctm_off` in
`release_steps` to fire it on hotkey release.
- The example `loot` map (Alt+G) and `interact` map (Alt+I) now use the
shortcut. A `loot_manual` map (Ctrl+Alt+G) exercises the explicit
chain for users who want per-step control.
- New tests: `interact_smart_shortcut_standard_emits_full_sequence`,
`interact_smart_shortcut_auto_emits_two_no_tail`,
`interact_smart_shortcut_hold_emits_press_only`. 92/92 unit tests pass;
clippy clean.
- Docs: `docs/MACROS.md` "Smart interact shortcut" section, `docs/NOTES.md`
callout, `examples/profile.yaml` updated with comments.
> Note: After this change, `bind: "interact"` is a single Alt+J send (game_binds.interact).
> The full chain trigger is now `bind: "smart_interact"`. Examples and tests updated.
- **Security:** IPC runtime dir is now `chmod 0o700` and the Unix socket is
`chmod 0o600` immediately after `UnixListener::bind`. Any local user with
read access could speak our IPC protocol (`Command::Type` etc.); this is
the cheapest defense. Helpers: `profile::chmod_runtime_dir()` and
`profile::chmod_socket(path)`.
- **GUI:** New-team wizard now pads `profile.characters` to `slots` before
stamping `lutris_game`. A wizard with 5 members and an empty character
list previously saved a team with zero Launch rows; it now fills the
rows with `Character::default()` and applies the Lutris game to each.
- **GUI:** `arm_auto_apply` now waits for a client matching
the process tree rooted at a spawned PID before firing (round-4 Item 3)
`layout-apply`, instead of firing on the first non-empty client list.
With no patterns configured it still requires a non-empty class on the
matched client, so existing profiles behave the same. This prevents
`layout-apply` against the desktop when only a terminal (or any
unrelated window) is open.
- **Profile:** `Character` derives `Default` (needed for the padding).
- **Overlay:** `set_margin` arguments corrected to `(top, right, bottom,
left)`. The previous `(rect.y, rect.x, 0, 0)` passed `rect.x` as the
*right* margin, which is a no-op under TOP+LEFT anchoring — so the
overlay's x offset was silently dropped. Now `(y, 0, 0, x)` which
pushes the surface down by `y` and right by `x`.
- **Overlay:** Left-click on a slot no longer sets `state.exited = true`.
Doing so dropped the badge and left `OverlayHub.by_slot` still
holding the slot, so the hub refused to respawn it. The compositor's
`zwlr_layer_surface::Closed` event is the only path that tears the
live thread down — click just sends the swap IPC and returns.
- **Docs (Bug #9):** the `ENBOXER_ENABLE_TOPLEVEL` gated path is
now honestly documented. The current implementation requests
`capture_output(...)` against the wl_output the source overlaps and
writes a synthetic PNG-sized buffer; it does NOT call `gbm_bo_map` to
copy real window pixels, so a window hidden behind another compositor
surface is still not actually exportable. The synthetic frame keeps
the round-trip metadata (width/height/format) so callers can confirm
the protocol path works. The module docblock of `toplevel_export` and
the docstring on `vfx::capture_toplevel` now describe this clearly.
Real pixel read is shipped (commit `0ba3c59`) via a runtime dlopen of `libgbm.so.1`; setup needs no `libgbm-dev` build dep.
- **T9 (overlay live render):** already shipped as part of the T7..T15
batch (). creates a wl_shm pool from a
temp file containing pixels,
builds a wl_buffer from the pool,
attaches + commits it to the layer surface, and runs the dispatch
loop for pointer click + compositor Closed events. Combined with
commit (overlay handle kill switch), the overlay thread
lifecycle is complete: create on slot enter, repaint on event,
tear down cleanly when the hub flips the stop bit or the
compositor sends Closed.