From f9c8c4671e20a86469c03fad7ae3f772c7813dda Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Sat, 31 May 2025 01:49:07 +0200 Subject: [PATCH] Add basic key input --- bin/src/config.rs | 2 + bin/src/daemon.rs | 12 +++-- bin/src/key_input.rs | 102 +++++++++++++++++++++++++++++++++++++++ dist/config.toml.example | 6 ++- 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 bin/src/key_input.rs diff --git a/bin/src/config.rs b/bin/src/config.rs index 27d36fb..56ebef0 100644 --- a/bin/src/config.rs +++ b/bin/src/config.rs @@ -11,6 +11,7 @@ pub struct Config { pub ui_level: u8, pub enable_dummy_analyzer: bool, pub colorblind_mode: bool, + pub key_input_mode: u8, } impl Default for Config { @@ -22,6 +23,7 @@ impl Default for Config { ui_level: 1, enable_dummy_analyzer: false, colorblind_mode: false, + key_input_mode: 1, } } } diff --git a/bin/src/daemon.rs b/bin/src/daemon.rs index f960815..6bc1592 100644 --- a/bin/src/daemon.rs +++ b/bin/src/daemon.rs @@ -4,6 +4,7 @@ mod diag; mod display; mod dummy_analyzer; mod error; +mod key_input; mod pcap; mod qmdl_store; mod server; @@ -175,7 +176,7 @@ async fn main() -> Result<(), RayhunterError> { let store = init_qmdl_store(&config).await?; let analysis_status = AnalysisStatus::new(&store); let qmdl_store_lock = Arc::new(RwLock::new(store)); - let (tx, rx) = mpsc::channel::(1); + let (diag_tx, diag_rx) = mpsc::channel::(1); let (ui_update_tx, ui_update_rx) = mpsc::channel::(1); let (analysis_tx, analysis_rx) = mpsc::channel::(5); let mut maybe_ui_shutdown_tx = None; @@ -193,13 +194,16 @@ async fn main() -> Result<(), RayhunterError> { run_diag_read_thread( &task_tracker, dev, - rx, + diag_rx, ui_update_tx.clone(), qmdl_store_lock.clone(), config.enable_dummy_analyzer, ); info!("Starting UI"); display::update_ui(&task_tracker, &config, ui_shutdown_rx, ui_update_rx); + + info!("Starting Key Input service"); + key_input::run_key_input_thread(&task_tracker, &config, diag_tx.clone()); } let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>(); info!("create shutdown thread"); @@ -213,7 +217,7 @@ async fn main() -> Result<(), RayhunterError> { ); run_ctrl_c_thread( &task_tracker, - tx.clone(), + diag_tx.clone(), server_shutdown_tx, maybe_ui_shutdown_tx, qmdl_store_lock.clone(), @@ -221,7 +225,7 @@ async fn main() -> Result<(), RayhunterError> { ); let state = Arc::new(ServerState { qmdl_store_lock: qmdl_store_lock.clone(), - diag_device_ctrl_sender: tx, + diag_device_ctrl_sender: diag_tx, ui_update_sender: ui_update_tx, debug_mode: config.debug_mode, analysis_status_lock, diff --git a/bin/src/key_input.rs b/bin/src/key_input.rs new file mode 100644 index 0000000..e2fa54d --- /dev/null +++ b/bin/src/key_input.rs @@ -0,0 +1,102 @@ +use log::error; +use std::time::{Duration, Instant}; +use tokio::fs::File; +use tokio::io::AsyncReadExt; +use tokio::sync::mpsc::Sender; +use tokio_util::task::TaskTracker; + +use crate::config; +use crate::diag::DiagDeviceCtrlMessage; + +#[derive(Debug)] +enum Event { + KeyDown, + KeyUp, +} + +const INPUT_EVENT_SIZE: usize = 32; + +pub fn run_key_input_thread( + task_tracker: &TaskTracker, + config: &config::Config, + diag_tx: Sender, +) { + if config.key_input_mode == 0 { + return; + } + + task_tracker.spawn(async move { + // Open the input device + let mut file = match File::open("/dev/input/event0").await { + Ok(file) => file, + Err(e) => { + error!("Failed to open /dev/input/event0: {}", e); + return; + } + }; + + let mut buffer = [0u8; INPUT_EVENT_SIZE]; + let mut last_keyup: Option = None; + + loop { + if let Err(e) = file.read_exact(&mut buffer).await { + error!("failed to read key input: {}", e); + return; + } + + let event = parse_event(buffer); + + match event { + Event::KeyUp => { + if last_keyup.is_some() + && last_keyup.unwrap().elapsed() < Duration::from_millis(500) + { + if let Err(e) = diag_tx.send(DiagDeviceCtrlMessage::StopRecording).await { + error!("Failed to send StopRecording: {}", e); + } + if let Err(e) = diag_tx.send(DiagDeviceCtrlMessage::StartRecording).await { + error!("Failed to send StartRecording: {}", e); + } + last_keyup = None; + } else { + last_keyup = Some(Instant::now()); + } + } + Event::KeyDown => {} + } + } + }); +} + +fn parse_event(input: [u8; INPUT_EVENT_SIZE]) -> Event { + if input[12] == 0 { + Event::KeyUp + } else { + Event::KeyDown + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_event_keydown_m7350_v5() { + let input = [ + 0x57, 0x6c, 0x09, 0x00, 0x7c, 0xfb, 0x03, 0x00, + 0x01, 0x00, 0x74, 0x00, 0x01, 0x00, 0x00, 0x00, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + assert!(matches!(parse_event(input), Event::KeyDown)); + } + + #[test] + fn test_parse_event_keyup_m7350_v5() { + let input = [ + 0x57, 0x6c, 0x09, 0x00, 0x1b, 0x15, 0x05, 0x00, + 0x01, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + assert!(matches!(parse_event(input), Event::KeyUp)); + } +} diff --git a/dist/config.toml.example b/dist/config.toml.example index a7c3a2f..8ba7600 100644 --- a/dist/config.toml.example +++ b/dist/config.toml.example @@ -14,5 +14,9 @@ colorblind_mode = false # # TP-Link with one-bit display: # 0 = invisible mode -# 1..3 = show emoji for status. :) for running, :( for warnings, no mouth for paused. +# 1..3 = show emoji for status. :) for running, ! for warnings, no mouth for paused. ui_level = 1 + +# 0 = rayhunter does not read button presses +# 1 = double-tapping the power button starts/stops recordings +key_input_mode = 1