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.
# Keys use Mod+Key (Alt+G, Shift+F3). Letters are case-insensitive.
name: esdf-team
name: team
client: wow-retail
slots: 2

View File

@ -1,7 +1,6 @@
use crate::hotkey::passthrough_id;
use crate::profile::{Map, Mode, Profile, Step};
use anyhow::{bail, Result};
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone)]
@ -9,7 +8,6 @@ pub struct Engine {
pub profile: Profile,
pub mode: Mode,
pub leader_slot: u32,
passthrough: HashSet<String>,
/// 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).
/// Reset on a fresh Engine; not persisted across reload.
@ -36,11 +34,9 @@ pub enum Hold {
impl Engine {
pub fn new(profile: Profile) -> Result<Self> {
let mode = profile.mode_default;
let passthrough = profile.passthrough_set()?;
Ok(Self {
leader_slot: 1,
mode,
passthrough,
profile,
round_robin: HashMap::new(),
})
@ -61,11 +57,6 @@ impl Engine {
if self.mode == Mode::Off {
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.mode == Mode::Mirror && self.is_mirror_key(hotkey))
}
@ -74,10 +65,9 @@ impl Engine {
if self.profile.repeater.keys.is_empty() {
return true;
}
let want = passthrough_id(hotkey).unwrap_or_else(|_| hotkey.to_string());
self.profile.repeater.keys.iter().any(|k| {
k.eq_ignore_ascii_case(hotkey) || passthrough_id(k).ok().is_some_and(|id| id == want)
})
self.profile.repeater.keys
.iter()
.any(|k| k.eq_ignore_ascii_case(hotkey))
}
/// Resolve a `target:` field on a step into the slot ids that should
@ -309,7 +299,6 @@ mod tests {
client: "wow-retail".into(),
slots: 3,
window_match: Default::default(),
passthrough: vec!["e".into(), "s".into(), "d".into(), "f".into()],
mode_default: Mode::Maps,
repeater: Default::default(),
game_binds,
@ -418,56 +407,18 @@ mod tests {
}
#[test]
fn esdf_is_not_intercepted() {
fn unmapped_keys_are_not_intercepted() {
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("s"));
// Mapped keys ARE intercepted.
assert!(e.should_intercept("1"));
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]
fn others_skips_leader() {
@ -710,7 +661,6 @@ mod tests {
hold: Hold::Tap,
}]
);
assert!(!e.should_intercept("e")); // passthrough ESDF
e.mode = Mode::Off;
assert!(!e.should_intercept("1"));
assert!(!e.should_intercept("q"));
@ -744,7 +694,6 @@ mod tests {
client: "wow-retail".into(),
slots: n,
window_match: Default::default(),
passthrough: vec![],
mode_default: Mode::Maps,
repeater: Default::default(),
game_binds: BTreeMap::new(),

View File

@ -73,7 +73,6 @@ struct Syms {
create_device: usize,
destroy_device: usize,
bo_import: usize,
#[allow(dead_code)] // resolved for future stride-overrun sanity
bo_get_stride: usize,
bo_destroy: usize,
bo_map: usize,
@ -198,6 +197,12 @@ impl Drop for GbmDevice {
// #5: render-fd leak fix. open_rdwr uses IntoRawFd (i.e.
// leaks the std::fs::File), so we close the fd explicitly here.
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()),
title: None,
},
passthrough: vec!["e".into(), "s".into(), "d".into(), "f".into()],
mode_default: Mode::Maps,
repeater: Repeater::default(),
game_binds,
@ -669,12 +668,6 @@ impl App {
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.horizontal(|ui| {
if ui

View File

@ -4,7 +4,7 @@ use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(transparent)]
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> {
let p = parse(raw)?;
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) {
Ok(p) => {
println!(
"profile: {} maps={} passthrough={:?} vfx={}",
"profile: {} maps={} vfx={}",
path.display(),
p.maps.len(),
p.passthrough,
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 serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashSet};
@ -13,9 +13,6 @@ pub struct Profile {
pub slots: u32,
#[serde(default)]
pub window_match: WindowMatch,
/// Keys that are never intercepted. Empty = no skip list.
#[serde(default)]
pub passthrough: Vec<String>,
#[serde(default = "default_mode")]
pub mode_default: Mode,
#[serde(default)]
@ -129,7 +126,7 @@ pub struct Repeater {
pub keys: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Interact {
#[serde(default = "default_interact_style")]
pub style: InteractStyle,
@ -144,18 +141,11 @@ fn default_walk_delay() -> u64 {
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")]
pub enum InteractStyle {
#[default]
Standard,
Auto,
Hold,
@ -189,12 +179,12 @@ pub struct Character {
pub auto_apply: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Group {
pub slots: Vec<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Map {
pub name: String,
pub hotkey: Hotkey,
@ -206,7 +196,7 @@ pub struct Map {
pub release_steps: Vec<Step>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Step {
#[serde(default)]
pub key: Option<String>,
@ -222,7 +212,7 @@ fn default_target() -> String {
"others".into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VideoFx {
pub name: String,
#[serde(default = "default_true")]
@ -250,7 +240,7 @@ pub enum LayoutPreset {
MainStrip,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Layout {
#[serde(default)]
pub preset: LayoutPreset,
@ -273,23 +263,8 @@ pub struct Layout {
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 x: i32,
pub y: i32,
@ -302,7 +277,7 @@ fn default_fps() -> u32 {
12
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct NormRect {
#[serde(default)]
pub x: f64,
@ -318,16 +293,6 @@ fn default_one() -> f64 {
1.0
}
impl Default for NormRect {
fn default() -> Self {
Self {
x: 0.0,
y: 0.0,
w: 0.28,
h: 0.28,
}
}
}
impl NormRect {
/// Values > 1 are pixels inside the window; otherwise fractions 0..=1 of the window.
@ -410,13 +375,6 @@ impl Profile {
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> {
self.game_binds

View File

@ -1,5 +1,5 @@
use crate::engine::{Action, Engine, Hold};
use crate::hotkey::{self, passthrough_id};
use crate::hotkey;
use crate::hypr::{self, BindSpec, Client};
use crate::overlay::OverlayHub;
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>> {
let passthrough = g.engine.profile.passthrough_set()?;
let mut specs = Vec::new();
let bin = g.exe.display().to_string();
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()?;
specs.push(BindSpec {
bind: parsed.hypr_bind(),
@ -389,10 +384,6 @@ fn bind_specs(g: &Session) -> Result<Vec<BindSpec>> {
g.engine.profile.repeater.keys.clone()
};
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() {
continue;
}

View File

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