add host-side daemon and config-backed parameter store
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
//! Provides functions to get the latest observed scroll speed.
|
||||
//! For host-side mode, the daemon writes a single numeric value to a stats file.
|
||||
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
thread::{self, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
static INPUT_SPEED_BITS: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub fn read_input_speed() -> f64 {
|
||||
f64::from_bits(INPUT_SPEED_BITS.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
pub fn setup_input_speed_reader() -> JoinHandle<anyhow::Result<()>> {
|
||||
thread::spawn(|| {
|
||||
let path = stats_path();
|
||||
|
||||
loop {
|
||||
if let Ok(content) = std::fs::read_to_string(&path)
|
||||
&& let Ok(parsed) = content.trim().parse::<f64>()
|
||||
{
|
||||
INPUT_SPEED_BITS.store(parsed.to_bits(), Ordering::Relaxed);
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(75));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn stats_path() -> PathBuf {
|
||||
if let Ok(path) = std::env::var("GSF_STATS_PATH") {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
|
||||
if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") {
|
||||
return PathBuf::from(runtime_dir).join("gsf-speed.txt");
|
||||
}
|
||||
|
||||
let home = std::env::var("HOME").context("HOME missing").ok();
|
||||
if let Some(home) = home {
|
||||
return PathBuf::from(home)
|
||||
.join(".local")
|
||||
.join("state")
|
||||
.join("gsf")
|
||||
.join("speed.txt");
|
||||
}
|
||||
|
||||
PathBuf::from("/tmp/gsf-speed.txt")
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
fixedptc::Fpt,
|
||||
params::{
|
||||
ALL_MODES, AccelMode, CommonParamArgs, LinearParamArgs, NaturalParamArgs, Param,
|
||||
SynchronousParamArgs, format_param_value, validate_param_value,
|
||||
},
|
||||
};
|
||||
|
||||
pub trait ParamStore: Debug {
|
||||
fn set(&mut self, param: Param, value: f64) -> anyhow::Result<()>;
|
||||
fn get(&self, param: Param) -> anyhow::Result<Fpt>;
|
||||
|
||||
fn set_current_accel_mode(&mut self, mode: AccelMode) -> anyhow::Result<()>;
|
||||
fn get_current_accel_mode(&self) -> anyhow::Result<AccelMode>;
|
||||
}
|
||||
|
||||
const CONFIG_ENV_VAR: &str = "GSF_CONFIG_PATH";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct StoredConfig {
|
||||
mode: u8,
|
||||
sens_mult: f64,
|
||||
yx_ratio: f64,
|
||||
input_dpi: f64,
|
||||
angle_rotation: f64,
|
||||
accel: f64,
|
||||
offset_linear: f64,
|
||||
output_cap: f64,
|
||||
decay_rate: f64,
|
||||
offset_natural: f64,
|
||||
limit: f64,
|
||||
gamma: f64,
|
||||
smooth: f64,
|
||||
motivity: f64,
|
||||
sync_speed: f64,
|
||||
}
|
||||
|
||||
impl Default for StoredConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: AccelMode::Linear as u8,
|
||||
sens_mult: 1.0,
|
||||
yx_ratio: 1.0,
|
||||
input_dpi: 1000.0,
|
||||
angle_rotation: 0.0,
|
||||
accel: 0.0,
|
||||
offset_linear: 0.0,
|
||||
output_cap: 0.0,
|
||||
decay_rate: 0.1,
|
||||
offset_natural: 0.0,
|
||||
limit: 1.5,
|
||||
gamma: 1.0,
|
||||
smooth: 0.5,
|
||||
motivity: 1.5,
|
||||
sync_speed: 5.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SysFsStore;
|
||||
|
||||
impl ParamStore for SysFsStore {
|
||||
fn set(&mut self, param: Param, value: f64) -> anyhow::Result<()> {
|
||||
validate_param_value(param, value)?;
|
||||
|
||||
let mut cfg = load_or_init_config()?;
|
||||
set_param_on_config(&mut cfg, param, value);
|
||||
save_config(&cfg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get(&self, param: Param) -> anyhow::Result<Fpt> {
|
||||
let cfg = load_or_init_config()?;
|
||||
Ok(get_param_from_config(&cfg, param).into())
|
||||
}
|
||||
|
||||
fn set_current_accel_mode(&mut self, mode: AccelMode) -> anyhow::Result<()> {
|
||||
let mut cfg = load_or_init_config()?;
|
||||
cfg.mode = mode as u8;
|
||||
save_config(&cfg)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_current_accel_mode(&self) -> anyhow::Result<AccelMode> {
|
||||
let cfg = load_or_init_config()?;
|
||||
let idx = (cfg.mode as usize) % ALL_MODES.len();
|
||||
Ok(ALL_MODES[idx])
|
||||
}
|
||||
}
|
||||
|
||||
impl SysFsStore {
|
||||
pub fn set_all_common(&mut self, args: CommonParamArgs) -> anyhow::Result<()> {
|
||||
let CommonParamArgs {
|
||||
sens_mult,
|
||||
yx_ratio,
|
||||
input_dpi,
|
||||
angle_rotation,
|
||||
} = args;
|
||||
|
||||
self.set(Param::SensMult, sens_mult)?;
|
||||
self.set(Param::YxRatio, yx_ratio)?;
|
||||
self.set(Param::InputDpi, input_dpi)?;
|
||||
self.set(Param::AngleRotation, angle_rotation)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_all_linear(&mut self, args: LinearParamArgs) -> anyhow::Result<()> {
|
||||
let LinearParamArgs {
|
||||
accel,
|
||||
offset_linear,
|
||||
output_cap,
|
||||
} = args;
|
||||
|
||||
self.set(Param::Accel, accel)?;
|
||||
self.set(Param::OffsetLinear, offset_linear)?;
|
||||
self.set(Param::OutputCap, output_cap)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_all_natural(&mut self, args: NaturalParamArgs) -> anyhow::Result<()> {
|
||||
let NaturalParamArgs {
|
||||
decay_rate,
|
||||
limit,
|
||||
offset_natural,
|
||||
} = args;
|
||||
|
||||
self.set(Param::DecayRate, decay_rate)?;
|
||||
self.set(Param::OffsetNatural, offset_natural)?;
|
||||
self.set(Param::Limit, limit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_all_synchronous(&mut self, args: SynchronousParamArgs) -> anyhow::Result<()> {
|
||||
let SynchronousParamArgs {
|
||||
gamma,
|
||||
smooth,
|
||||
motivity,
|
||||
sync_speed,
|
||||
} = args;
|
||||
|
||||
self.set(Param::Gamma, gamma)?;
|
||||
self.set(Param::Smooth, smooth)?;
|
||||
self.set(Param::Motivity, motivity)?;
|
||||
self.set(Param::SyncSpeed, sync_speed)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn config_path() -> anyhow::Result<PathBuf> {
|
||||
if let Ok(p) = std::env::var(CONFIG_ENV_VAR) {
|
||||
return Ok(PathBuf::from(p));
|
||||
}
|
||||
|
||||
let home = std::env::var("HOME").context("HOME is not set")?;
|
||||
Ok(PathBuf::from(home)
|
||||
.join(".config")
|
||||
.join("gsf")
|
||||
.join("config.json"))
|
||||
}
|
||||
|
||||
fn load_or_init_config() -> anyhow::Result<StoredConfig> {
|
||||
let path = config_path()?;
|
||||
|
||||
if !path.exists() {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.with_context(|| anyhow!("failed to create config directory {}", parent.display()))?;
|
||||
}
|
||||
let cfg = StoredConfig::default();
|
||||
save_config(&cfg)?;
|
||||
return Ok(cfg);
|
||||
}
|
||||
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.with_context(|| anyhow!("failed to read config file {}", path.display()))?;
|
||||
let cfg: StoredConfig =
|
||||
serde_json::from_str(&content).context("failed to parse gsf config JSON")?;
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
fn save_config(cfg: &StoredConfig) -> anyhow::Result<()> {
|
||||
let path = config_path()?;
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.with_context(|| anyhow!("failed to create config directory {}", parent.display()))?;
|
||||
}
|
||||
|
||||
let content = serde_json::to_string_pretty(cfg).context("failed to serialize config")?;
|
||||
std::fs::write(&path, content)
|
||||
.with_context(|| anyhow!("failed to write config {}", path.display()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_param_from_config(cfg: &StoredConfig, param: Param) -> f64 {
|
||||
match param {
|
||||
Param::SensMult => cfg.sens_mult,
|
||||
Param::YxRatio => cfg.yx_ratio,
|
||||
Param::InputDpi => cfg.input_dpi,
|
||||
Param::AngleRotation => cfg.angle_rotation,
|
||||
Param::Accel => cfg.accel,
|
||||
Param::OffsetLinear => cfg.offset_linear,
|
||||
Param::OutputCap => cfg.output_cap,
|
||||
Param::DecayRate => cfg.decay_rate,
|
||||
Param::OffsetNatural => cfg.offset_natural,
|
||||
Param::Limit => cfg.limit,
|
||||
Param::Gamma => cfg.gamma,
|
||||
Param::Smooth => cfg.smooth,
|
||||
Param::Motivity => cfg.motivity,
|
||||
Param::SyncSpeed => cfg.sync_speed,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_param_on_config(cfg: &mut StoredConfig, param: Param, value: f64) {
|
||||
match param {
|
||||
Param::SensMult => cfg.sens_mult = value,
|
||||
Param::YxRatio => cfg.yx_ratio = value,
|
||||
Param::InputDpi => cfg.input_dpi = value,
|
||||
Param::AngleRotation => cfg.angle_rotation = value,
|
||||
Param::Accel => cfg.accel = value,
|
||||
Param::OffsetLinear => cfg.offset_linear = value,
|
||||
Param::OutputCap => cfg.output_cap = value,
|
||||
Param::DecayRate => cfg.decay_rate = value,
|
||||
Param::OffsetNatural => cfg.offset_natural = value,
|
||||
Param::Limit => cfg.limit = value,
|
||||
Param::Gamma => cfg.gamma = value,
|
||||
Param::Smooth => cfg.smooth = value,
|
||||
Param::Motivity => cfg.motivity = value,
|
||||
Param::SyncSpeed => cfg.sync_speed = value,
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Fpt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&format_param_value(f64::from(*self)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user