use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use super::settings::{
CameraControls, GestureOwner, LightSettings, Lighting, ScrollResolution, SmartShift,
deserialize_gesture_owner,
};
use crate::binding::{Action, Binding, ButtonId, GestureDirection};
use crate::device::{Capabilities, DeviceKind, DeviceModelInfo, LightCapabilities};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeviceIdentity {
pub display_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_info: Option<DeviceModelInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub codename: Option<String>,
pub kind: DeviceKind,
pub capabilities: Capabilities,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub light_capabilities: Option<LightCapabilities>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub driver_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub registry_model_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(from = "RawDeviceConfig")]
pub struct DeviceConfig {
#[serde(default = "default_true", skip_serializing_if = "is_true")]
pub enabled: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gesture_owner: Option<GestureOwner>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub identity: Option<DeviceIdentity>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub bindings: BTreeMap<ButtonId, Binding>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub per_app_bindings: BTreeMap<String, BTreeMap<ButtonId, Action>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dpi_presets: Vec<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dpi: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lighting: Option<Lighting>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub light: Option<LightSettings>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub smartshift: Option<SmartShift>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub camera_controls: Option<CameraControls>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub camera_profiles: BTreeMap<String, CameraControls>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub camera_profile: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub thumbwheel_sensitivity: Option<i32>,
#[serde(default, skip_serializing_if = "is_false")]
pub invert_scroll: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scroll_resolution: Option<ScrollResolution>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub host_switch_targets: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fn_lock: Option<bool>,
}
impl Default for DeviceConfig {
fn default() -> Self {
Self {
enabled: true,
gesture_owner: None,
identity: None,
bindings: BTreeMap::new(),
per_app_bindings: BTreeMap::new(),
dpi_presets: Vec::new(),
dpi: None,
lighting: None,
light: None,
smartshift: None,
camera_controls: None,
camera_profiles: BTreeMap::new(),
camera_profile: None,
thumbwheel_sensitivity: None,
invert_scroll: false,
scroll_resolution: None,
host_switch_targets: Vec::new(),
fn_lock: None,
}
}
}
fn default_true() -> bool {
true
}
#[allow(
clippy::trivially_copy_pass_by_ref,
reason = "serde's skip_serializing_if requires a fn(&T) -> bool signature"
)]
fn is_true(b: &bool) -> bool {
*b
}
#[allow(
clippy::trivially_copy_pass_by_ref,
reason = "serde's skip_serializing_if requires a fn(&T) -> bool signature"
)]
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Deserialize)]
struct RawDeviceConfig {
#[serde(default, deserialize_with = "deserialize_gesture_owner")]
gesture_owner: Option<GestureOwner>,
#[serde(default)]
identity: Option<DeviceIdentity>,
#[serde(default)]
bindings: BTreeMap<ButtonId, Binding>,
#[serde(default)]
button_bindings: BTreeMap<ButtonId, Action>,
#[serde(default)]
gesture_bindings: BTreeMap<GestureDirection, Action>,
#[serde(default)]
per_app_bindings: BTreeMap<String, BTreeMap<ButtonId, Action>>,
#[serde(default)]
dpi_presets: Vec<u32>,
#[serde(default)]
dpi: Option<u32>,
#[serde(default)]
lighting: Option<Lighting>,
#[serde(default)]
light: Option<LightSettings>,
#[serde(default)]
smartshift: Option<SmartShift>,
#[serde(default)]
camera_controls: Option<CameraControls>,
#[serde(default)]
camera_profiles: BTreeMap<String, CameraControls>,
#[serde(default)]
camera_profile: Option<String>,
#[serde(default)]
thumbwheel_sensitivity: Option<i32>,
#[serde(default)]
invert_scroll: bool,
#[serde(default)]
scroll_resolution: Option<ScrollResolution>,
#[serde(default)]
host_switch_targets: Vec<String>,
#[serde(default)]
fn_lock: Option<bool>,
#[serde(default = "default_true")]
enabled: bool,
}
impl From<RawDeviceConfig> for DeviceConfig {
fn from(raw: RawDeviceConfig) -> Self {
let mut bindings = raw.bindings;
if !raw.gesture_bindings.is_empty() {
bindings
.entry(ButtonId::GestureButton)
.or_insert_with(|| Binding::Gesture(raw.gesture_bindings));
}
for (button, action) in raw.button_bindings {
if button == ButtonId::GestureButton {
continue;
}
bindings.entry(button).or_insert(Binding::Single(action));
}
DeviceConfig {
enabled: raw.enabled,
gesture_owner: raw.gesture_owner,
identity: raw.identity,
bindings,
per_app_bindings: raw.per_app_bindings,
dpi_presets: raw.dpi_presets,
dpi: raw.dpi,
lighting: raw.lighting,
light: raw.light,
smartshift: raw.smartshift,
camera_controls: raw.camera_controls,
camera_profiles: raw.camera_profiles,
camera_profile: raw.camera_profile,
thumbwheel_sensitivity: raw.thumbwheel_sensitivity,
invert_scroll: raw.invert_scroll,
scroll_resolution: raw.scroll_resolution,
host_switch_targets: raw.host_switch_targets,
fn_lock: raw.fn_lock,
}
}
}
#[cfg(test)]
mod tests {
use super::DeviceConfig;
#[test]
fn host_switch_targets_round_trip_as_physical_keys() -> Result<(), Box<dyn std::error::Error>> {
let config: DeviceConfig = toml::from_str(
r#"host_switch_targets = [
"receiver:keyboard:slot:1",
"receiver:mouse:slot:2",
]"#,
)?;
assert_eq!(
config.host_switch_targets,
["receiver:keyboard:slot:1", "receiver:mouse:slot:2"]
);
let serialized = toml::to_string(&config)?;
assert!(serialized.contains("host_switch_targets"));
Ok(())
}
}