use evdev::Key;
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};
use crate::key_parser::{KeyParser, ParseError};
use crate::layer_manager::LayerManager;
pub type RemapTable = HashMap<Key, Key>;
#[derive(Debug)]
pub enum RemapError {
InvalidKey {
key: String,
source: String,
parse_error: String,
},
Config(String),
ParseError(ParseError),
}
impl fmt::Display for RemapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RemapError::InvalidKey { key, source, parse_error } => {
write!(f, "Invalid key name '{}' in {}: {}", key, source, parse_error)
}
RemapError::Config(msg) => write!(f, "Configuration error: {}", msg),
RemapError::ParseError(e) => write!(f, "Parse error: {}", e),
}
}
}
impl std::error::Error for RemapError {}
impl From<ParseError> for RemapError {
fn from(err: ParseError) -> Self {
RemapError::ParseError(err)
}
}
#[derive(Debug, Clone)]
pub struct RemapProfile {
pub name: String,
pub remaps: Arc<RwLock<HashMap<Key, Key>>>,
key_parser: Arc<KeyParser>,
}
impl RemapProfile {
pub fn new(
name: String,
config: &HashMap<String, String>,
) -> Result<Self, RemapError> {
let key_parser = Arc::new(KeyParser::new());
Self::with_key_parser(name, config, key_parser)
}
pub fn with_key_parser(
name: String,
config: &HashMap<String, String>,
key_parser: Arc<KeyParser>,
) -> Result<Self, RemapError> {
let mut remaps = HashMap::new();
for (input_name, output_name) in config.iter() {
let input_key = key_parser.parse(input_name).map_err(|e| {
RemapError::InvalidKey {
key: input_name.clone(),
source: "input".to_string(),
parse_error: e.to_string(),
}
})?;
let output_key = key_parser.parse(output_name).map_err(|e| {
RemapError::InvalidKey {
key: output_name.clone(),
source: "output".to_string(),
parse_error: e.to_string(),
}
})?;
remaps.insert(input_key, output_key);
}
Ok(Self {
name,
remaps: Arc::new(RwLock::new(remaps)),
key_parser,
})
}
pub fn name(&self) -> &str {
&self.name
}
pub async fn remap_count(&self) -> usize {
self.remaps.read().await.len()
}
pub async fn has_remap(&self, key_code: Key) -> bool {
self.remaps.read().await.contains_key(&key_code)
}
pub async fn get_remaps(&self) -> HashMap<Key, Key> {
self.remaps.read().await.clone()
}
pub fn remaps_arc(&self) -> &Arc<RwLock<HashMap<Key, Key>>> {
&self.remaps
}
pub fn key_parser(&self) -> &Arc<KeyParser> {
&self.key_parser
}
}
pub struct RemapEngine {
remaps: Arc<RwLock<HashMap<Key, Key>>>,
layer_remaps: Vec<Arc<RwLock<HashMap<Key, Key>>>>,
layer_manager: Arc<RwLock<LayerManager>>,
key_parser: Arc<KeyParser>,
}
impl RemapEngine {
pub fn new() -> Self {
let layer_remaps = vec![
Arc::new(RwLock::new(HashMap::new())), Arc::new(RwLock::new(HashMap::new())), Arc::new(RwLock::new(HashMap::new())), ];
Self {
remaps: Arc::new(RwLock::new(HashMap::new())),
layer_remaps,
layer_manager: Arc::new(RwLock::new(LayerManager::new(None))),
key_parser: Arc::new(KeyParser::new()),
}
}
pub fn with_key_parser(key_parser: Arc<KeyParser>) -> Self {
let layer_remaps = vec![
Arc::new(RwLock::new(HashMap::new())), Arc::new(RwLock::new(HashMap::new())), Arc::new(RwLock::new(HashMap::new())), ];
Self {
remaps: Arc::new(RwLock::new(HashMap::new())),
layer_remaps,
layer_manager: Arc::new(RwLock::new(LayerManager::new(None))),
key_parser,
}
}
pub fn set_layer_manager(&mut self, layer_manager: Arc<RwLock<LayerManager>>) {
self.layer_manager = layer_manager;
}
pub async fn load_layer_remap(
&self,
layer_id: usize,
config: &HashMap<String, String>,
) -> Result<(), RemapError> {
if layer_id >= self.layer_remaps.len() {
return Err(RemapError::Config(format!(
"Layer ID {} exceeds available layers ({} layers configured)",
layer_id,
self.layer_remaps.len()
)));
}
info!("Loading key remap configuration for layer {}", layer_id);
let mut parsed_remaps = HashMap::new();
for (input_name, output_name) in config.iter() {
let input_key = self.key_parser.parse(input_name).map_err(|e| {
RemapError::InvalidKey {
key: input_name.clone(),
source: "input".to_string(),
parse_error: e.to_string(),
}
})?;
let output_key = self.key_parser.parse(output_name).map_err(|e| {
RemapError::InvalidKey {
key: output_name.clone(),
source: "output".to_string(),
parse_error: e.to_string(),
}
})?;
parsed_remaps.insert(input_key, output_key);
debug!("Layer {} remap: {} -> {}", layer_id, input_name, output_name);
}
let mut layer_remap = self.layer_remaps[layer_id].write().await;
*layer_remap = parsed_remaps;
if layer_id == 0 {
let mut remaps = self.remaps.write().await;
*remaps = layer_remap.clone();
}
info!(
"Loaded {} key remappings for layer {} successfully",
layer_remap.len(),
layer_id
);
Ok(())
}
pub fn layer_count(&self) -> usize {
self.layer_remaps.len()
}
pub async fn load_config(
&self,
config: &HashMap<String, String>,
) -> Result<(), RemapError> {
info!("Loading key remap configuration");
let mut parsed_remaps = HashMap::new();
for (input_name, output_name) in config.iter() {
let input_key = self.key_parser.parse(input_name).map_err(|e| {
RemapError::InvalidKey {
key: input_name.clone(),
source: "input".to_string(),
parse_error: e.to_string(),
}
})?;
let output_key = self.key_parser.parse(output_name).map_err(|e| {
RemapError::InvalidKey {
key: output_name.clone(),
source: "output".to_string(),
parse_error: e.to_string(),
}
})?;
parsed_remaps.insert(input_key, output_key);
debug!("Remap: {} -> {}", input_name, output_name);
}
let mut remaps = self.remaps.write().await;
*remaps = parsed_remaps;
info!(
"Loaded {} key remappings successfully",
remaps.len()
);
Ok(())
}
pub async fn remap(&self, key_code: Key) -> Option<Key> {
let remaps = self.remaps.read().await;
let output = remaps.get(&key_code).copied();
if let Some(out) = output {
debug!("Remapped {:?} -> {:?}", key_code, out);
}
output
}
pub async fn process_event(&self, key_code: Key, value: i32) -> Option<(Key, i32)> {
let remaps = self.remaps.read().await;
if let Some(&output_key) = remaps.get(&key_code) {
Some((output_key, value))
} else {
None
}
}
pub async fn remap_count(&self) -> usize {
let remaps = self.remaps.read().await;
remaps.len()
}
pub async fn has_remap(&self, key_code: Key) -> bool {
let remaps = self.remaps.read().await;
remaps.contains_key(&key_code)
}
pub async fn clear(&self) {
let mut remaps = self.remaps.write().await;
let count = remaps.len();
remaps.clear();
info!("Cleared {} key remappings", count);
}
pub fn key_parser(&self) -> &Arc<KeyParser> {
&self.key_parser
}
pub async fn get_remaps(&self) -> HashMap<Key, Key> {
let remaps = self.remaps.read().await;
remaps.clone()
}
pub async fn remap_layer_aware(&self, device_id: &str, key_code: Key) -> Option<Key> {
let effective_layer = self.layer_manager.read().await.get_effective_layer(device_id).await;
for layer_id in (0..=effective_layer).rev() {
if let Some(remaps) = self.layer_remaps.get(layer_id) {
let remap_table = remaps.read().await;
if let Some(&output_key) = remap_table.get(&key_code) {
debug!(
"Layer-aware remap: device {} key {:?} -> {:?} (from layer {})",
device_id, key_code, output_key, layer_id
);
return Some(output_key);
}
}
}
debug!(
"Layer-aware remap: device {} key {:?} not found in any layer (effective: {})",
device_id, key_code, effective_layer
);
None
}
pub async fn process_event_layer_aware(
&self,
device_id: &str,
key_code: Key,
value: i32,
) -> Option<(Key, i32)> {
let effective_layer = self.layer_manager.read().await.get_effective_layer(device_id).await;
for layer_id in (0..=effective_layer).rev() {
if let Some(remaps) = self.layer_remaps.get(layer_id) {
let remap_table = remaps.read().await;
if let Some(&output_key) = remap_table.get(&key_code) {
debug!(
"Layer-aware event: device {} key {:?} -> {:?} (value: {}, from layer {})",
device_id, key_code, output_key, value, layer_id
);
return Some((output_key, value));
}
}
}
None
}
pub fn layer_manager(&self) -> &Arc<RwLock<LayerManager>> {
&self.layer_manager
}
pub async fn get_layer_remaps(&self, layer_id: usize) -> Option<HashMap<Key, Key>> {
if let Some(remaps) = self.layer_remaps.get(layer_id) {
let remap_table = remaps.read().await;
Some(remap_table.clone())
} else {
None
}
}
}
impl Default for RemapEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_remap_engine_creation() {
let engine = RemapEngine::new();
assert_eq!(engine.remap_count().await, 0);
}
#[tokio::test]
async fn test_load_config_basic() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
let result = engine.load_config(&config).await;
assert!(result.is_ok());
assert_eq!(engine.remap_count().await, 1);
}
#[tokio::test]
async fn test_load_config_with_friendly_names() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("a".to_string(), "b".to_string());
config.insert("capslock".to_string(), "leftctrl".to_string());
let result = engine.load_config(&config).await;
assert!(result.is_ok());
assert_eq!(engine.remap_count().await, 2);
}
#[tokio::test]
async fn test_remap_returns_correct_key() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
engine.load_config(&config).await.unwrap();
let result = engine.remap(Key::KEY_A).await;
assert_eq!(result, Some(Key::KEY_B));
let result = engine.remap(Key::KEY_C).await;
assert_eq!(result, None);
}
#[tokio::test]
async fn test_invalid_input_key_fails_validation() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("nonexistent_key".to_string(), "KEY_B".to_string());
let result = engine.load_config(&config).await;
assert!(result.is_err());
match result {
Err(RemapError::InvalidKey { key, source, .. }) => {
assert_eq!(key, "nonexistent_key");
assert_eq!(source, "input");
}
_ => panic!("Expected InvalidKey error"),
}
}
#[tokio::test]
async fn test_invalid_output_key_fails_validation() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "nonexistent_key".to_string());
let result = engine.load_config(&config).await;
assert!(result.is_err());
match result {
Err(RemapError::InvalidKey { key, source, .. }) => {
assert_eq!(key, "nonexistent_key");
assert_eq!(source, "output");
}
_ => panic!("Expected InvalidKey error"),
}
}
#[tokio::test]
async fn test_eager_validation_no_partial_load() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
config.insert("KEY_C".to_string(), "invalid_key".to_string());
let result = engine.load_config(&config).await;
assert!(result.is_err());
assert_eq!(engine.remap_count().await, 0);
}
#[tokio::test]
async fn test_case_insensitive_config() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("key_a".to_string(), "KEY_B".to_string());
config.insert("CAPSLOCK".to_string(), "leftctrl".to_string());
let result = engine.load_config(&config).await;
assert!(result.is_ok());
assert_eq!(engine.remap_count().await, 2);
assert_eq!(engine.remap(Key::KEY_A).await, Some(Key::KEY_B));
assert_eq!(engine.remap(Key::KEY_CAPSLOCK).await, Some(Key::KEY_LEFTCTRL));
}
#[tokio::test]
async fn test_has_remap() {
let engine = RemapEngine::new();
assert!(!engine.has_remap(Key::KEY_A).await);
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
engine.load_config(&config).await.unwrap();
assert!(engine.has_remap(Key::KEY_A).await);
assert!(!engine.has_remap(Key::KEY_C).await);
}
#[tokio::test]
async fn test_clear_remaps() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
config.insert("KEY_C".to_string(), "KEY_D".to_string());
engine.load_config(&config).await.unwrap();
assert_eq!(engine.remap_count().await, 2);
engine.clear().await;
assert_eq!(engine.remap_count().await, 0);
}
#[tokio::test]
async fn test_complex_remap_scenario() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("capslock".to_string(), "leftctrl".to_string());
config.insert("esc".to_string(), "grave".to_string());
engine.load_config(&config).await.unwrap();
assert_eq!(engine.remap(Key::KEY_CAPSLOCK).await, Some(Key::KEY_LEFTCTRL));
assert_eq!(engine.remap(Key::KEY_ESC).await, Some(Key::KEY_GRAVE));
}
#[tokio::test]
async fn test_shared_key_parser() {
let parser = Arc::new(KeyParser::new());
let _engine = RemapEngine::with_key_parser(parser.clone());
assert_eq!(parser.parse("a"), Ok(Key::KEY_A));
}
#[tokio::test]
async fn test_get_remaps() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
engine.load_config(&config).await.unwrap();
let remaps = engine.get_remaps().await;
assert_eq!(remaps.len(), 1);
assert_eq!(remaps.get(&Key::KEY_A), Some(&Key::KEY_B));
}
#[tokio::test]
async fn test_remap_to_none_for_unmapped_keys() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
engine.load_config(&config).await.unwrap();
assert!(engine.remap(Key::KEY_A).await.is_some());
assert!(engine.remap(Key::KEY_Z).await.is_none());
assert!(engine.remap(Key::KEY_0).await.is_none());
}
#[tokio::test]
async fn test_remap_profile_creation() {
let mut config = HashMap::new();
config.insert("capslock".to_string(), "leftctrl".to_string());
config.insert("a".to_string(), "b".to_string());
let profile = RemapProfile::new("test-profile".to_string(), &config);
assert!(profile.is_ok());
let profile = profile.unwrap();
assert_eq!(profile.name(), "test-profile");
assert_eq!(profile.remap_count().await, 2);
}
#[tokio::test]
async fn test_remap_profile_invalid_key_fails() {
let mut config = HashMap::new();
config.insert("invalid_key".to_string(), "KEY_A".to_string());
let result = RemapProfile::new("bad-profile".to_string(), &config);
assert!(result.is_err());
}
#[tokio::test]
async fn test_remap_profile_arc_cloning() {
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
let profile1 = RemapProfile::new("profile1".to_string(), &config).unwrap();
let profile2 = profile1.clone();
assert_eq!(profile1.name(), profile2.name());
assert_eq!(profile1.remap_count().await, profile2.remap_count().await);
}
#[tokio::test]
async fn test_layer_remap_creation() {
let engine = RemapEngine::new();
assert_eq!(engine.layer_count(), 3);
let layer_0 = engine.get_layer_remaps(0).await;
let layer_1 = engine.get_layer_remaps(1).await;
let layer_2 = engine.get_layer_remaps(2).await;
assert!(layer_0.is_some());
assert!(layer_1.is_some());
assert!(layer_2.is_some());
assert!(layer_0.unwrap().is_empty());
assert!(layer_1.unwrap().is_empty());
assert!(layer_2.unwrap().is_empty());
assert!(engine.get_layer_remaps(3).await.is_none());
}
#[tokio::test]
async fn test_load_layer_remap() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_X".to_string());
config.insert("KEY_B".to_string(), "KEY_Y".to_string());
let result = engine.load_layer_remap(1, &config).await;
assert!(result.is_ok());
let layer_1 = engine.get_layer_remaps(1).await.unwrap();
assert_eq!(layer_1.len(), 2);
assert_eq!(layer_1.get(&Key::KEY_A), Some(&Key::KEY_X));
assert_eq!(layer_1.get(&Key::KEY_B), Some(&Key::KEY_Y));
let layer_0 = engine.get_layer_remaps(0).await.unwrap();
assert!(layer_0.is_empty());
}
#[tokio::test]
async fn test_load_layer_remap_invalid_layer() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
let result = engine.load_layer_remap(5, &config).await;
assert!(result.is_err());
match result {
Err(RemapError::Config(msg)) => {
assert!(msg.contains("Layer ID 5"));
assert!(msg.contains("exceeds available layers"));
}
_ => panic!("Expected Config error with layer ID message"),
}
}
#[tokio::test]
async fn test_remap_layer_aware_base_layer() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
engine.load_layer_remap(0, &config).await.unwrap();
let result = engine.remap_layer_aware("test_device", Key::KEY_A).await;
assert_eq!(result, Some(Key::KEY_B));
let result = engine.remap_layer_aware("test_device", Key::KEY_C).await;
assert_eq!(result, None);
}
#[tokio::test]
async fn test_remap_layer_aware_cascade() {
let engine = RemapEngine::new();
let mut base_config = HashMap::new();
base_config.insert("KEY_A".to_string(), "KEY_B".to_string());
base_config.insert("KEY_C".to_string(), "KEY_D".to_string());
engine.load_layer_remap(0, &base_config).await.unwrap();
let mut layer1_config = HashMap::new();
layer1_config.insert("KEY_A".to_string(), "KEY_X".to_string());
engine.load_layer_remap(1, &layer1_config).await.unwrap();
engine.layer_manager.write().await.activate_hold_layer("test_device", 1).await.unwrap();
let result = engine.remap_layer_aware("test_device", Key::KEY_A).await;
assert_eq!(result, Some(Key::KEY_X));
let result = engine.remap_layer_aware("test_device", Key::KEY_C).await;
assert_eq!(result, Some(Key::KEY_D));
}
#[tokio::test]
async fn test_remap_layer_aware_priority() {
let engine = RemapEngine::new();
let mut base_config = HashMap::new();
base_config.insert("KEY_A".to_string(), "KEY_B".to_string());
engine.load_layer_remap(0, &base_config).await.unwrap();
let mut layer1_config = HashMap::new();
layer1_config.insert("KEY_A".to_string(), "KEY_X".to_string());
engine.load_layer_remap(1, &layer1_config).await.unwrap();
let mut layer2_config = HashMap::new();
layer2_config.insert("KEY_A".to_string(), "KEY_Y".to_string());
engine.load_layer_remap(2, &layer2_config).await.unwrap();
let lm = engine.layer_manager.write().await;
lm.activate_hold_layer("test_device", 1).await.unwrap();
lm.activate_hold_layer("test_device", 2).await.unwrap();
drop(lm);
let result = engine.remap_layer_aware("test_device", Key::KEY_A).await;
assert_eq!(result, Some(Key::KEY_Y));
engine.layer_manager.write().await.deactivate_hold_layer("test_device", 2).await.unwrap();
let result = engine.remap_layer_aware("test_device", Key::KEY_A).await;
assert_eq!(result, Some(Key::KEY_X));
engine.layer_manager.write().await.deactivate_hold_layer("test_device", 1).await.unwrap();
let result = engine.remap_layer_aware("test_device", Key::KEY_A).await;
assert_eq!(result, Some(Key::KEY_B));
}
#[tokio::test]
async fn test_remap_layer_aware_no_remap() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
engine.load_layer_remap(0, &config).await.unwrap();
engine.layer_manager.write().await.activate_layer("test_device", 1).await;
let result = engine.remap_layer_aware("test_device", Key::KEY_Z).await;
assert_eq!(result, None);
}
#[tokio::test]
async fn test_process_event_layer_aware() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_X".to_string());
engine.load_layer_remap(1, &config).await.unwrap();
engine.layer_manager.write().await.activate_hold_layer("test_device", 1).await.unwrap();
let result = engine.process_event_layer_aware("test_device", Key::KEY_A, 1).await;
assert_eq!(result, Some((Key::KEY_X, 1)));
let result = engine.process_event_layer_aware("test_device", Key::KEY_A, 0).await;
assert_eq!(result, Some((Key::KEY_X, 0)));
let result = engine.process_event_layer_aware("test_device", Key::KEY_A, 2).await;
assert_eq!(result, Some((Key::KEY_X, 2)));
let result = engine.process_event_layer_aware("test_device", Key::KEY_Z, 1).await;
assert_eq!(result, None);
}
#[tokio::test]
async fn test_layer_manager_accessor() {
let engine = RemapEngine::new();
let layer_manager = engine.layer_manager();
layer_manager.write().await.activate_layer("test_device", 1).await;
let effective = layer_manager.read().await.get_effective_layer("test_device").await;
assert_eq!(effective, 1);
}
#[tokio::test]
async fn test_multiple_devices_independent_layers() {
let engine = RemapEngine::new();
let mut config1 = HashMap::new();
config1.insert("KEY_A".to_string(), "KEY_X".to_string());
engine.load_layer_remap(1, &config1).await.unwrap();
let mut config2 = HashMap::new();
config2.insert("KEY_A".to_string(), "KEY_Y".to_string());
engine.load_layer_remap(2, &config2).await.unwrap();
let lm = engine.layer_manager.write().await;
lm.activate_hold_layer("device1", 1).await.unwrap();
lm.activate_hold_layer("device2", 2).await.unwrap();
drop(lm);
let result1 = engine.remap_layer_aware("device1", Key::KEY_A).await;
assert_eq!(result1, Some(Key::KEY_X));
let result2 = engine.remap_layer_aware("device2", Key::KEY_A).await;
assert_eq!(result2, Some(Key::KEY_Y));
let result3 = engine.remap_layer_aware("device3", Key::KEY_A).await;
assert_eq!(result3, None);
}
#[tokio::test]
async fn test_load_layer_remap_eager_validation() {
let engine = RemapEngine::new();
let mut config = HashMap::new();
config.insert("KEY_A".to_string(), "KEY_B".to_string());
config.insert("KEY_C".to_string(), "invalid_key".to_string());
let result = engine.load_layer_remap(1, &config).await;
assert!(result.is_err());
let layer_1 = engine.get_layer_remaps(1).await.unwrap();
assert!(layer_1.is_empty());
}
}