enBoxer/README.md
en bb6d9e8566 Drop stale stub/follow-up/not-shipped markers in docs
After T10 shipped (commit 0ba3c59) several docs still claimed the
gbm_bo_map / ENBOXER_ENABLE_TOPLEVEL path was a stub, follow-up, or
"/dev/dri/renderD128 + new dep" work that never happened. Rewrite:

- README.md: ENABLE_TOPLEVEL row no longer says "unset = stub".
- docs/VIDEO.md: live toplevel-export is real (gbm_bo_map via
  runtime dlopen of libgbm.so.1, real RGBA8 PNG via the png crate).
- CHANGELOG.md: T10 follow-up entry rewritten as shipped.
- docs/MACROS.md: stale "Forever is not out yet / later helper
  (not shipped)" paragraph rewritten to point users at the
  per-character GUI rebind instead.
2026-09-16 12:43:33 +02:00

10 KiB

enBoxer

Mapped-key routing and live window crops for Arch Linux + Hyprland. Rust rewrite of a Windows multibox controller — the project layout, slot overlay, and key-routing engine follow the same shape, but nothing game-side is touched. Keys and clicks only. Pushes to https://gitea.nettsi.de/en/enBoxer over HTTPS only.

You list which hotkeys go where. Everything you do not list stays on the focused primary. A passthrough list (the example uses ESDF) leaves movement keys alone. Alt-tab off the team and the desktop is normal.

Nothing is loaded into the game. Hyprland delivers keys with hl.dsp.send_shortcut / send_key_state. You bind macros in WoW yourself.

Routing (three modes)

Toggle with Shift+Alt+M (configurable) or enboxer mode maps|mirror|off while the daemon runs. A Hyprland notification shows the new mode.

  1. maps — only keys you listed under maps are intercepted and sent where the map says. Everything else goes to the front window.
  2. mirror — the front window still gets the real key; the same key is cloned to the other game windows. passthrough (e.g. ESDF) is not cloned.
  3. off — nothing is intercepted. All keys go to the front window. The mode-toggle hotkey still works.

A map is a hotkey plus steps (send a key, send a named game_bind, wait) plus a target.

Targets:

Target Meaning
current The leader slot only
others Every captured slot except the leader
all Every captured slot including the leader
group:<name> Slots listed in groups.<name>
slot:<n> One specific slot
slots:<n>,<m>,… An explicit list
round_robin / rr Each press advances to the next slot (1→2→3→…→1, includes the leader, wrapping). State is per-map.

Mode-extras

  • Mouse click broadcast (mirror). When mode is mirror, left and right clicks on the primary are also cloned to every other captured slot via the Hyprland compositor (cursor stays where you put it). Wired through the same compositor path as the keys. No press-and-hold guard yet — TODO.
  • Clipboard broadcast. Session → Send clipboard to others (Ctrl+V) copies the current clipboard (wl-paste then xclip) and pastes into every non-leader captured slot.
  • Round-robin example. See examples/profile.yamlrez_cycle: pressing Alt+R fires F8 to slot 1, then slot 2, then slot 3, wrapping back to 1 on the next press. Each map gets its own cursor.

Interact / loot

WoW's Interact with Target bind (plus Click to Move) walks an alt to the target and loots or talks. One press. Spamming it makes characters circle. The stock loot map: assist others → CTM on → interact once → wait walk_delay_ms → CTM off. See docs/MACROS.md.

Per-character assist_key / follow_key resolve /assist ThisName / /follow ThisName against the current main's slot. Update the name in examples/profile.yaml per team.

Window layout

The layout wizard builds a tile grid for N captured game clients:

  • Presets: stacked, same-size grid, main + strip (one large main plus a row of minions).
  • Per-slot x y w h in pixels.
  • Drag tiles directly on the GUI canvas — the layout page shows a draggable, clickable monitor rectangle per output.
  • Generate fills in defaults from the selected preset; Capture current positions reads hyprctl -j clients and stores whatever is on screen.
  • Save and Apply calls enboxer layout-apply and uses hyprctl dispatch to float / move / resize. Gated by ENBOXER_ALLOW_LAYOUT=1 (env or GUI checkbox on Session) and a confirm dialog before apply.
  • Swap next / Make main rotates which slot is in the large tile. Reset all restores the saved layout.
  • Stay on top pins the new tiles. Borderless installs a Hyprland window_rule (rounding 0, border_size 0) for the captured class when the daemon starts.

Slot overlay (click-to-swap)

When ENBOXER_ENABLE_OVERLAY=1 is set in the daemon's environment, enboxer run spawns a wlr-layer-shell surface per captured slot showing the slot number, anchored to the slot's top-left. Clicking the badge calls swap <slot> so you can change which client is main without alt-tabbing. With the env unset (default), cargo test and enboxer doctor never connect to Wayland. See docs/DESIGN.md.

Video FX

A region of another client is captured and shown on the primary. While the cursor is over the viewer, keys and clicks go to the source slot only.

