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.
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.
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.
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.
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).
- 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.