From 7f2cb52d535ece13ff7d50012774d43936d69d89 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Tue, 31 Dec 2019 08:25:40 -0800 Subject: Add config file parsing and loading --- Cargo.lock | 52 ++++++++++++++++++++++++++ Cargo.toml | 3 ++ pixelbookgo.toml | 23 ++++++++++++ src/main.rs | 36 ++++++------------ src/mapping.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 197 insertions(+), 26 deletions(-) create mode 100644 pixelbookgo.toml diff --git a/Cargo.lock b/Cargo.lock index d60be47..caf74d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,7 +116,10 @@ dependencies = [ "evdev-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -278,6 +281,24 @@ dependencies = [ "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.8.0" @@ -340,6 +361,24 @@ dependencies = [ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thiserror" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.3.6" @@ -358,6 +397,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-segmentation" version = "1.6.0" @@ -453,6 +500,8 @@ dependencies = [ "checksum regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc220bd33bdce8f093101afe22a037b8eb0e5af33592e6a9caafff0d4cb81cbd" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum rustversion 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0538bd897e17257b0128d2fd95c2ed6df939374073a36166051a79e2eb7986" +"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" +"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum structopt 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "884ae79d6aad1e738f4a70dff314203fd498490a63ebc4d03ea83323c40b7b72" "checksum structopt-derive 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0a97f829a34a0a9d5b353a881025a23b8c9fd09d46be6045df6b22920dbd7a93" @@ -460,8 +509,11 @@ dependencies = [ "checksum syn-mid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0" "checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thiserror 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f357d1814b33bc2dc221243f8424104bfe72dbe911d5b71b3816a2dff1c977e" +"checksum thiserror-impl 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum toml 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "01d1404644c8b12b16bfcffa4322403a91a451584daaaa7c28d3152e6cbc98cf" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" diff --git a/Cargo.toml b/Cargo.toml index 9185fe2..27e27a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,6 @@ anyhow = "1.0" log = "0.4" pretty_env_logger = "0.3" structopt = "0.3" +serde = { version="1.0", features=["derive"]} +thiserror = "1.0" +toml = "0.5" diff --git a/pixelbookgo.toml b/pixelbookgo.toml new file mode 100644 index 0000000..b77d19e --- /dev/null +++ b/pixelbookgo.toml @@ -0,0 +1,23 @@ +device_name = "AT Translated Set 2 keyboard" + +[[dual_role]] +input = "KEY_CAPSLOCK" +hold = ["KEY_LEFTCTRL"] +tap = ["KEY_ESC"] + +[[remap]] +input = ["KEY_F1"] +output = ["KEY_BACK"] + +[[remap]] +input = ["KEY_F5"] +output = ["KEY_BRIGHTNESSDOWN"] + +[[remap]] +input = ["KEY_F6"] +output = ["KEY_BRIGHTNESSUP"] + +[[remap]] +input = ["KEY_F8"] +output = ["KEY_MUTE"] + diff --git a/src/main.rs b/src/main.rs index 3473bf7..e71ff25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use crate::mapping::*; use crate::remapper::*; use anyhow::*; +use std::path::PathBuf; use std::time::Duration; use structopt::StructOpt; @@ -20,6 +21,10 @@ struct Opt { /// configuration #[structopt(name = "list-devices", long)] list_devices: bool, + + /// Specify the configuration file to be loaded + #[structopt(name = "config-file", long)] + config_file: PathBuf, } fn main() -> Result<()> { @@ -30,36 +35,17 @@ fn main() -> Result<()> { return deviceinfo::list_devices(); } - let mappings = vec![ - Mapping::DualRole { - input: KeyCode::KEY_CAPSLOCK, - hold: vec![KeyCode::KEY_LEFTCTRL], - tap: vec![KeyCode::KEY_ESC], - }, - Mapping::Remap { - input: [KeyCode::KEY_F1].into_iter().cloned().collect(), - output: [KeyCode::KEY_BACK].into_iter().cloned().collect(), - }, - Mapping::Remap { - input: [KeyCode::KEY_F8].into_iter().cloned().collect(), - output: [KeyCode::KEY_MUTE].into_iter().cloned().collect(), - }, - Mapping::Remap { - input: [KeyCode::KEY_F5].into_iter().cloned().collect(), - output: [KeyCode::KEY_BRIGHTNESSDOWN].into_iter().cloned().collect(), - }, - Mapping::Remap { - input: [KeyCode::KEY_F6].into_iter().cloned().collect(), - output: [KeyCode::KEY_BRIGHTNESSUP].into_iter().cloned().collect(), - }, - ]; + let mapping_config = MappingConfig::from_file(&opt.config_file).context(format!( + "loading --config-file={}", + opt.config_file.display() + ))?; log::error!("Short delay: release any keys now!"); std::thread::sleep(Duration::new(2, 0)); - let device_info = deviceinfo::DeviceInfo::with_name("AT Translated Set 2 keyboard")?; + let device_info = deviceinfo::DeviceInfo::with_name(&mapping_config.device_name)?; - let mut mapper = InputMapper::create_mapper(device_info.path, mappings)?; + let mut mapper = InputMapper::create_mapper(device_info.path, mapping_config.mappings)?; mapper.run_mapper()?; Ok(()) } diff --git a/src/mapping.rs b/src/mapping.rs index 6fa655e..34a9615 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -1,5 +1,36 @@ -pub use evdev_rs::enums::{EventCode, EV_KEY as KeyCode}; +use anyhow::Context; +pub use evdev_rs::enums::{EventCode, EventType, EV_KEY as KeyCode}; +use serde::Deserialize; use std::collections::HashSet; +use std::path::Path; +use thiserror::Error; + +#[derive(Debug, Clone)] +pub struct MappingConfig { + pub device_name: String, + pub mappings: Vec, +} + +impl MappingConfig { + pub fn from_file>(path: P) -> anyhow::Result { + let path = path.as_ref(); + let toml_data = std::fs::read_to_string(path) + .context(format!("reading toml from {}", path.display()))?; + let config_file: ConfigFile = + toml::from_str(&toml_data).context(format!("parsing toml from {}", path.display()))?; + let mut mappings = vec![]; + for dual in config_file.dual_role { + mappings.push(dual.into()); + } + for remap in config_file.remap { + mappings.push(remap.into()); + } + Ok(Self { + device_name: config_file.device_name, + mappings, + }) + } +} #[derive(Debug, Clone, Eq, PartialEq)] pub enum Mapping { @@ -13,3 +44,79 @@ pub enum Mapping { output: HashSet, }, } + +#[derive(Debug, Deserialize)] +#[serde(try_from = "String")] +struct KeyCodeWrapper { + pub code: KeyCode, +} + +impl Into for KeyCodeWrapper { + fn into(self) -> KeyCode { + self.code + } +} + +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("Invalid key `{0}`")] + InvalidKey(String), + #[error("Impossible: parsed KEY_XXX but not into an EV_KEY")] + ImpossibleParseKey, +} + +impl std::convert::TryFrom for KeyCodeWrapper { + type Error = ConfigError; + fn try_from(s: String) -> Result { + match EventCode::from_str(&EventType::EV_KEY, &s) { + Some(code) => match code { + EventCode::EV_KEY(code) => Ok(KeyCodeWrapper { code }), + _ => Err(ConfigError::ImpossibleParseKey), + }, + None => Err(ConfigError::InvalidKey(s)), + } + } +} + +#[derive(Debug, Deserialize)] +struct DualRoleConfig { + input: KeyCodeWrapper, + hold: Vec, + tap: Vec, +} + +impl Into for DualRoleConfig { + fn into(self) -> Mapping { + Mapping::DualRole { + input: self.input.into(), + hold: self.hold.into_iter().map(Into::into).collect(), + tap: self.tap.into_iter().map(Into::into).collect(), + } + } +} + +#[derive(Debug, Deserialize)] +struct RemapConfig { + input: Vec, + output: Vec, +} + +impl Into for RemapConfig { + fn into(self) -> Mapping { + Mapping::Remap { + input: self.input.into_iter().map(Into::into).collect(), + output: self.output.into_iter().map(Into::into).collect(), + } + } +} + +#[derive(Debug, Deserialize)] +struct ConfigFile { + device_name: String, + + #[serde(default)] + dual_role: Vec, + + #[serde(default)] + remap: Vec, +} -- cgit v1.2.3