Two capture paths:

  1. Visible source — grim (default). Fast, zero-copy when a source window sits on a visible output. This is what mpv reloads each frame.
  2. Covered source — zwlr_export_dmabuf_unstable_v1 (when ENBOXER_ENABLE_TOPLEVEL=1). Lets you stack clients fully covered and still pull their pixels through the compositor. The dispatcher picks grim when the source is on a visible output and falls through to dmabuf export when it's covered. The dmabuf pixel-read path uses a documented gbm_bo_map upgrade step — see src/toplevel_export.rs for the protocol + format negotiation layer that is in place.

See docs/VIDEO.md.

Teams and the Lutris launcher

Multiple teams can live side-by-side. Each team is a separate YAML in ~/.config/enboxer/teams/<slug>.yaml; the active team is recorded in ~/.config/enboxer/current_team. Use the GUI Teams menu (top bar) to:

  • New team… — opens a wizard with name, member count, and a Lutris game picker. The Lutris picker is visible only in this flow.
  • Switch team… — load any saved team.
  • Refresh Lutris for current team — re-read ~/.config/lutris/games/<slug>.yml so changes Lutris made to the runner / env / DLL overrides take effect on the next launch.
  • Show Lutris config for current team — display the parsed runner / exe / env / prefix / DLL overrides for transparency.
  • Delete team… — removes the team YAML. Does not touch Lutris.

The Launch menu (under Session) shows one button per character that has a lutris_game set, plus Launch all. enBoxer spawns Wine directly with the merged env (WINEPREFIX, WINEDLLOVERRIDES, WINEESYNC, WINEFSYNC, DXVK_*, VKD3D_CONFIG) — never the lutris CLI, because Lutris is not built for launching the same game five times.

Optional auto_apply: true on a character polls hyprctl -j clients for up to 30 s after spawn and runs layout-apply once the matched window appears. Off by default. The auto-apply path is the only code that touches window geometry after Launch; everything else stays read-only until you click Save and Apply in the GUI.

Build

cd ~/git/enBoxer
cargo build --release
install -Dm755 target/release/enboxer ~/.local/bin/enboxer

Runtime deps on the host: Hyprland 0.56 (Lua dispatchers), hyprctl, grim, mpv (Video FX overlay), slurp (rectangle picker), wl-paste and xclip (clipboard broadcast). For the Lutris launcher: a working wine on PATH and the user's Lutris install.

For the optional live paths: the Wayland protocol bindings (wayland-client 0.31, wayland-protocols-wlr 0.3.12) are already pulled in by Cargo.toml.

Use

enboxer                       # control panel (File / Session / Layout / Maps / Video / Macros / Teams)
enboxer run -c ~/.config/enboxer/profile.yaml     # daemon only
enboxer doctor
enboxer press Alt+G            # fire a map without a Hyprland bind
enboxer layout-apply -c ...    # one-shot layout apply (gated by ENBOXER_ALLOW_LAYOUT)

The GUI's Teams menu is the first thing to use when you have not configured a team yet. Use New team… to pick a Lutris game and member count. The Launch menu then has one button per character.

ENBOXER_ALLOW_LAYOUT=1 (env) or the Session checkbox is required for Save and Apply, swap-geometry, and reset.

Environment variables (gates)

Var Effect Default
ENBOXER_ALLOW_LAYOUT Permit layout-apply, swap, reset unset = off
ENBOXER_ENABLE_OVERLAY Spawn the live wlr-layer-shell slot overlay unset = stub (geometry + routing only)
ENBOXER_ENABLE_TOPLEVEL Spawn the live wlr_export_dmabuf_unstable_v1 capture path unset = grim fallback

cargo test and enboxer doctor never set these gates, so they cannot touch the user's Hyprland session.

Files

Path Purpose
~/git/enBoxer The Rust crate
examples/profile.yaml Stock profile (ESDF passthrough, main+strip layout, stock loot map, round-robin example)
examples/hyprland.lua Hyprland bind helper (snippet, optional)
~/.config/enboxer/profile.yaml Active profile (single-profile legacy / no-team fallback)
~/.config/enboxer/teams/<slug>.yaml One team = one file
~/.config/enboxer/current_team One-line file with the active team's slug
~/.config/lutris/games/*.yml Lutris game store (read by the launcher)

Safety (non-negotiable)

  • Default enboxer run: keys only. No float / move / resize / pin.
  • ENBOXER_ALLOW_LAYOUT=1 is required for apply / swap-geometry / reset. The GUI confirms before Apply.
  • The example profile has auto_apply: false, Video FX disabled, and empty layout tiles.
  • No DLL, no memory inject, no required WoW addon in v1.
  • cargo test, enboxer doctor, and the un-gated stub paths do not connect to Wayland or move windows.

Docs

Doc What
GOALS.md What this project is for (read this first)
AGENTS.md Rules for Grok / OpenCode
PLAN.md Shipped vs next
DESCRIPTION.md One-page description
AGENT.md Designer / PM / worker contract
TASKS.md OpenCode tickets
CHANGELOG.md What landed
docs/DESIGN.md Routing model and overlay capture model
docs/NOTES.md WoW interact / Hyprland capture
docs/VIDEO.md Live crop of another client
docs/MACROS.md In-game macros

License

GPL-3.0-or-later.