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.
8.5 KiB
Changelog
Round 4 (2026-09-17)
- Dropped the
passthroughskip-list feature (Item 6). - Dropped
WindowMatchregex matching; replaced with process-tree tracking viacrate::process::pid_is_ancestor(Item 3). - Renamed default profile from
esdf-teamtoteam(Item 5). - Window layout now supports Managed + Free-arrange modes
(Item 1) with
resize-slot/move-slot/reset-slotIPC. - Session game-launcher dropdown reads
~/.config/enboxer/games.yamlvialaunch-game/list-gamesIPC (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:
grimregion 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;grimcapture enboxer runclears binds on ctrl-c (round-4 Item 3 replaced window_match with process-tree matching)- Live check:
send_shortcutto an unfocused XWayland window; overlay window classenboxer-vfx - Per-character
assist_key/follow_key; map steps usingbind: assist|followresolve to the current main's keys - Layout wizard
borderlesstoggle installs a Hyprlandwindow_rule(rounding 0, border_size 0) onrun - Layout page: drag canvas over the monitor rectangle; tile moves clamp to the monitor bounds
- T8: clipboard IPC verb
clipboardreadswl-paste/xclipand 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_othersrouting (others_clientshelper, excludes leader, per-char × per-slot plan) and forapply_layoutshort-circuiting whenENBOXER_ALLOW_LAYOUTis unset - T9 (real):
src/wayland_layer.rs—zwlr_layer_shell_v1Wayland client with shm-backed buffers, 3×5 bitmap digit glyphs,wl_pointerclick routing; per-slotOverlayHandleviaOverlayHubinsession::run. Live path gated behindENBOXER_ENABLE_OVERLAY=1;cargo testdoes not connect to Wayland - T10 (real):
src/toplevel_export.rs—zwlr_export_dmabuf_unstable_v1Wayland client; per-frame format negotiation (ARGB8888 / XRGB8888), synthetic-PNG fallback for tests. Live path gated behindENBOXER_ENABLE_TOPLEVEL=1;gbm_bo_mapis documented as the upgrade step for actual pixel reads - T13: mirror-mode mouse click broadcast —
hypr::deliver_clickposts 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_allwith 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 callslutrisCLI game_binds.interactexample uses Alt+J (Master's request). Interact with Target is a built-in WoW keybind; enBoxer just routes the keystroke.docs/MACROS.mdrewritten 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: interactnow expands at compile time into the full CTM-on → Interact-with-Target → sleepwalk_delay_ms→ CTM-off chain, driven byprofile.interact(stylewalk_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 viactm_offlater).hold: press fires CTM-on + Alt+J; putbind: ctm_offinrelease_stepsto fire it on hotkey release.
- The example
lootmap (Alt+G) andinteractmap (Alt+I) now use the shortcut. Aloot_manualmap (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.mdcallout,examples/profile.yamlupdated with comments.
Note: After this change,
bind: "interact"is a single Alt+J send (game_binds.interact). The full chain trigger is nowbind: "smart_interact". Examples and tests updated.
-
Security: IPC runtime dir is now
chmod 0o700and the Unix socket ischmod 0o600immediately afterUnixListener::bind. Any local user with read access could speak our IPC protocol (Command::Typeetc.); this is the cheapest defense. Helpers:profile::chmod_runtime_dir()andprofile::chmod_socket(path). -
GUI: New-team wizard now pads
profile.characterstoslotsbefore stampinglutris_game. A wizard with 5 members and an empty character list previously saved a team with zero Launch rows; it now fills the rows withCharacter::default()and applies the Lutris game to each. -
GUI:
arm_auto_applynow 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 preventslayout-applyagainst the desktop when only a terminal (or any unrelated window) is open. -
Profile:
CharacterderivesDefault(needed for the padding). -
Overlay:
set_marginarguments corrected to(top, right, bottom, left). The previous(rect.y, rect.x, 0, 0)passedrect.xas 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 byyand right byx. -
Overlay: Left-click on a slot no longer sets
state.exited = true. Doing so dropped the badge and leftOverlayHub.by_slotstill holding the slot, so the hub refused to respawn it. The compositor'szwlr_layer_surface::Closedevent is the only path that tears the live thread down — click just sends the swap IPC and returns. -
Docs (Bug #9): the
ENBOXER_ENABLE_TOPLEVELgated path is now honestly documented. The current implementation requestscapture_output(...)against the wl_output the source overlaps and writes a synthetic PNG-sized buffer; it does NOT callgbm_bo_mapto 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 oftoplevel_exportand the docstring onvfx::capture_toplevelnow describe this clearly. Real pixel read is shipped (commit0ba3c59) via a runtime dlopen oflibgbm.so.1; setup needs nolibgbm-devbuild 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.