Commit Graph

13 Commits

Author SHA1 Message Date
en
8cb96c1f25 Item 1 (Grok round 4): window layout manager rewrite + free-arrange mode.
Master wanted two modes:
- Managed (existing wizard behaviour): auto-arrange grid via the
  layout wizard.
- Free-arrange: launch windows without applying layout. Per-window
  initial resolution. Apply-size button (not position). Daemon does
  NOT keep overwriting resolution if operator manually resized.

Reference: KWin scripts apply-size / apply-position for the live
resize plumbing.

Implementation:

src/profile.rs:
  * LayoutMode enum: Managed (default) | Free. Serde round-trips
    as "managed" / "free".
  * Layout.mode field added (default = Managed via LayoutMode derive).
  * LayoutSlot split into initial_size (Option<(u32,u32)>),
    pos (Option<(i32,i32)>), size_locked (bool). The legacy x/y/w/h
    fields stay for Managed mode (cached geometry).

src/hypr.rs:
  * move_resize_window split into resize_window(w, h) + move_window(x, y).
  * move_resize_window kept as a thin wrapper so the original
    layout-apply IPC path still works.

src/layout.rs:
  * apply(slots, windows) split into apply_size + apply_pos; apply()
    is now the thin wrapper that calls both.
  * All 4 LayoutSlot literals (Stacked preset, Grid helper, main_strip,
    capture_from) gained ..Default::default().

src/session.rs:
  * New IPC verbs: resize-slot N, move-slot N, reset-slot N.
  * resize_slot: applies initial_size (or cached w/h), sets
    size_locked=true so the daemon does not undo future operator
    resizes.
  * move_slot: applies pos (or cached x/y).
  * reset_slot_lock: clears size_locked on slot-N.
  * All three bail with a clear message if the mode is not Free.

src/gui.rs:
  * Mode toggle (Managed / Free) at the top of page_layout.
  * page_layout_managed: existing wizard (presets, drag tiles,
    Save and Apply).
  * page_layout_free: per-window initial W/H picker, Apply-size,
    Apply-position, Reset-size buttons. Per-slot lock indicator.
    Uses DragValue::range (clamp_range is deprecated).
  * App.pending_ipc: Vec<String> queue; the main loop will drain
    it and fire the IPC verbs.

cargo test 103/103 (unchanged from Item 4 + 3 games tests); clippy clean.
2026-09-17 07:22:13 +02:00
en
1d92bc568d Item 3 (Grok round 4): replace regex window matching with process-tree tracking.
Master quote: "i do not know why you have regex for the games? If you are launching the games you should be able to find out what you launched instead and then place it in the correct spot in the grid. programs spawn under a tree structure on linux so you should easily be able to figure out what you spawned."

Implementation:

- src/process.rs (new): pid_is_ancestor(ancestor, candidate) walks /proc/<pid>/status PPid chain upward. Bounded to 64 hops so a malformed /proc cannot spin us. Unit-tested with three cases: self, zero-pids, and the running test process against its own ppid.

- src/lib.rs: pub mod process registered.

- src/profile.rs:
  * Profile.window_match field removed (was Option<String> class + Option<String> title regex).
  * WindowMatch struct removed.
  * HashSet import kept (still used elsewhere).

- src/session.rs:
  * Session.spawned_pids: HashSet<u32> field added + initialised.
  * refresh_slots: replaces regex filter with crate::process::pid_is_ancestor(root, c.pid) against every spawned root.
  * profile-init log line dropped window_match.class / .title args.
  * borderless path: removed class_re use; for now re-applies apply_vfx_window_rules (per-spawn windowrulev2 lands once the launcher wires the pid in Item 4).
  * matches_regex helper removed (was only used by arm_auto_apply).

- src/layout.rs:
  * select_windows now a placeholder: returns the first N visible clients in z-order. Real picker is in session::refresh_slots.
  * Regex import dropped.

- src/gui.rs:
  * WindowMatch import removed.
  * Default profile literal: window_match field removed.
  * Profile editor: the four class/title text-edit lines replaced with a comment explaining Item 3.
  * arm_auto_apply: regex compile block + class_pat / title_pat / any_pattern bindings removed; the thread body now uses the process-tree match with a placeholder for the spawned-pids source until Item 4 wires it.

- src/team.rs + src/engine.rs: Profile literals with window_match: Default::default() removed.

- src/hypr.rs: untouched. apply_borderless_rules kept but no longer called by the daemon.

cargo test 100/100 (3 new pid_is_ancestor tests); clippy clean.
2026-09-17 05:42:06 +02:00
en
20271264cf Item 5+6 (Grok round 4): default profile name -> team, drop passthrough feature
Item 5: examples/profile.yaml default profile name renamed from
"esdf-team" to "team". The default shipped profile should be
called what it is.

