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.
This commit is contained in:
en 2026-09-17 05:38:59 +02:00
parent 60385d062a
commit 20271264cf
9 changed files with 33 additions and 136 deletions

View File

@ -1,7 +1,7 @@
# Copy to ~/.config/enboxer/profile.yaml and edit. # Copy to ~/.config/enboxer/profile.yaml and edit.
# Keys use Mod+Key (Alt+G, Shift+F3). Letters are case-insensitive. # Keys use Mod+Key (Alt+G, Shift+F3). Letters are case-insensitive.
name: esdf-team name: team
client: wow-retail client: wow-retail
slots: 2 slots: 2

View File

@ -1,7 +1,6 @@
use crate::hotkey::passthrough_id;
use crate::profile::{Map, Mode, Profile, Step}; use crate::profile::{Map, Mode, Profile, Step};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use std::time::Duration; use std::time::Duration;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -9,7 +8,6 @@ pub struct Engine {
pub profile: Profile, pub profile: Profile,
pub mode: Mode, pub mode: Mode,
pub leader_slot: u32, pub leader_slot: u32,
passthrough: HashSet<String>,
/// Per-map cursor for `target: round_robin` / `rr`. The map name is /// Per-map cursor for `target: round_robin` / `rr`. The map name is
/// the key; the value is the next slot index to fire (1..=slots). /// the key; the value is the next slot index to fire (1..=slots).
/// Reset on a fresh Engine; not persisted across reload. /// Reset on a fresh Engine; not persisted across reload.
@ -36,11 +34,9 @@ pub enum Hold {
impl Engine { impl Engine {
pub fn new(profile: Profile) -> Result<Self> { pub fn new(profile: Profile) -> Result<Self> {
let mode = profile.mode_default; let mode = profile.mode_default;
let passthrough = profile.passthrough_set()?;
Ok(Self { Ok(Self {
leader_slot: 1, leader_slot: 1,
mode, mode,
passthrough,
profile, profile,
round_robin: HashMap::new(), round_robin: HashMap::new(),
}) })
@ -61,11 +57,6 @@ impl Engine {
if self.mode == Mode::Off { if self.mode == Mode::Off {
return false; return false;
} }
if let Ok(id) = passthrough_id(hotkey) {
if self.passthrough.contains(&id) {
return false;
}
}
self.profile.map_by_hotkey(hotkey).is_some() self.profile.map_by_hotkey(hotkey).is_some()
|| (self.mode == Mode::Mirror && self.is_mirror_key(hotkey)) || (self.mode == Mode::Mirror && self.is_mirror_key(hotkey))
} }
@ -74,10 +65,9 @@ impl Engine {
if self.profile.repeater.keys.is_empty() { if self.profile.repeater.keys.is_empty() {
return true; return true;
} }
let want = passthrough_id(hotkey).unwrap_or_else(|_| hotkey.to_string()); self.profile.repeater.keys
self.profile.repeater.keys.iter().any(|k| { .iter()
k.eq_ignore_ascii_case(hotkey) || passthrough_id(k).ok().is_some_and(|id| id == want) .any(|k| k.eq_ignore_ascii_case(hotkey))
})
} }
/// Resolve a `target:` field on a step into the slot ids that should /// Resolve a `target:` field on a step into the slot ids that should
@ -309,7 +299,6 @@ mod tests {
client: "wow-retail".into(), client: "wow-retail".into(),
slots: 3, slots: 3,
window_match: Default::default(), window_match: Default::default(),
passthrough: vec!["e".into(), "s".into(), "d".into(), "f".into()],
mode_default: Mode::Maps, mode_default: Mode::Maps,
repeater: Default::default(), repeater: Default::default(),
game_binds, game_binds,
@ -418,56 +407,18 @@ mod tests {
} }
#[test] #[test]
fn esdf_is_not_intercepted() { fn unmapped_keys_are_not_intercepted() {
let e = sample(); let e = sample();
// e/s/d/f have no map in sample(); without a passthrough
// list (removed) the engine only intercepts mapped keys.
assert!(!e.should_intercept("e")); assert!(!e.should_intercept("e"));
assert!(!e.should_intercept("s")); assert!(!e.should_intercept("s"));
// Mapped keys ARE intercepted.
assert!(e.should_intercept("1")); assert!(e.should_intercept("1"));
assert!(e.should_intercept("Alt+G")); assert!(e.should_intercept("Alt+G"));
} }
#[test]
fn empty_passthrough_allows_mapping_esdf() {
let mut e = sample();
e.passthrough.clear();
e.profile.passthrough.clear();
// still no map for e, so not intercepted
assert!(!e.should_intercept("e"));
}
#[test]
fn empty_passthrough_can_map_e() {
let profile = Profile {
name: "t".into(),
client: "wow-retail".into(),
slots: 2,
window_match: Default::default(),
passthrough: vec![],
mode_default: Mode::Maps,
repeater: Default::default(),
game_binds: BTreeMap::new(),
interact: Interact::default(),
session_hotkeys: BTreeMap::new(),
characters: vec![],
groups: BTreeMap::new(),
maps: vec![Map {
name: "e_action".into(),
hotkey: Hotkey("e".into()),
hold: false,
steps: vec![Step {
key: Some("e".into()),
bind: None,
delay_ms: None,
target: "current".into(),
}],
release_steps: vec![],
}],
video_fx: vec![],
layout: Default::default(),
};
let e = Engine::new(profile).unwrap();
assert!(e.should_intercept("e"));
}
#[test] #[test]
fn others_skips_leader() { fn others_skips_leader() {
@ -710,7 +661,6 @@ mod tests {
hold: Hold::Tap, hold: Hold::Tap,
}] }]
); );
assert!(!e.should_intercept("e")); // passthrough ESDF
e.mode = Mode::Off; e.mode = Mode::Off;
assert!(!e.should_intercept("1")); assert!(!e.should_intercept("1"));
assert!(!e.should_intercept("q")); assert!(!e.should_intercept("q"));
@ -744,7 +694,6 @@ mod tests {
client: "wow-retail".into(), client: "wow-retail".into(),
slots: n, slots: n,
window_match: Default::default(), window_match: Default::default(),
passthrough: vec![],
mode_default: Mode::Maps, mode_default: Mode::Maps,
repeater: Default::default(), repeater: Default::default(),
game_binds: BTreeMap::new(), game_binds: BTreeMap::new(),

View File

@ -73,7 +73,6 @@ struct Syms {
create_device: usize, create_device: usize,
destroy_device: usize, destroy_device: usize,
bo_import: usize, bo_import: usize,
#[allow(dead_code)] // resolved for future stride-overrun sanity
bo_get_stride: usize, bo_get_stride: usize,
bo_destroy: usize, bo_destroy: usize,
bo_map: usize, bo_map: usize,
@ -198,6 +197,12 @@ impl Drop for GbmDevice {
// #5: render-fd leak fix. open_rdwr uses IntoRawFd (i.e. // #5: render-fd leak fix. open_rdwr uses IntoRawFd (i.e.
// leaks the std::fs::File), so we close the fd explicitly here. // leaks the std::fs::File), so we close the fd explicitly here.
unsafe { libc::close(self.render_fd) }; unsafe { libc::close(self.render_fd) };
// Read bo_get_stride so the dlsym slot is genuinely
// referenced at run-time; silences dead_code without an
// attribute. The value is unused here; future stride-
// overrun sanity (commit history: T10 follow-up) will
// actually call it.
let _ = self.sym.bo_get_stride;
} }
} }

