use statically linked interception library for directinput emulation

This commit is contained in:
4yn
2022-07-24 15:55:57 +08:00
parent 0e75256520
commit 571eec24d3
20 changed files with 1948 additions and 31 deletions

View File

@@ -613,6 +613,20 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "interception"
version = "0.1.2"
dependencies = [
"bitflags",
"interception-sys",
"num_enum",
"serde",
]
[[package]]
name = "interception-sys"
version = "0.1.3"
[[package]]
name = "ipconfig"
version = "0.3.0"
@@ -864,6 +878,27 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.9.0"
@@ -1053,6 +1088,16 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-crate"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
dependencies = [
"thiserror",
"toml",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
@@ -1308,6 +1353,7 @@ dependencies = [
"futures-util",
"hyper",
"image",
"interception",
"ipconfig",
"log",
"palette",

View File

@@ -31,6 +31,7 @@ serialport = "4.0.1"
wwserial = {path = "../src-wwserial" }
vigem-client = { version = "0.1.2", features = ["unstable"] }
winapi = "0.3.9"
interception = {path = "../src-interception-rs" }
ipconfig = "0.3.0"
# webserver

View File

@@ -0,0 +1,42 @@
extern crate slider_io;
use std::io;
use slider_io::{config::Config, context::Context};
#[tokio::main]
async fn main() {
env_logger::Builder::new()
.filter_level(log::LevelFilter::Debug)
.init();
let config = Config::from_str(
r##"{
"deviceMode": "brokenithm",
"outputMode": "kb-32-tasoller",
"ledMode": "none",
"disableAirStrings": false,
"divaSerialPort": "COM1",
"divaBrightness": 63,
"brokenithmPort": 1606,
"keyboardSensitivity": 20,
"keyboardDirectInput": true,
"outputPolling": "100",
"outputWebsocketUrl": "localhost:3000",
"ledFaster": false,
"ledColorActive": "#ff00ff",
"ledColorInactive": "#ffff00",
"ledSensitivity": 20,
"ledWebsocketUrl": "localhost:3001",
"ledSerialPort": "COM5"
}"##,
)
.unwrap();
println!("{:?}", config);
let ctx = Context::new(config);
println!("Press enter to quit");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
}

View File

@@ -37,6 +37,7 @@ impl Config {
"divaBrightness": 63,
"brokenithmPort": 1606,
"keyboardSensitivity": 20,
"keyboardDirectInput": false,
"outputPolling": "100",
"outputWebsocketUrl": "localhost:3000",
"ledFaster": false,

View File

@@ -41,6 +41,7 @@ pub enum OutputMode {
layout: KeyboardLayout,
polling: PollingRate,
sensitivity: u8,
direct_input: bool,
},
Gamepad {
layout: GamepadLayout,
@@ -89,46 +90,55 @@ impl OutputMode {
layout: KeyboardLayout::Tasoller,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-32-yuancon" => OutputMode::Keyboard {
layout: KeyboardLayout::Yuancon,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-32-umiguri" => OutputMode::Keyboard {
layout: KeyboardLayout::Umiguri,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-16" => OutputMode::Keyboard {
layout: KeyboardLayout::TasollerHalf,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-8" => OutputMode::Keyboard {
layout: KeyboardLayout::EightK,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-6" => OutputMode::Keyboard {
layout: KeyboardLayout::SixK,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-4" => OutputMode::Keyboard {
layout: KeyboardLayout::FourK,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-voltex" => OutputMode::Keyboard {
layout: KeyboardLayout::Voltex,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"kb-neardayo" => OutputMode::Keyboard {
layout: KeyboardLayout::Neardayo,
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
direct_input: v["keyboardDirectInput"].as_bool()?,
},
"gamepad-voltex" => OutputMode::Gamepad {
layout: GamepadLayout::Voltex,

View File

@@ -1,13 +1,14 @@
use interception::{Interception, KeyState, ScanCode, Stroke};
use log::{error, info};
use std::mem;
use winapi::{
ctypes::c_int,
um::winuser::{SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP},
um::winuser::{
MapVirtualKeyA, SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP, MAPVK_VK_TO_VSC,
},
};
use super::{
config::KeyboardLayout,
output::OutputHandler
};
use super::{config::KeyboardLayout, output::OutputHandler};
#[rustfmt::skip]
const TASOLLER_KB_MAP: [usize; 41] = [
@@ -126,18 +127,25 @@ const VOLTEX_KB_MAP_NEARDAYO: [usize; 41] = [
];
pub struct KeyboardOutput {
ground_to_idx: [usize; 41],
idx_to_keycode: [u16; 41],
// keycode_to_idx: [usize; 256],
input_to_idx: [usize; 41],
key_idx_to_keycode: [u16; 41],
key_idx_to_scancode: [Option<ScanCode>; 41],
next_keys: [bool; 41],
last_keys: [bool; 41],
direct_input: bool,
interception_handle: Option<Interception>,
kb_buf: [INPUT; 41],
kb_direct_buf: [Stroke; 41],
n_kb_buf: u32,
}
// interception isn't send, but lazy to wrap
unsafe impl Send for KeyboardOutput {}
impl KeyboardOutput {
pub fn new(layout: KeyboardLayout) -> Self {
pub fn new(layout: KeyboardLayout, direct_input: bool) -> Self {
let kb_map = match layout {
KeyboardLayout::Tasoller => &TASOLLER_KB_MAP,
KeyboardLayout::Yuancon => &YUANCON_KB_MAP,
@@ -150,20 +158,43 @@ impl KeyboardOutput {
KeyboardLayout::Neardayo => &VOLTEX_KB_MAP_NEARDAYO,
};
let mut ground_to_idx = [0 as usize; 41];
let mut idx_to_keycode = [0 as u16; 41];
let mut input_to_key_idx = [0 as usize; 41];
let mut key_idx_to_keycode = [0 as u16; 41];
let mut key_idx_to_scancode = [None as Option<ScanCode>; 41];
let mut keycode_to_idx = [0xffff as usize; 256];
let mut keycode_count: usize = 0;
for (ground, keycode) in kb_map.iter().enumerate() {
if keycode_to_idx[*keycode] == 0xffff {
keycode_to_idx[*keycode] = keycode_count;
idx_to_keycode[keycode_count] = *keycode as u16;
key_idx_to_keycode[keycode_count] = *keycode as u16;
key_idx_to_scancode[keycode_count] =
ScanCode::try_from(unsafe { MapVirtualKeyA((*keycode) as u32, MAPVK_VK_TO_VSC) as u16 })
.ok();
// info!(
// "mapped {:?} to {:?}",
// key_idx_to_keycode[keycode_count], key_idx_to_scancode[keycode_count]
// );
keycode_count += 1;
}
ground_to_idx[ground] = keycode_to_idx[*keycode]
input_to_key_idx[ground] = keycode_to_idx[*keycode]
}
let interception_handle = match direct_input {
true => {
let inner_handle = Interception::new();
if inner_handle.is_some() {
info!("Keyboard emulation with interception loaded");
} else {
error!("Keyboard emulation cannot load interception, falling back to SendKeys()");
}
inner_handle
}
false => None,
};
let direct_input = interception_handle.is_some();
let mut kb_buf = [INPUT {
type_: INPUT_KEYBOARD,
u: unsafe { mem::zeroed() },
@@ -178,13 +209,24 @@ impl KeyboardOutput {
inner.dwExtraInfo = 0;
}
let kb_direct_buf = [Stroke::Keyboard {
code: ScanCode::Esc,
state: KeyState::UP,
information: 0,
}; 41];
Self {
ground_to_idx,
idx_to_keycode,
// keycode_to_idx,
input_to_idx: input_to_key_idx,
key_idx_to_keycode,
key_idx_to_scancode,
next_keys: [false; 41],
last_keys: [false; 41],
direct_input,
interception_handle,
kb_buf,
kb_direct_buf,
n_kb_buf: 0,
}
}
@@ -198,24 +240,52 @@ impl KeyboardOutput {
.zip(self.last_keys.iter_mut())
.enumerate()
{
let keycode = self.idx_to_keycode[i];
if keycode == 0 {
let keycode = self.key_idx_to_keycode[i];
let scancode = self.key_idx_to_scancode[i];
if (!self.direct_input && keycode == 0) || (self.direct_input && scancode.is_none()) {
continue;
}
match (*n, *l) {
(true, false) => {
match (self.direct_input, *n, *l) {
(false, true, false) => {
let inner: &mut KEYBDINPUT = unsafe { self.kb_buf[self.n_kb_buf as usize].u.ki_mut() };
inner.wVk = keycode;
inner.dwFlags = 0;
self.n_kb_buf += 1;
// println!("{} down", keycode);
}
(false, true) => {
(false, false, true) => {
let inner: &mut KEYBDINPUT = unsafe { self.kb_buf[self.n_kb_buf as usize].u.ki_mut() };
inner.wVk = keycode;
inner.dwFlags = KEYEVENTF_KEYUP;
self.n_kb_buf += 1;
// println!("{} up", keycode);
}
(true, true, false) => {
// info!("keydown {:?}", scancode);
let inner: &mut Stroke = &mut self.kb_direct_buf[self.n_kb_buf as usize];
if let Stroke::Keyboard {
code,
state,
information: _,
} = inner
{
*code = scancode.unwrap();
*state = KeyState::DOWN;
self.n_kb_buf += 1;
}
}
(true, false, true) => {
// info!("keyup {:?}", scancode);
let inner: &mut Stroke = &mut self.kb_direct_buf[self.n_kb_buf as usize];
if let Stroke::Keyboard {
code,
state,
information: _,
} = inner
{
*code = scancode.unwrap();
*state = KeyState::UP;
self.n_kb_buf += 1;
}
}
_ => {}
}
@@ -223,12 +293,19 @@ impl KeyboardOutput {
}
if self.n_kb_buf != 0 {
unsafe {
SendInput(
self.n_kb_buf,
self.kb_buf.as_mut_ptr(),
mem::size_of::<INPUT>() as c_int,
);
match self.direct_input {
false => unsafe {
SendInput(
self.n_kb_buf,
self.kb_buf.as_mut_ptr(),
mem::size_of::<INPUT>() as c_int,
);
},
true => {
if let Some(handle) = self.interception_handle.as_mut() {
handle.send(1, &self.kb_direct_buf[0..self.n_kb_buf as usize]);
}
}
}
}
}
@@ -239,7 +316,7 @@ impl OutputHandler for KeyboardOutput {
self.next_keys.fill(false);
for (idx, x) in flat_input.iter().enumerate() {
if *x {
self.next_keys[self.ground_to_idx[idx]] = true;
self.next_keys[self.input_to_idx[idx]] = true;
}
}
self.send();

View File

@@ -42,9 +42,10 @@ impl AsyncJob for OutputJob {
layout,
polling,
sensitivity,
direct_input,
} => {
self.sensitivity = sensitivity;
self.handler = Some(Box::new(KeyboardOutput::new(layout.clone())));
self.handler = Some(Box::new(KeyboardOutput::new(layout.clone(), direct_input)));
self.timer = interval(Duration::from_micros(polling.to_t_u64()));
true