Split the chmod helper so tests can exercise it on a private tempdir
instead of mutating the user's XDG_RUNTIME_DIR. The thin
chmod_runtime_dir() wrapper still picks runtime_dir() for the production
path (called from session.rs).
- set_margin (wayland_layer.rs:234): correct arg order to
(top, right, bottom, left). The previous code passed rect.x as
the right margin, a no-op under TOP+LEFT anchoring, so the
overlay's x offset was silently dropped. Comment updated to
document the protocol's true shape.
- click handler (wayland_layer.rs:466): drop 'state.exited = true'
after send_swap. Flipping exited destroyed the badge but 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 drives thread teardown — the click now
just fires the swap IPC and returns.
- CHANGELOG entries.
cargo test 96+/0; clippy clean.
- arm_auto_apply (gui.rs ~1625): compile profile.window_match.class and
.title regexes once, then poll hyprctl for a client that matches
before firing layout-apply. Fall back to 'first non-empty class'
when neither pattern is configured, so existing profiles keep working.
Closes the security/correctness gap where any open window could trigger
layout-apply against the desktop.
- New-team wizard (gui.rs ~1438): profile.characters.truncate is replaced
with resize(slots, Character::default()) so a wizard with N members
and zero existing characters now actually gets N Launch buttons.
Character gains #[derive(... Default)].
- CHANGELOG entries.
cargo test 96+/0; clippy clean.
fix(security): the daemon's IPC socket was world-accessible. Any local
user could speak the IPC protocol and type arbitrary text into game
windows via Command::Type.
- src/profile.rs: add chmod_runtime_dir() and chmod_socket(path) helpers
- src/session.rs: call chmod_runtime_dir() after create_dir_all, and
chmod_socket() right after UnixListener::bind succeeds
- Tests: chmod_socket_sets_0o600, chmod_runtime_dir_sets_0o700 (saved/
restored live dir perms to avoid clobbering a real session)
- CHANGELOG.md: security note
cargo test 96+/0; clippy clean.
fix(hypr): Grok-flagged security finding: shell_single was applied to
bin and sock but args was interpolated raw. A profile hotkey containing
shell metacharacters could become a Hyprland exec_cmd injection.
- src/hypr.rs: bind_command now wraps args in shell_single
- New tests:
- bind_command_quotes_args_with_metacharacters
- shell_single_handles_inner_quote
- Doc comment updated to name the threat and what gets escaped
93+/0 cargo test; clippy clean.
bind: "interact" now resolves to game_binds.interact (Alt+J) only —
a single keystroke. The full ISBoxer-style chain (CTM on -> Alt+J ->
sleep walk_delay_ms -> CTM off) lives at bind: "smart_interact".
Fixes the doubling bug Grok flagged: bind: "interact" previously
expanded into the full chain unconditionally, so the loot_manual and
interact_hold example maps fired ctm_on/ctm_off twice and produced
unwanted sleep delays.
- engine.rs: rename shortcut trigger (was: "interact")
- examples/profile.yaml: loot now uses smart_interact; loot_manual and
interact_hold keep "interact" as a single Alt+J send
- docs/MACROS.md: heading + shortcut comment
- docs/NOTES.md: trigger name
- Add new test: interact_simple_sends_only_alt_j (1-line single-send)
- Existing smart shortcut tests renamed to bind: smart_interact
- CHANGELOG.md: trigger split note
93/93 cargo test pass; clippy clean.
A step with bind: interact now expands at compile time into the full chain
(CTM on -> Interact with Target -> sleep walk_delay_ms -> CTM off), 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; bind: ctm_off in
release_steps to fire it on hotkey release
Example loot (Alt+G) and interact (Alt+I) maps now use the shortcut. A
loot_manual map (Ctrl+Alt+G) exercises the explicit chain for users who
want per-step control.
92/92 unit tests pass; clippy clean. New tests:
- interact_smart_shortcut_standard_emits_full_sequence
- interact_smart_shortcut_auto_emits_two_no_tail
- interact_smart_shortcut_hold_emits_press_only
Docs: MACROS.md (new Smart interact shortcut section with style table),
NOTES.md callout, examples/profile.yaml comments, CHANGELOG.md entry.
Hyprland binds called `ipc` twice so mapped keys never reached the daemon.
Overlays now use mpv with wayland-app-id and JSON reload. Empty window_match
matches nothing. Ctrl-c clears binds. Proven: send_shortcut to unfocused
XWayland, mpv overlay class enboxer-vfx.