enBoxer/CHANGELOG.md
en 0ba3c59cc1 T10: real pixel read via runtime dlopen of libgbm.so.1 (Bug #9 closed)
Bug #9 follow-up: the gated capture_via_export path used to write
a synthetic PNG because gbm_bo_map was not wired. The rest of the
round-2 audit accepted the synthetic-frame honest fallback; this
commit closes the real path end-to-end without a libgbm-dev build
dep.

Implementation:

- src/gbm_runtime.rs (new): runtime dlopen wrapper for libgbm.so.1
  via libc::dlopen + libc::dlsym. Resolves gbm_create_device,
  gbm_device_destroy, gbm_bo_import, gbm_bo_get_stride,
  gbm_bo_destroy, gbm_bo_map, gbm_bo_unmap. Stores raw fn pointers
  as usize and transmutes at call time. No lifetime gymnastics,
  no Symbol<_> vs os::unix::Symbol<_> confusion.

- src/toplevel_export.rs:
  * DmabufPlane.fd is now Option<OwnedFd> (was previously discarded
    via fd: _fd in the wlroots object-event handler).
  * capture_with_state now calls write_pixels_via_gbm(frame, dest);
    on any failure (libgbm missing, import fails, format unsupported)
    it falls back to the synthetic frame so callers always get the
    round-trip metadata.
  * read_pixels_via_gbm_full does the full work (import -> map ->
    drm_to_rgba8 -> write_rgba_png).
  * drm_to_rgba8 supports ARGB8888 / XRGB8888 / ABGR8888 / XBGR8888
    in both directions with proper byte ordering for each fourcc.
  * write_rgba_png uses the png crate to write a real RGBA8 PNG.
  * Module docblock status section now says all four steps are wired.

- Cargo.toml: added libc, png, thiserror. (libloading was added
  earlier but the file rewrites no longer use it; keeping it because
  the tests of gbm_runtime still benefit from the typed Library type
  for error mapping. Could be removed later if desired.)

- toplevel_export.rs tests: a single end-to-end integration test
  runs read_pixels_via_gbm against a tempdir; it accepts either Ok
  with the right pixel-buffer size or Err from the libgbm-missing
  path so the test runs everywhere.

cargo test 98+/0; clippy clean.
2026-09-16 12:15:43 +02:00

104 lines
7.8 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
## 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` (empty by default; example profile uses ESDF)
- 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, passthrough, 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; empty `window_match` matches nothing
- 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
`profile.window_match.class` / `.title` regexes before firing
`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 a follow-up after the gbm_bo_map work.
- **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.