Skip to content

Commit

Permalink
feat(win-interception): add mouse device exclude
Browse files Browse the repository at this point in the history
  • Loading branch information
jtroo committed Jun 16, 2024
1 parent 56676e5 commit e4c7a7e
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 6 deletions.
55 changes: 55 additions & 0 deletions parser/src/cfg/defcfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ pub struct CfgOptions {
all(feature = "interception_driver", target_os = "windows"),
target_os = "unknown"
))]
pub windows_interception_mouse_hwids_exclude: Option<Vec<[u8; HWID_ARR_SZ]>>,
#[cfg(any(
all(feature = "interception_driver", target_os = "windows"),
target_os = "unknown"
))]
pub windows_interception_keyboard_hwids: Option<Vec<[u8; HWID_ARR_SZ]>>,
#[cfg(any(target_os = "macos", target_os = "unknown"))]
pub macos_dev_names_include: Option<Vec<String>>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -317,6 +327,9 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
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}");
Expand Down Expand Up @@ -357,6 +370,9 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
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() {
Expand Down Expand Up @@ -398,6 +414,45 @@ pub fn parse_defcfg(expr: &[SExpr]) -> Result<CfgOptions> {
.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::<u8>().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"),
Expand Down
8 changes: 8 additions & 0 deletions src/kanata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ pub struct Kanata {
/// by kanata.
intercept_mouse_hwids: Option<Vec<[u8; HWID_ARR_SZ]>>,
#[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<Vec<[u8; HWID_ARR_SZ]>>,
#[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<Vec<[u8; HWID_ARR_SZ]>>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
34 changes: 28 additions & 6 deletions src/kanata/windows/interception.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ impl Kanata {
let keyboards_to_intercept_hwids = kanata.lock().intercept_kb_hwids.clone();
let mouse_to_intercept_hwids: Option<Vec<[u8; HWID_ARR_SZ]>> =
kanata.lock().intercept_mouse_hwids.clone();
if mouse_to_intercept_hwids.is_some() {
let mouse_to_intercept_excluded_hwids: Option<Vec<[u8; HWID_ARR_SZ]>> =
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)),
Expand All @@ -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]);
Expand All @@ -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,
Expand Down Expand Up @@ -132,27 +138,42 @@ fn is_device_interceptable(
input_dev: ic::Device,
intrcptn: &ic::Interception,
allowed_hwids: &Option<Vec<[u8; HWID_ARR_SZ]>>,
excluded_hwids: &Option<Vec<[u8; HWID_ARR_SZ]>>,
cache: &mut HashMap<ic::Device, bool>,
) -> 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<Vec<[u8; HWID_ARR_SZ]>>,
excluded_hwids: &Option<Vec<[u8; HWID_ARR_SZ]>>,
state: ic::MouseState,
rolling: i16,
intrcptn: &ic::Interception,
Expand All @@ -162,6 +183,7 @@ fn mouse_state_to_event(
input_dev,
intrcptn,
allowed_hwids,
excluded_hwids,
device_interceptability_cache,
) {
return None;
Expand Down

0 comments on commit e4c7a7e

Please sign in to comment.