Item 6: drop the passthrough feature entirely. Removed:
  - Profile.passthrough field + Profile::passthrough_set
  - Engine.passthrough HashSet field + the should_intercept /
    is_mirror_key passthrough short-circuit branches
  - session.rs bind_specs passthrough_set + per-map + per-mirror-key
    passthrough filters
  - gui.rs passthrough label + text-edit widget
  - main.rs profile log line that referenced p.passthrough
  - hotkey.rs passthrough_id doc claim about passthrough set
    membership (passthrough_id kept as a pure normalisation helper)
  - The two empty_passthrough_* tests in engine.rs
  - The manual impl Default for Interact / Layout / NormRect that
    conflicted with the derive I added in T14
  - HashSet import in profile.rs that was only used by passthrough_set
  - Hotkey + InteractStyle now derive Default so Profile::default()
    still works end-to-end after passthrough removal

Kept (NOT removed): vfx::FeedHit.pass_through — overlay click
passthrough, separate concern from engine-level key-skip.

Also silenced the stale #[allow(dead_code)] on
GbmDevice.sym.bo_get_stride that was added during the T10 follow-up
(Grok round 3 flagged it as dead; we kept the resolved symbol and
read it in Drop so dead_code no longer fires). No more spinning red
circle from that warning.

cargo test 97/97; clippy clean.
2026-09-17 05:38:59 +02:00
en
b6937158e4 Profile::default() + T13 cadence-clamp unit test
Profile and Mode derive Default so the T13 cadence-clamp test can
build a Session without a real profile file. Mode uses #[default]
on the Maps variant (the natural first option). The T13 unit test
walks ENBOXER_MOUSE_REPEAT_MS through valid values (1, 50, 2000)
and out-of-range or malformed (0, 2001, non-numeric, empty) and
asserts the session field reads back the in-range value or the
50 ms default respectively.

cargo test 99+/0; clippy clean.
2026-09-16 17:16:16 +02:00
en
0ad3e3335e Refactor chmod_runtime_dir to chmod_dir(path); test uses tempdir
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).
2026-09-16 07:59:34 +02:00
en
1b8b2cdf24 GUI: arm_auto_apply waits for window_match; new-team pads characters
- 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.
2026-09-16 05:58:27 +02:00
en
cece41d2ee Tighten IPC socket permissions (chmod 0o700/0o600)
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.
2026-09-16 05:45:48 +02:00
en
9d2f51685d Finish the project (T7..T15): layout wizard, broadcast, overlay, dmabuf, teams
Layout wizard (T7):
- App::layout_canvas with monitor backgrounds, click-to-select, draggable tiles
- borderless Hyprland window_rule on run
- App::refresh_monitors + Refresh monitors button; monitors cache for canvas sync

Routing extras (T8, T13, T14):
- clipboard IPC verb (wl-paste / xclip -> Ctrl+V to non-leader slots)
- mirror-mode mouse click broadcast via hypr::deliver_click
- round_robin / rr bind target rotates through ALL slots (leader included)

Slot overlay (T9 real):
- src/wayland_layer.rs: zwlr_layer_shell_v1 client, shm buffers, 3x5 bitmap
  digit glyphs, wl_pointer click -> swap <slot>
- gated ENBOXER_ENABLE_OVERLAY=1; cargo test does not connect

Covered-window VFX capture (T10 real):
- src/toplevel_export.rs: zwlr_export_dmabuf_unstable_v1 client
- ARGB8888 / XRGB8888 format negotiation, synthetic-PNG fallback for tests
- gated ENBOXER_ENABLE_TOPLEVEL=1; gbm_bo_map upgrade documented

Teams + Lutris launcher (T15):
- 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 merges Lutris config + per-character wine-prefix
- GUI: Teams menu (New / Switch / Refresh / Show / Delete) + Launch menu
- Lutris picker visible only in New-team flow; direct Wine spawn, no lutris CLI

Tests: 89 passed; 0 failed (up from 24).
Clippy: clean with -D warnings.

Safety:
- No live hyprctl dispatch that moves / resizes / pins / closes the session.
- All Wayland paths feature-gated; cargo test does not connect.
- apply_layout still gated by allow_layout + confirm_apply.

Files: 14 modified + 6 new (src/{launcher,lutris,overlay,team,toplevel_export,wayland_layer}.rs)
Diff: +1570 / -29
2026-09-15 17:18:37 +02:00
en
af45b054cc Window swap/focus/reset hotkeys; clamp layout tiles to the monitor.
Do not run live compositor tests that move the user's windows.
2026-09-15 08:56:01 +02:00
en
50b2aebee6 Add window layout: stacked, grid, main+strip, Save and Apply.
Layout page in the control panel places captured clients like the
source project's wizard: per-slot x/y/w/h, pin, generate, capture.
2026-09-15 08:42:55 +02:00
en
32731fe59b Add control-panel GUI; Video FX rects in pixels or fractions.
`enboxer` with no command opens File/Session/Video/Maps. Native Wayland
clients get a brief focus steal; XWayland/Wine stays unfocused.
2026-09-15 08:26:07 +02:00
en
936f123b18 Three routing modes: maps, mirror, off.
Mirror clones the real key to the other clients (passthrough still skipped).
Off leaves the front window alone except the mode-toggle hotkey.
2026-09-15 08:14:08 +02:00
en
4daf0ea86a Initial enBoxer: mapped-key routing and Video FX on Hyprland.
Phase 1 tickets T1–T6, ponytail dead-code cuts, docs and plan in-tree.
2026-09-15 07:19:15 +02:00