diff --git a/parser/src/cfg/defcfg.rs b/parser/src/cfg/defcfg.rs index e8eb03549..f2ff2615b 100644 --- a/parser/src/cfg/defcfg.rs +++ b/parser/src/cfg/defcfg.rs @@ -94,6 +94,11 @@ pub struct CfgOptions { all(feature = "interception_driver", target_os = "windows"), target_os = "unknown" ))] + pub windows_interception_mouse_hwids_exclude: Option>, + #[cfg(any( + all(feature = "interception_driver", target_os = "windows"), + target_os = "unknown" + ))] pub windows_interception_keyboard_hwids: Option>, #[cfg(any(target_os = "macos", target_os = "unknown"))] pub macos_dev_names_include: Option>, @@ -150,6 +155,11 @@ impl Default for CfgOptions { all(feature = "interception_driver", target_os = "windows"), target_os = "unknown" ))] + windows_interception_mouse_hwids_exclude: None, + #[cfg(any( + all(feature = "interception_driver", target_os = "windows"), + target_os = "unknown" + ))] windows_interception_keyboard_hwids: None, #[cfg(any(target_os = "macos", target_os = "unknown"))] macos_dev_names_include: None, @@ -317,6 +327,9 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result { target_os = "unknown" ))] { + if cfg.windows_interception_mouse_hwids_exclude.is_some() { + bail_expr!(val, "{label} and windows-interception-mouse-hwid-exclude cannot both be included"); + } let v = sexpr_to_str_or_err(val, label)?; let hwid = v; log::trace!("win hwid: {hwid}"); @@ -357,6 +370,9 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result { target_os = "unknown" ))] { + if cfg.windows_interception_mouse_hwids_exclude.is_some() { + bail_expr!(val, "{label} and windows-interception-mouse-hwid-exclude cannot both be included"); + } let hwids = sexpr_to_list_or_err(val, label)?; let mut parsed_hwids = vec![]; for hwid_expr in hwids.iter() { @@ -398,6 +414,45 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result { .shrink_to_fit(); } } + "windows-interception-mouse-hwids-exclude" => { + #[cfg(any( + all(feature = "interception_driver", target_os = "windows"), + target_os = "unknown" + ))] + { + if cfg.windows_interception_mouse_hwids.is_some() { + bail_expr!(val, "{label} and windows-interception-mouse-hwid(s) cannot both be included"); + } + let hwids = sexpr_to_list_or_err(val, label)?; + let mut parsed_hwids = vec![]; + for hwid_expr in hwids.iter() { + let hwid = sexpr_to_str_or_err( + hwid_expr, + "entry in windows-interception-mouse-hwids-exclude", + )?; + log::trace!("win hwid: {hwid}"); + let hwid_vec = hwid + .split(',') + .try_fold(vec![], |mut hwid_bytes, hwid_byte| { + hwid_byte.trim_matches(' ').parse::().map(|b| { + hwid_bytes.push(b); + hwid_bytes + }) + }).map_err(|_| anyhow_expr!(hwid_expr, "Entry in {label} is invalid. Entries should be numbers [0,255] separated by commas"))?; + let hwid_slice = hwid_vec.iter().copied().enumerate() + .try_fold([0u8; HWID_ARR_SZ], |mut hwid, idx_byte| { + let (i, b) = idx_byte; + if i > HWID_ARR_SZ { + bail_expr!(hwid_expr, "entry in {label} is too long; it should be up to {HWID_ARR_SZ} 8-bit unsigned integers") + } + hwid[i] = b; + Ok(hwid) + }); + parsed_hwids.push(hwid_slice?); + } + cfg.windows_interception_mouse_hwids_exclude = Some(parsed_hwids); + } + } "windows-interception-keyboard-hwids" => { #[cfg(any( all(feature = "interception_driver", target_os = "windows"), diff --git a/src/kanata/mod.rs b/src/kanata/mod.rs index 8ed4bc3b8..b3a0a126d 100755 --- a/src/kanata/mod.rs +++ b/src/kanata/mod.rs @@ -162,6 +162,10 @@ pub struct Kanata { /// by kanata. intercept_mouse_hwids: Option>, #[cfg(all(feature = "interception_driver", target_os = "windows"))] + /// Used to know which mouse input devices to exclude from processing inputs by kanata. This is + /// mutually exclusive from `intercept_mouse_hwids` and kanata will panic if both are included. + intercept_mouse_hwids_excluded: Option>, + #[cfg(all(feature = "interception_driver", target_os = "windows"))] /// Used to know which input device to treat as a mouse for intercepting and processing inputs /// by kanata. intercept_kb_hwids: Option>, @@ -347,6 +351,8 @@ impl Kanata { #[cfg(all(feature = "interception_driver", target_os = "windows"))] intercept_mouse_hwids: cfg.options.windows_interception_mouse_hwids, #[cfg(all(feature = "interception_driver", target_os = "windows"))] + intercept_mouse_hwids_excluded: cfg.options.windows_interception_mouse_hwids_exclude, + #[cfg(all(feature = "interception_driver", target_os = "windows"))] intercept_kb_hwids: cfg.options.windows_interception_keyboard_hwids, dynamic_macro_replay_state: None, dynamic_macro_record_state: None, @@ -446,6 +452,8 @@ impl Kanata { #[cfg(all(feature = "interception_driver", target_os = "windows"))] intercept_mouse_hwids: cfg.options.windows_interception_mouse_hwids, #[cfg(all(feature = "interception_driver", target_os = "windows"))] + intercept_mouse_hwids_excluded: cfg.options.windows_interception_mouse_hwids_exclude, + #[cfg(all(feature = "interception_driver", target_os = "windows"))] intercept_kb_hwids: cfg.options.windows_interception_keyboard_hwids, dynamic_macro_replay_state: None, dynamic_macro_record_state: None, diff --git a/src/kanata/windows/interception.rs b/src/kanata/windows/interception.rs index e22638414..4e583d8d1 100644 --- a/src/kanata/windows/interception.rs +++ b/src/kanata/windows/interception.rs @@ -22,7 +22,9 @@ impl Kanata { let keyboards_to_intercept_hwids = kanata.lock().intercept_kb_hwids.clone(); let mouse_to_intercept_hwids: Option> = kanata.lock().intercept_mouse_hwids.clone(); - if mouse_to_intercept_hwids.is_some() { + let mouse_to_intercept_excluded_hwids: Option> = + kanata.lock().intercept_mouse_hwids_excluded.clone(); + if mouse_to_intercept_hwids.is_some() || mouse_to_intercept_excluded_hwids.is_some() { intrcptn.set_filter( ic::is_mouse, ic::Filter::MouseFilter(ic::MouseState::all() & (!ic::MouseState::MOVE)), @@ -40,6 +42,7 @@ impl Kanata { dev, &intrcptn, &keyboards_to_intercept_hwids, + &None, &mut is_dev_interceptable, ) { log::debug!("stroke {:?} is from undesired device", strokes[i]); @@ -62,11 +65,14 @@ impl Kanata { KeyEvent { code, value } } ic::Stroke::Mouse { state, rolling, .. } => { - if mouse_to_intercept_hwids.is_some() { + if mouse_to_intercept_hwids.is_some() + || mouse_to_intercept_excluded_hwids.is_some() + { log::trace!("checking mouse stroke {:?}", strokes[i]); if let Some(event) = mouse_state_to_event( dev, &mouse_to_intercept_hwids, + &mouse_to_intercept_excluded_hwids, state, rolling, &intrcptn, @@ -132,27 +138,42 @@ fn is_device_interceptable( input_dev: ic::Device, intrcptn: &ic::Interception, allowed_hwids: &Option>, + excluded_hwids: &Option>, cache: &mut HashMap, ) -> bool { - match allowed_hwids { - None => true, - Some(allowed) => match cache.get(&input_dev) { + match (allowed_hwids, excluded_hwids) { + (None, None) => true, + (Some(allowed), None) => match cache.get(&input_dev) { Some(v) => *v, None => { let mut hwid = [0u8; HWID_ARR_SZ]; log::trace!("getting hardware id for input dev: {input_dev}"); let res = intrcptn.get_hardware_id(input_dev, &mut hwid); let dev_is_interceptable = allowed.contains(&hwid); - log::info!("res {res}; device #{input_dev} hwid {hwid:?} matches allowed keyboard input: {dev_is_interceptable}"); + log::info!("res {res}; device #{input_dev} hwid {hwid:?} matches allowed: {dev_is_interceptable}"); cache.insert(input_dev, dev_is_interceptable); dev_is_interceptable } }, + (None, Some(excluded)) => match cache.get(&input_dev) { + Some(v) => *v, + None => { + let mut hwid = [0u8; HWID_ARR_SZ]; + log::trace!("getting hardware id for input dev: {input_dev}"); + let res = intrcptn.get_hardware_id(input_dev, &mut hwid); + let dev_is_interceptable = !excluded.contains(&hwid); + log::info!("res {res}; device #{input_dev} hwid {hwid:?} omitted from exclude: {dev_is_interceptable}"); + cache.insert(input_dev, dev_is_interceptable); + dev_is_interceptable + } + }, + _ => unreachable!("excluded and allowed should be mutually exclusive"), } } fn mouse_state_to_event( input_dev: ic::Device, allowed_hwids: &Option>, + excluded_hwids: &Option>, state: ic::MouseState, rolling: i16, intrcptn: &ic::Interception, @@ -162,6 +183,7 @@ fn mouse_state_to_event( input_dev, intrcptn, allowed_hwids, + excluded_hwids, device_interceptability_cache, ) { return None;