Commit Graph

8 Commits

Author SHA1 Message Date
en
863f7e584e Round 6 (Grok verification): fix the live-path breakers.
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.
2026-09-17 08:24:03 +02:00
en
677ae0d3dc Round 5 batch B (Grok review): live wiring for items 2, 4, 5, 6, 7, 9.
Grok round 5 found these surfaces either dead or lying:

2. Item 4 GUI never shipped. page_session had no game dropdown;
   menu_launch was still Lutris. Added a Game row: ComboBox fed by
   the daemon `list-games` verb (refresh_games helper), Launch
   button fires `launch-game NAME`. New App fields game_list /
   game_pick.

4. GUI local spawn never filled Session.spawned_pids. spawn_plan
   now captures the child PID and pushes it via the new `track-pid`
   IPC verb; status shows the pid. Daemon-side track-pid inserts
   into Session.spawned_pids.

5. launch_game drop(Child) did not reap -> zombies (wine wrappers
   exit fast). Replaced with a background thread that calls
   child.wait(); the PID stays in spawned_pids.

6. Deleted the select_windows placeholder that returned a fake
   first-N-visible list disagreeing with the daemon assignment.
   select_windows now reads the daemon slot list via the new
   `slots` IPC verb (layout::slots_from_daemon) and returns the
   same (id, Client) shape.

7. resize_slot / move_slot used slot.saturating_sub(1) as an index
   into layout.slots, which breaks when Session.slots has gaps
   after a hide ([1a, 3b] -> resize-slot 3 bailed). LayoutSlot
   gains a stable `id: u32` field; both handlers now find the
   layout slot by id. LayoutSlot literals across the repo given
   an id.

9. keybind_detail Map panel now exposes release_steps (Add /
   Remove / edit per entry), matching the press steps editor.

cargo test 103/103; clippy clean.
2026-09-17 08:16:05 +02:00
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
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
ed75a8b899 Document full product goals; gate layout; named profiles; type-to-others.
GOALS.md / AGENTS.md are the source of truth. Layout apply requires
ENBOXER_ALLOW_LAYOUT=1 and a GUI confirm. Example Video FX is off.
2026-09-15 09:11:31 +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