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

8.5 KiB
Raw Blame History

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.rszwlr_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.rszwlr_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.