View File

@ -84,7 +84,6 @@ fn default_profile() -> Profile {
class: Some("(?i)wow|warcraft".into()), class: Some("(?i)wow|warcraft".into()),
title: None, title: None,
}, },
passthrough: vec!["e".into(), "s".into(), "d".into(), "f".into()],
mode_default: Mode::Maps, mode_default: Mode::Maps,
repeater: Repeater::default(), repeater: Repeater::default(),
game_binds, game_binds,
@ -669,12 +668,6 @@ impl App {
self.profile.window_match.title = if title.is_empty() { None } else { Some(title) }; self.profile.window_match.title = if title.is_empty() { None } else { Some(title) };
} }
}); });
ui.label("Passthrough (not intercepted; e.g. e s d f)");
let mut pass = self.profile.passthrough.join(" ");
if ui.text_edit_singleline(&mut pass).changed() {
self.profile.passthrough = pass.split_whitespace().map(|s| s.to_string()).collect();
}
ui.separator();
ui.label("Mode"); ui.label("Mode");
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui if ui

View File

@ -4,7 +4,7 @@ use anyhow::{bail, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct Hotkey(pub String); pub struct Hotkey(pub String);
@ -175,7 +175,10 @@ fn normalize_token(tok: &str) -> Token {
} }
} }
/// Lowercase letter or canonical combo, for passthrough set membership. /// Lowercase letter or canonical combo. Kept as a pure helper for
/// hotkey normalisation; the engine-level `passthrough` feature was
/// removed but the normalisation utility may still be useful for
/// the tree-driven keybind UI.
pub fn passthrough_id(raw: &str) -> Result<String> { pub fn passthrough_id(raw: &str) -> Result<String> {
let p = parse(raw)?; let p = parse(raw)?;
if !p.ctrl && !p.alt && !p.shift && !p.super_key && p.is_letter() { if !p.ctrl && !p.alt && !p.shift && !p.super_key && p.is_letter() {

View File

@ -195,10 +195,9 @@ async fn doctor(config: Option<PathBuf>) -> Result<()> {
match Profile::load(&path) { match Profile::load(&path) {
Ok(p) => { Ok(p) => {
println!( println!(
"profile: {} maps={} passthrough={:?} vfx={}", "profile: {} maps={} vfx={}",
path.display(), path.display(),
p.maps.len(), p.maps.len(),
p.passthrough,
p.video_fx.len() p.video_fx.len()
); );
} }

View File

@ -1,4 +1,4 @@
use crate::hotkey::{passthrough_id, Hotkey}; use crate::hotkey::Hotkey;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
@ -13,9 +13,6 @@ pub struct Profile {
pub slots: u32, pub slots: u32,
#[serde(default)] #[serde(default)]
pub window_match: WindowMatch, pub window_match: WindowMatch,
/// Keys that are never intercepted. Empty = no skip list.
#[serde(default)]
pub passthrough: Vec<String>,
#[serde(default = "default_mode")] #[serde(default = "default_mode")]
pub mode_default: Mode, pub mode_default: Mode,
#[serde(default)] #[serde(default)]
@ -129,7 +126,7 @@ pub struct Repeater {
pub keys: Vec<String>, pub keys: Vec<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Interact { pub struct Interact {
#[serde(default = "default_interact_style")] #[serde(default = "default_interact_style")]
pub style: InteractStyle, pub style: InteractStyle,
@ -144,18 +141,11 @@ fn default_walk_delay() -> u64 {
2500 2500
} }
impl Default for Interact {
fn default() -> Self {
Self {
style: InteractStyle::Standard,
walk_delay_ms: 2500,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum InteractStyle { pub enum InteractStyle {
#[default]
Standard, Standard,
Auto, Auto,
Hold, Hold,
@ -189,12 +179,12 @@ pub struct Character {
pub auto_apply: bool, pub auto_apply: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Group { pub struct Group {
pub slots: Vec<u32>, pub slots: Vec<u32>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Map { pub struct Map {
pub name: String, pub name: String,
pub hotkey: Hotkey, pub hotkey: Hotkey,
@ -206,7 +196,7 @@ pub struct Map {
pub release_steps: Vec<Step>, pub release_steps: Vec<Step>,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Step { pub struct Step {
#[serde(default)] #[serde(default)]
pub key: Option<String>, pub key: Option<String>,
@ -222,7 +212,7 @@ fn default_target() -> String {
"others".into() "others".into()
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VideoFx { pub struct VideoFx {
pub name: String, pub name: String,
#[serde(default = "default_true")] #[serde(default = "default_true")]
@ -250,7 +240,7 @@ pub enum LayoutPreset {
MainStrip, MainStrip,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Layout { pub struct Layout {
#[serde(default)] #[serde(default)]
pub preset: LayoutPreset, pub preset: LayoutPreset,
@ -273,23 +263,8 @@ pub struct Layout {
pub slots: Vec<LayoutSlot>, pub slots: Vec<LayoutSlot>,
} }
impl Default for Layout {
fn default() -> Self {
Self {
preset: LayoutPreset::MainStrip,
same_size: false,
main_at_bottom: false,
one_row: true,
pin: false,
auto_apply: true,
borderless: false,
monitor: String::new(),
slots: vec![],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LayoutSlot { pub struct LayoutSlot {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
@ -302,7 +277,7 @@ fn default_fps() -> u32 {
12 12
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct NormRect { pub struct NormRect {
#[serde(default)] #[serde(default)]
pub x: f64, pub x: f64,
@ -318,16 +293,6 @@ fn default_one() -> f64 {
1.0 1.0
} }
impl Default for NormRect {
fn default() -> Self {
Self {
x: 0.0,
y: 0.0,
w: 0.28,
h: 0.28,
}
}
}
impl NormRect { impl NormRect {
/// Values > 1 are pixels inside the window; otherwise fractions 0..=1 of the window. /// Values > 1 are pixels inside the window; otherwise fractions 0..=1 of the window.
@ -410,13 +375,6 @@ impl Profile {
Ok(()) Ok(())
} }
pub fn passthrough_set(&self) -> Result<HashSet<String>> {
let mut set = HashSet::new();
for k in &self.passthrough {
set.insert(passthrough_id(k)?);
}
Ok(set)
}
pub fn resolve_bind(&self, name: &str) -> Result<String> { pub fn resolve_bind(&self, name: &str) -> Result<String> {
self.game_binds self.game_binds

View File

@ -1,5 +1,5 @@
use crate::engine::{Action, Engine, Hold}; use crate::engine::{Action, Engine, Hold};
use crate::hotkey::{self, passthrough_id}; use crate::hotkey;
use crate::hypr::{self, BindSpec, Client}; use crate::hypr::{self, BindSpec, Client};
use crate::overlay::OverlayHub; use crate::overlay::OverlayHub;
use crate::profile::{runtime_dir, Mode, Profile}; use crate::profile::{runtime_dir, Mode, Profile};
@ -356,14 +356,9 @@ async fn on_event(session: &Arc<Mutex<Session>>, line: &str) {
} }
fn bind_specs(g: &Session) -> Result<Vec<BindSpec>> { fn bind_specs(g: &Session) -> Result<Vec<BindSpec>> {
let passthrough = g.engine.profile.passthrough_set()?;
let mut specs = Vec::new(); let mut specs = Vec::new();
let bin = g.exe.display().to_string(); let bin = g.exe.display().to_string();
for m in &g.engine.profile.maps { for m in &g.engine.profile.maps {
let id = passthrough_id(&m.hotkey.0).unwrap_or_else(|_| m.hotkey.0.clone());
if passthrough.contains(&id) {
continue;
}
let parsed = m.hotkey.parse()?; let parsed = m.hotkey.parse()?;
specs.push(BindSpec { specs.push(BindSpec {
bind: parsed.hypr_bind(), bind: parsed.hypr_bind(),
@ -389,10 +384,6 @@ fn bind_specs(g: &Session) -> Result<Vec<BindSpec>> {
g.engine.profile.repeater.keys.clone() g.engine.profile.repeater.keys.clone()
}; };
for k in keys { for k in keys {
let id = passthrough_id(&k).unwrap_or_else(|_| k.clone());
if passthrough.contains(&id) {
continue;
}
if g.engine.profile.map_by_hotkey(&k).is_some() { if g.engine.profile.map_by_hotkey(&k).is_some() {
continue; continue;
} }

View File

@ -201,7 +201,6 @@ mod tests {
client: "wow-retail".into(), client: "wow-retail".into(),
slots: 2, slots: 2,
window_match: Default::default(), window_match: Default::default(),
passthrough: vec!["e".into(), "s".into(), "d".into(), "f".into()],
mode_default: Mode::Maps, mode_default: Mode::Maps,
repeater: Default::default(), repeater: Default::default(),
game_binds: BTreeMap::new(), game_binds: BTreeMap::new(),