vitaly 0.1.32

VIA/Vial API client and cli tool for guiless keyboard configuration.
Documentation
use crate::common;
use crate::keymap;
use crate::protocol;
use anyhow::{Result, anyhow};
use hidapi::{DeviceInfo, HidApi};
use serde_json::{Value, json};
use std::collections::HashMap;
use std::fs;

pub fn run(
    api: &HidApi,
    device: &DeviceInfo,
    meta_file: &Option<String>,
    file: &String,
) -> Result<()> {
    let device_path = device.path();
    let dev = api.open_path(device_path)?;

    let uid: u64 = protocol::load_uid(&dev)?;
    let capabilities = protocol::scan_capabilities(&dev)?;
    let meta = common::load_meta(&dev, &capabilities, meta_file)?;
    let cols = meta["matrix"]["cols"]
        .as_u64()
        .ok_or(anyhow!("matrix/cols not found in meta"))? as u8;
    let rows = meta["matrix"]["rows"]
        .as_u64()
        .ok_or(anyhow!("matrix/rows not found in meta"))? as u8;

    let encoders_count = keymap::get_encoders_count(&meta["layouts"]["keymap"])?;
    let mut encoders = Vec::new();
    for layer_number in 0..capabilities.layer_count {
        let mut layer_encoders = Vec::new();
        for encoder_index in 0..encoders_count {
            layer_encoders.push(protocol::load_encoder(&dev, layer_number, encoder_index)?);
        }
        encoders.push(layer_encoders);
    }

    let keys = protocol::load_layers_keys(&dev, capabilities.layer_count, rows, cols)?;
    let combos = match capabilities.combo_count {
        0 => Vec::new(),
        _ => protocol::load_combos(&dev, capabilities.combo_count)?,
    };
    let tap_dances = match capabilities.tap_dance_count {
        0 => Vec::new(),
        _ => protocol::load_tap_dances(&dev, capabilities.tap_dance_count)?,
    };
    let macros = protocol::load_macros(
        &dev,
        capabilities.macro_count,
        capabilities.macro_buffer_size,
    )?;

    let key_overrides = match capabilities.key_override_count {
        0 => Vec::new(),
        _ => protocol::load_key_overrides(&dev, capabilities.key_override_count)?,
    };

    let alt_repeats = match capabilities.alt_repeat_key_count {
        0 => Vec::new(),
        _ => protocol::load_alt_repeats(&dev, capabilities.alt_repeat_key_count)?,
    };

    let qmk_settings = if capabilities.vial_version >= protocol::VIAL_PROTOCOL_QMK_SETTINGS {
        protocol::load_qmk_settings(&dev)?
    } else {
        HashMap::new()
    };

    let layout_options = match &meta["layouts"]["labels"] {
        Value::Null => -1,
        _ => protocol::load_layout_options(&dev)? as i64,
    };

    let mut result = json!({
        "version": 1,
        "via_protocol": capabilities.via_version,
        "uid": uid,
        "layout": keys.to_json(capabilities.vial_version)?,
        "layout_options": layout_options,
    });

    result
        .as_object_mut()
        .ok_or(anyhow!("broken root"))?
        .insert(
            "encoder_layout".to_string(),
            Value::Array(protocol::encoders_to_json(
                &encoders,
                capabilities.vial_version,
            )?),
        );

    if capabilities.vial_version > 0 {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert(
                "vial_protocol".to_string(),
                capabilities.vial_version.into(),
            );
    }

    if !alt_repeats.is_empty() {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert(
                "alt_repeat_key".to_string(),
                Value::Array(protocol::alt_repeats_to_json(
                    &alt_repeats,
                    capabilities.vial_version,
                )?),
            );
    }

    if !key_overrides.is_empty() {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert(
                "key_override".to_string(),
                Value::Array(protocol::key_overrides_to_json(
                    &key_overrides,
                    capabilities.vial_version,
                )?),
            );
    }

    if !combos.is_empty() {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert(
                "combo".to_string(),
                Value::Array(protocol::combos_to_json(
                    &combos,
                    capabilities.vial_version,
                )?),
            );
    }

    if !tap_dances.is_empty() {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert(
                "tap_dance".to_string(),
                Value::Array(protocol::tap_dances_to_json(
                    &tap_dances,
                    capabilities.vial_version,
                )?),
            );
    }

    if !macros.is_empty() {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert(
                "macro".to_string(),
                Value::Array(protocol::macros_to_json(
                    &macros,
                    capabilities.vial_version,
                )?),
            );
    } else {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert("macro".to_string(), Value::Array([].to_vec()));
    }

    if !qmk_settings.is_empty() {
        result
            .as_object_mut()
            .ok_or(anyhow!("broken root"))?
            .insert(
                "settings".to_string(),
                protocol::qmk_settings_to_json(&qmk_settings)?,
            );
    }

    fs::write(file, result.to_string())?;
    println!("\nConfiguration saved to file {}", file);
    Ok(())
}