use crate::keycodes;
use crate::protocol::{
CMD_VIA_VIAL_PREFIX, CMD_VIAL_DYNAMIC_ENTRY_OP, DYNAMIC_VIAL_TAP_DANCE_GET,
DYNAMIC_VIAL_TAP_DANCE_SET, ProtocolError, VIA_UNHANDLED, send, send_recv,
};
use anyhow::{Result, anyhow};
use hidapi::HidDevice;
use serde_json::{Value, json};
use thiserror::Error;
#[derive(Error, Debug)]
#[error("{0}")]
pub struct TapDanceFormatError(pub String);
#[derive(Debug)]
pub struct TapDance {
pub index: u8,
pub tap: u16,
pub hold: u16,
pub double_tap: u16,
pub tap_hold: u16,
pub tapping_term: u16,
}
impl TapDance {
pub fn from_string(index: u8, value: &str, vial_version: u32) -> Result<TapDance> {
let (keys_string, output) = value
.split_once("~")
.ok_or(anyhow!("tapping term in ms should be passed after ~"))?;
let tapping_term: u16 = output.replace(" ", "").parse()?;
let keys: Vec<_> = keys_string.split("+").collect();
let mut ks: [u16; 4] = [0x0; 4];
for (idx, kn) in keys.iter().enumerate() {
ks[idx] = keycodes::name_to_qid(kn, vial_version)?;
}
Ok(TapDance {
index,
tap: ks[0],
hold: ks[1],
double_tap: ks[2],
tap_hold: ks[3],
tapping_term,
})
}
pub fn from_json(index: u8, tap_dances_json: &Value, vial_version: u32) -> Result<TapDance> {
let mut ks: [u16; 5] = [0x0; 5];
let values = tap_dances_json
.as_array()
.ok_or(anyhow!("TapDances should be encoded into array"))?;
for (pos, val) in values.iter().enumerate() {
match pos {
0..=3 => {
let value_string = val
.as_str()
.ok_or(anyhow!("TapDance elements 0-3 should be strings"))?;
let qid = keycodes::name_to_qid(value_string, vial_version)?;
ks[pos] = qid
}
4 => {
ks[pos] = val
.as_u64()
.ok_or(anyhow!("TapDance 3th element should be positive number"))?
as u16
}
_ => {
return Err(TapDanceFormatError(
"TapDance array should be strictly 5 elements long".to_string(),
)
.into());
}
}
}
Ok(TapDance {
index,
tap: ks[0],
hold: ks[1],
double_tap: ks[2],
tap_hold: ks[3],
tapping_term: ks[4],
})
}
pub fn is_empty(&self) -> bool {
self.tap == 0 && self.hold == 0 && self.double_tap == 0 && self.tap_hold == 0
}
pub fn empty(index: u8) -> TapDance {
TapDance {
index,
tap: 0,
hold: 0,
double_tap: 0,
tap_hold: 0,
tapping_term: 0,
}
}
pub fn dump(&self, vial_version: u32) -> Result<(), std::fmt::Error> {
print!("{}) ", self.index);
if self.is_empty() {
print!("EMPTY");
} else {
if self.tap != 0 {
print!(
"On tap: {}, ",
keycodes::qid_to_name(self.tap, vial_version)
);
}
if self.hold != 0 {
print!(
"On hold: {}, ",
keycodes::qid_to_name(self.hold, vial_version)
);
}
if self.double_tap != 0 {
print!(
"On double tap: {}, ",
keycodes::qid_to_name(self.double_tap, vial_version)
);
}
if self.tap_hold != 0 {
print!(
"On tap + hold: {}, ",
keycodes::qid_to_name(self.tap_hold, 6)
);
}
print!("Tapping term (ms) = {}", self.tapping_term);
}
Ok(())
}
}
pub fn load_tap_dances(device: &HidDevice, count: u8) -> Result<Vec<TapDance>> {
let mut tapdances: Vec<TapDance> = vec![];
for idx in 0..count {
match send_recv(
device,
&[
CMD_VIA_VIAL_PREFIX,
CMD_VIAL_DYNAMIC_ENTRY_OP,
DYNAMIC_VIAL_TAP_DANCE_GET,
idx,
],
) {
Ok(buff) => {
if buff[0] != VIA_UNHANDLED {
let tapdance = TapDance {
index: idx,
tap: ((buff[2] as u16) << 8) + buff[1] as u16,
hold: ((buff[4] as u16) << 8) + buff[3] as u16,
double_tap: ((buff[6] as u16) << 8) + buff[5] as u16,
tap_hold: ((buff[8] as u16) << 8) + buff[7] as u16,
tapping_term: ((buff[10] as u16) << 8) + buff[9] as u16,
};
tapdances.push(tapdance)
} else {
return Err(ProtocolError::ViaUnhandledError.into());
}
}
Err(e) => return Err(e),
}
}
Ok(tapdances)
}
pub fn load_tap_dances_from_json(
tap_dances_json: &Value,
vial_version: u32,
) -> Result<Vec<TapDance>> {
let mut result = Vec::new();
let tap_dances = tap_dances_json
.as_array()
.ok_or(anyhow!("TapDances should be encoded as array"))?;
for (i, tap_dance) in tap_dances.iter().enumerate() {
result.push(TapDance::from_json(i as u8, tap_dance, vial_version)?)
}
Ok(result)
}
pub fn set_tap_dance(device: &HidDevice, tapdance: &TapDance) -> Result<()> {
match send(
device,
&[
CMD_VIA_VIAL_PREFIX,
CMD_VIAL_DYNAMIC_ENTRY_OP,
DYNAMIC_VIAL_TAP_DANCE_SET,
tapdance.index,
(tapdance.tap & 0xFF) as u8,
((tapdance.tap >> 8) & 0xFF) as u8,
(tapdance.hold & 0xFF) as u8,
((tapdance.hold >> 8) & 0xFF) as u8,
(tapdance.double_tap & 0xFF) as u8,
((tapdance.double_tap >> 8) & 0xFF) as u8,
(tapdance.tap_hold & 0xFF) as u8,
((tapdance.tap_hold >> 8) & 0xFF) as u8,
(tapdance.tapping_term & 0xFF) as u8,
((tapdance.tapping_term >> 8) & 0xFF) as u8,
],
) {
Ok(_) => Ok(()),
Err(e) => Err(ProtocolError::HidError(e).into()),
}
}
pub fn tap_dances_to_json(tap_dances: &Vec<TapDance>, vial_version: u32) -> Result<Vec<Value>> {
let mut result = Vec::new();
for tap_dance in tap_dances {
result.push(json!([
keycodes::qid_to_name(tap_dance.tap, vial_version),
keycodes::qid_to_name(tap_dance.hold, vial_version),
keycodes::qid_to_name(tap_dance.double_tap, vial_version),
keycodes::qid_to_name(tap_dance.tap_hold, vial_version),
tap_dance.tapping_term,
]))
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_tap_hold() {
let tapdance = TapDance::from_string(7, &"KC_V + KC_B ~ 50".to_string(), 6).unwrap();
assert_eq!(tapdance.index, 7);
assert_eq!(keycodes::qid_to_name(tapdance.tap, 6), "KC_V");
assert_eq!(keycodes::qid_to_name(tapdance.hold, 6), "KC_B");
assert_eq!(tapdance.double_tap, 0);
assert_eq!(tapdance.tap_hold, 0);
assert_eq!(tapdance.tapping_term, 50);
}
#[test]
fn test_from_string_one_key() {
let td = TapDance::from_string(0, &"KC_A ~ 100".to_string(), 6).unwrap();
assert_eq!(keycodes::qid_to_name(td.tap, 6), "KC_A");
assert_eq!(td.hold, 0);
assert_eq!(td.tapping_term, 100);
}
#[test]
fn test_from_string_four_keys() {
let td = TapDance::from_string(1, &"KC_A+KC_B+KC_C+KC_D ~ 200".to_string(), 6).unwrap();
assert_eq!(keycodes::qid_to_name(td.tap, 6), "KC_A");
assert_eq!(keycodes::qid_to_name(td.hold, 6), "KC_B");
assert_eq!(keycodes::qid_to_name(td.double_tap, 6), "KC_C");
assert_eq!(keycodes::qid_to_name(td.tap_hold, 6), "KC_D");
assert_eq!(td.tapping_term, 200);
}
#[test]
fn test_from_string_errors() {
assert!(
TapDance::from_string(0, &"KC_A".to_string(), 6).is_err(),
"Missing ~"
);
assert!(
TapDance::from_string(0, &"KC_A ~ abc".to_string(), 6).is_err(),
"Invalid tapping term"
);
assert!(
TapDance::from_string(0, &"INVALID ~ 100".to_string(), 6).is_err(),
"Invalid keycode"
);
}
#[test]
fn test_from_json_valid() {
let json = json!(["KC_A", "KC_B", "KC_C", "KC_D", 250]);
let td = TapDance::from_json(0, &json, 6).unwrap();
assert_eq!(keycodes::qid_to_name(td.tap, 6), "KC_A");
assert_eq!(keycodes::qid_to_name(td.hold, 6), "KC_B");
assert_eq!(keycodes::qid_to_name(td.double_tap, 6), "KC_C");
assert_eq!(keycodes::qid_to_name(td.tap_hold, 6), "KC_D");
assert_eq!(td.tapping_term, 250);
}
#[test]
fn test_from_json_errors() {
assert!(
TapDance::from_json(0, &json!("KC_A"), 6).is_err(),
"Not an array"
);
let td = TapDance::from_json(0, &json!(["KC_A"]), 6).unwrap();
assert_eq!(keycodes::qid_to_name(td.tap, 6), "KC_A");
assert_eq!(td.hold, 0);
assert!(
TapDance::from_json(0, &json!(["KC_A", "KC_B", "KC_C", "KC_D", 200, "KC_E"]), 6)
.is_err(),
"Array too long"
);
assert!(
TapDance::from_json(0, &json!(["KC_A", "KC_B", "KC_C", "KC_D", "KC_E"]), 6).is_err(),
"Tapping term not a number"
);
assert!(
TapDance::from_json(0, &json!([1, 2, 3, 4, 100]), 6).is_err(),
"Keycode not a string"
);
assert!(
TapDance::from_json(0, &json!(["INVALID", "KC_B", "KC_C", "KC_D", 100]), 6).is_err(),
"Invalid keycode"
);
}
#[test]
fn test_empty_and_is_empty() {
let empty_td = TapDance::empty(0);
assert!(empty_td.is_empty());
assert_eq!(empty_td.tapping_term, 0);
let non_empty_td = TapDance::from_string(1, &"KC_A ~ 100".to_string(), 6).unwrap();
assert!(!non_empty_td.is_empty());
}
}