407 lines
10 KiB
Rust
407 lines
10 KiB
Rust
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use crate::config_io;
|
|
use crate::config_io::get_conf_d_path;
|
|
use crate::config_io::CONFIG_ROOT_PATH;
|
|
use crate::gui::modular::ModularUiConfig;
|
|
use crate::overlays::toast::DisplayMethod;
|
|
use crate::overlays::toast::ToastTopic;
|
|
use crate::state::LeftRight;
|
|
use anyhow::bail;
|
|
use config::Config;
|
|
use config::File;
|
|
use idmap::IdMap;
|
|
use log::error;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
|
|
pub type AStrMap<V> = Vec<(Arc<str>, V)>;
|
|
|
|
pub trait AStrMapExt<V> {
|
|
fn arc_ins(&mut self, key: Arc<str>, value: V) -> bool;
|
|
fn arc_get(&self, key: &str) -> Option<&V>;
|
|
fn arc_rm(&mut self, key: &str) -> Option<V>;
|
|
}
|
|
|
|
impl<V> AStrMapExt<V> for AStrMap<V> {
|
|
fn arc_ins(&mut self, key: Arc<str>, value: V) -> bool {
|
|
if self.iter().any(|(k, _)| k.as_ref().eq(key.as_ref())) {
|
|
return false;
|
|
}
|
|
self.push((key, value));
|
|
true
|
|
}
|
|
|
|
fn arc_get(&self, key: &str) -> Option<&V> {
|
|
self.iter()
|
|
.find_map(|(k, v)| if k.as_ref().eq(key) { Some(v) } else { None })
|
|
}
|
|
|
|
fn arc_rm(&mut self, key: &str) -> Option<V> {
|
|
let index = self.iter().position(|(k, _)| k.as_ref().eq(key));
|
|
index.map(|i| self.remove(i).1)
|
|
}
|
|
}
|
|
|
|
pub type AStrSet = Vec<Arc<str>>;
|
|
|
|
pub trait AStrSetExt {
|
|
fn arc_ins(&mut self, value: Arc<str>) -> bool;
|
|
fn arc_get(&self, value: &str) -> bool;
|
|
fn arc_rm(&mut self, value: &str) -> bool;
|
|
}
|
|
|
|
impl AStrSetExt for AStrSet {
|
|
fn arc_ins(&mut self, value: Arc<str>) -> bool {
|
|
if self.iter().any(|v| v.as_ref().eq(value.as_ref())) {
|
|
return false;
|
|
}
|
|
self.push(value);
|
|
true
|
|
}
|
|
|
|
fn arc_get(&self, value: &str) -> bool {
|
|
self.iter().any(|v| v.as_ref().eq(value))
|
|
}
|
|
|
|
fn arc_rm(&mut self, value: &str) -> bool {
|
|
let index = self.iter().position(|v| v.as_ref().eq(value));
|
|
index
|
|
.map(|i| {
|
|
self.remove(i);
|
|
true
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
}
|
|
|
|
pub type PwTokenMap = AStrMap<String>;
|
|
|
|
pub fn def_watch_pos() -> [f32; 3] {
|
|
[-0.03, -0.01, 0.125]
|
|
}
|
|
|
|
pub fn def_watch_rot() -> [f32; 4] {
|
|
[-0.7071066, 0.0007963618, 0.7071066, 0.0]
|
|
}
|
|
|
|
pub fn def_left() -> LeftRight {
|
|
LeftRight::Left
|
|
}
|
|
|
|
pub fn def_pw_tokens() -> PwTokenMap {
|
|
AStrMap::new()
|
|
}
|
|
|
|
fn def_click_freeze_time_ms() -> u32 {
|
|
300
|
|
}
|
|
|
|
pub fn def_true() -> bool {
|
|
true
|
|
}
|
|
|
|
fn def_false() -> bool {
|
|
false
|
|
}
|
|
|
|
fn def_one() -> f32 {
|
|
1.0
|
|
}
|
|
|
|
pub fn def_half() -> f32 {
|
|
0.5
|
|
}
|
|
|
|
pub fn def_point7() -> f32 {
|
|
0.7
|
|
}
|
|
|
|
fn def_osc_port() -> u16 {
|
|
9000
|
|
}
|
|
|
|
fn def_screens() -> AStrSet {
|
|
AStrSet::new()
|
|
}
|
|
|
|
fn def_curve_values() -> AStrMap<f32> {
|
|
AStrMap::new()
|
|
}
|
|
|
|
fn def_auto() -> Arc<str> {
|
|
"auto".into()
|
|
}
|
|
|
|
fn def_toast_topics() -> IdMap<ToastTopic, DisplayMethod> {
|
|
IdMap::new()
|
|
}
|
|
|
|
#[derive(Deserialize, Serialize)]
|
|
pub struct GeneralConfig {
|
|
#[serde(default = "def_watch_pos")]
|
|
pub watch_pos: [f32; 3],
|
|
|
|
#[serde(default = "def_watch_rot")]
|
|
pub watch_rot: [f32; 4],
|
|
|
|
#[serde(default = "def_left")]
|
|
pub watch_hand: LeftRight,
|
|
|
|
#[serde(default = "def_click_freeze_time_ms")]
|
|
pub click_freeze_time_ms: u32,
|
|
|
|
#[serde(default = "def_true")]
|
|
pub notifications_enabled: bool,
|
|
|
|
#[serde(default = "def_true")]
|
|
pub notifications_sound_enabled: bool,
|
|
|
|
#[serde(default = "def_toast_topics")]
|
|
pub notification_topics: IdMap<ToastTopic, DisplayMethod>,
|
|
|
|
#[serde(default = "def_true")]
|
|
pub keyboard_sound_enabled: bool,
|
|
|
|
#[serde(default = "def_one")]
|
|
pub keyboard_scale: f32,
|
|
|
|
#[serde(default = "def_one")]
|
|
pub desktop_view_scale: f32,
|
|
|
|
#[serde(default = "def_half")]
|
|
pub watch_view_angle_min: f32,
|
|
|
|
#[serde(default = "def_point7")]
|
|
pub watch_view_angle_max: f32,
|
|
|
|
#[serde(default = "def_one")]
|
|
pub long_press_duration: f32,
|
|
|
|
#[serde(default = "def_pw_tokens")]
|
|
pub pw_tokens: PwTokenMap,
|
|
|
|
#[serde(default = "def_osc_port")]
|
|
pub osc_out_port: u16,
|
|
|
|
#[serde(default = "def_false")]
|
|
pub upright_screen_fix: bool,
|
|
|
|
#[serde(default = "def_false")]
|
|
pub double_cursor_fix: bool,
|
|
|
|
#[serde(default = "def_screens")]
|
|
pub show_screens: AStrSet,
|
|
|
|
#[serde(default = "def_curve_values")]
|
|
pub curve_values: AStrMap<f32>,
|
|
|
|
#[serde(default = "def_auto")]
|
|
pub capture_method: Arc<str>,
|
|
|
|
#[serde(default = "def_point7")]
|
|
pub xr_grab_sensitivity: f32,
|
|
|
|
#[serde(default = "def_point7")]
|
|
pub xr_click_sensitivity: f32,
|
|
|
|
#[serde(default = "def_point7")]
|
|
pub xr_alt_click_sensitivity: f32,
|
|
|
|
#[serde(default = "def_half")]
|
|
pub xr_grab_sensitivity_release: f32,
|
|
|
|
#[serde(default = "def_half")]
|
|
pub xr_click_sensitivity_release: f32,
|
|
|
|
#[serde(default = "def_half")]
|
|
pub xr_alt_click_sensitivity_release: f32,
|
|
|
|
#[serde(default = "def_true")]
|
|
pub allow_sliding: bool,
|
|
|
|
#[serde(default = "def_true")]
|
|
pub realign_on_showhide: bool,
|
|
}
|
|
|
|
impl GeneralConfig {
|
|
fn sanitize_range(name: &str, val: f32, from: f32, to: f32) {
|
|
if !val.is_normal() || val < from || val > to {
|
|
panic!(
|
|
"GeneralConfig: {} needs to be between {} and {}",
|
|
name, from, to
|
|
);
|
|
}
|
|
}
|
|
|
|
pub fn load_from_disk() -> GeneralConfig {
|
|
let config = load_general();
|
|
config.post_load();
|
|
config
|
|
}
|
|
|
|
fn post_load(&self) {
|
|
GeneralConfig::sanitize_range("keyboard_scale", self.keyboard_scale, 0.05, 5.0);
|
|
GeneralConfig::sanitize_range("desktop_view_scale", self.desktop_view_scale, 0.05, 5.0);
|
|
}
|
|
}
|
|
|
|
const FALLBACKS: [&str; 3] = [
|
|
include_str!("res/keyboard.yaml"),
|
|
include_str!("res/watch.yaml"),
|
|
include_str!("res/settings.yaml"),
|
|
];
|
|
|
|
const FILES: [&str; 3] = ["keyboard.yaml", "watch.yaml", "settings.yaml"];
|
|
|
|
#[derive(Clone, Copy)]
|
|
#[repr(usize)]
|
|
pub enum ConfigType {
|
|
Keyboard,
|
|
Watch,
|
|
Settings,
|
|
}
|
|
|
|
pub fn load_known_yaml<T>(config_type: ConfigType) -> T
|
|
where
|
|
T: for<'de> Deserialize<'de>,
|
|
{
|
|
let fallback = FALLBACKS[config_type as usize];
|
|
let file_name = FILES[config_type as usize];
|
|
let maybe_override = config_io::load(file_name);
|
|
|
|
for yaml in [maybe_override.as_deref(), Some(fallback)].iter().flatten() {
|
|
match serde_yaml::from_str::<T>(yaml) {
|
|
Ok(d) => return d,
|
|
Err(e) => {
|
|
error!("Failed to parse {}, falling back to defaults.", file_name);
|
|
error!("{}", e);
|
|
}
|
|
}
|
|
}
|
|
// can only get here if internal fallback is broken
|
|
panic!("No usable config found.");
|
|
}
|
|
|
|
pub fn load_custom_ui(name: &str) -> anyhow::Result<ModularUiConfig> {
|
|
let filename = format!("{}.yaml", name);
|
|
let Some(yaml_data) = config_io::load(&filename) else {
|
|
bail!("Could not read file at {}", &filename);
|
|
};
|
|
Ok(serde_yaml::from_str(&yaml_data)?)
|
|
}
|
|
|
|
pub fn load_general() -> GeneralConfig {
|
|
let mut settings_builder = Config::builder();
|
|
|
|
// Add files from conf.d directory
|
|
let path_conf_d = get_conf_d_path();
|
|
|
|
for mut base_conf in [CONFIG_ROOT_PATH.clone(), path_conf_d.clone()] {
|
|
base_conf.push("config.yaml");
|
|
if base_conf.exists() {
|
|
log::info!("Loading config file: {}", base_conf.to_string_lossy());
|
|
settings_builder = settings_builder.add_source(File::from(base_conf));
|
|
}
|
|
}
|
|
|
|
if let Ok(paths_unsorted) = std::fs::read_dir(path_conf_d) {
|
|
let mut paths: Vec<_> = paths_unsorted
|
|
.filter_map(|r| match r {
|
|
Ok(entry) => Some(entry),
|
|
Err(e) => {
|
|
error!("Failed to read conf.d directory: {}", e);
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
// Sort paths alphabetically
|
|
paths.sort_by_key(|dir| dir.path());
|
|
for path in paths {
|
|
log::info!("Loading config file: {}", path.path().to_string_lossy());
|
|
settings_builder = settings_builder.add_source(File::from(path.path()));
|
|
}
|
|
}
|
|
|
|
match settings_builder.build() {
|
|
Ok(settings) => {
|
|
match settings.try_deserialize::<GeneralConfig>() {
|
|
Ok(config) => {
|
|
return config;
|
|
}
|
|
Err(e) => {
|
|
panic!("Failed to deserialize settings: {}", e);
|
|
}
|
|
};
|
|
}
|
|
Err(e) => {
|
|
panic!("Failed to build settings: {}", e);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Config that is saved from the settings panel
|
|
|
|
#[derive(Serialize)]
|
|
pub struct AutoSettings {
|
|
pub watch_pos: [f32; 3],
|
|
pub watch_rot: [f32; 4],
|
|
pub watch_hand: LeftRight,
|
|
pub watch_view_angle_min: f32,
|
|
pub watch_view_angle_max: f32,
|
|
pub notifications_enabled: bool,
|
|
pub notifications_sound_enabled: bool,
|
|
pub realign_on_showhide: bool,
|
|
pub allow_sliding: bool,
|
|
}
|
|
|
|
fn get_settings_path() -> PathBuf {
|
|
let mut path = config_io::get_conf_d_path();
|
|
path.push("zz-saved-config.json5");
|
|
path
|
|
}
|
|
pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
|
|
let conf = AutoSettings {
|
|
watch_pos: config.watch_pos,
|
|
watch_rot: config.watch_rot,
|
|
watch_hand: config.watch_hand,
|
|
watch_view_angle_min: config.watch_view_angle_min,
|
|
watch_view_angle_max: config.watch_view_angle_max,
|
|
notifications_enabled: config.notifications_enabled,
|
|
notifications_sound_enabled: config.notifications_sound_enabled,
|
|
realign_on_showhide: config.realign_on_showhide,
|
|
allow_sliding: config.allow_sliding,
|
|
};
|
|
|
|
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
|
std::fs::write(get_settings_path(), json)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Config that is saved after manipulating overlays
|
|
|
|
#[derive(Serialize)]
|
|
pub struct AutoState {
|
|
pub show_screens: AStrSet,
|
|
pub curve_values: AStrMap<f32>,
|
|
}
|
|
|
|
fn get_state_path() -> PathBuf {
|
|
let mut path = config_io::get_conf_d_path();
|
|
path.push("zz-saved-state.json5");
|
|
path
|
|
}
|
|
pub fn save_state(config: &GeneralConfig) -> anyhow::Result<()> {
|
|
let conf = AutoState {
|
|
show_screens: config.show_screens.clone(),
|
|
curve_values: config.curve_values.clone(),
|
|
};
|
|
|
|
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
|
|
std::fs::write(get_state_path(), json)?;
|
|
|
|
Ok(())
|
|
}
|