This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
@@ -0,0 +1,36 @@
|
||||
use std::{
|
||||
env::{self, consts::ARCH},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let out = PathBuf::from(
|
||||
env::var("OUT_DIR").expect("Expected OUT_DIR to be defined in the environment"),
|
||||
);
|
||||
|
||||
let fixedpt_bits = match ARCH {
|
||||
"x86" => "32",
|
||||
#[cfg(feature = "long_bit_32")]
|
||||
"x86_64" => "32",
|
||||
#[cfg(not(feature = "long_bit_32"))]
|
||||
"x86_64" => "64",
|
||||
a => panic!("unsupported/untested architecture: {a}"),
|
||||
};
|
||||
let mut compiler = cc::Build::new();
|
||||
compiler
|
||||
.file("src/libmaccel.c")
|
||||
.define("FIXEDPT_BITS", fixedpt_bits);
|
||||
|
||||
if cfg!(feature = "dbg") {
|
||||
compiler.define("DEBUG", "1");
|
||||
compiler.debug(true);
|
||||
}
|
||||
|
||||
compiler.compile("maccel");
|
||||
|
||||
println!("cargo:rust-link-search=static={}", out.display());
|
||||
|
||||
const DRIVER_DIR: &str = "../../driver";
|
||||
println!("cargo:rerun-if-changed={DRIVER_DIR}");
|
||||
println!("cargo:rerun-if-changed=src/libmaccel.c");
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod context;
|
||||
pub mod inputspeed;
|
||||
mod libmaccel;
|
||||
mod params;
|
||||
pub mod persist;
|
||||
mod sens_fns;
|
||||
|
||||
pub use context::*;
|
||||
pub use libmaccel::fixedptc;
|
||||
pub use params::*;
|
||||
pub use sens_fns::*;
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "../../../driver/accel_rs.h"
|
||||
#include "../../../driver/fixedptc.h"
|
||||
|
||||
char *fpt_to_str(fpt num);
|
||||
fpt str_to_fpt(char *string);
|
||||
double fpt_to_float(fpt value);
|
||||
fpt fpt_from_float(double value);
|
||||
|
||||
extern char *fpt_to_str(fpt num) { return fptoa(num); }
|
||||
extern fpt str_to_fpt(char *string) { return atofp(string); }
|
||||
|
||||
extern double fpt_to_float(fpt value) { return fpt_todouble(value); }
|
||||
|
||||
extern fpt fpt_from_float(double value) { return fpt_rconst(value); }
|
||||
@@ -0,0 +1,91 @@
|
||||
pub mod fixedptc {
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use super::c_libmaccel::{self, str_to_fpt};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
#[repr(transparent)]
|
||||
pub struct Fpt(pub i64);
|
||||
|
||||
impl From<Fpt> for f64 {
|
||||
fn from(value: Fpt) -> Self {
|
||||
unsafe { c_libmaccel::fpt_to_float(value) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Fpt {
|
||||
fn from(value: f64) -> Self {
|
||||
unsafe { c_libmaccel::fpt_from_float(value) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn fpt_and_float_conversion_to_and_fro() {
|
||||
macro_rules! assert_for {
|
||||
($value:literal) => {{
|
||||
let fp = Fpt::from($value);
|
||||
assert_eq!(f64::from(fp), $value);
|
||||
}};
|
||||
}
|
||||
|
||||
assert_for!(1.5);
|
||||
assert_for!(4.5);
|
||||
assert_for!(1.0);
|
||||
assert_for!(0.0);
|
||||
assert_for!(0.125);
|
||||
assert_for!(0.5);
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Fpt> for &'a str {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: &'a Fpt) -> Result<Self, Self::Error> {
|
||||
unsafe {
|
||||
let s = CStr::from_ptr(c_libmaccel::fpt_to_str(*value));
|
||||
let s = core::str::from_utf8(s.to_bytes())?;
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Fpt {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let cstr = CString::new(s).context("Failed to convert to a C string")?;
|
||||
let f = unsafe { str_to_fpt(cstr.as_ptr()) };
|
||||
Ok(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Vector {
|
||||
pub x: i64,
|
||||
pub y: i64,
|
||||
}
|
||||
|
||||
mod c_libmaccel {
|
||||
use super::{Vector, fixedptc};
|
||||
use crate::params::AccelParams;
|
||||
use std::ffi::c_char;
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn sensitivity_rs(speed_in: fixedptc::Fpt, args: AccelParams) -> Vector;
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn fpt_to_str(num: fixedptc::Fpt) -> *const c_char;
|
||||
pub fn str_to_fpt(string: *const c_char) -> fixedptc::Fpt;
|
||||
pub fn fpt_from_float(value: f64) -> fixedptc::Fpt;
|
||||
pub fn fpt_to_float(value: fixedptc::Fpt) -> f64;
|
||||
}
|
||||
}
|
||||
|
||||
pub use c_libmaccel::sensitivity_rs;
|
||||
@@ -0,0 +1,358 @@
|
||||
use crate::libmaccel::fixedptc::Fpt;
|
||||
use paste::paste;
|
||||
|
||||
/// Declare an enum for every parameter.
|
||||
macro_rules! declare_common_params {
|
||||
($($param:tt,)+) => {
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Param {
|
||||
$($param),+
|
||||
}
|
||||
|
||||
paste!(
|
||||
#[derive(Debug)]
|
||||
pub struct AllParamArgs {
|
||||
$( pub [< $param:snake:lower >]: Fpt ),+
|
||||
}
|
||||
);
|
||||
|
||||
pub const ALL_PARAMS: &[Param] = &[ $(Param::$param),+ ];
|
||||
};
|
||||
}
|
||||
|
||||
// Helper macro to create FFI-safe curve parameter structs
|
||||
macro_rules! make_curve_params_struct {
|
||||
// Case: has parameters
|
||||
($mode:tt, $($param:tt),+) => {
|
||||
paste! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct [< $mode CurveParams >] {
|
||||
$( pub [< $param:snake:lower >]: Fpt ),+
|
||||
}
|
||||
}
|
||||
};
|
||||
// Case: no parameters, add a ZST field for FFI safety
|
||||
($mode:tt, ) => {
|
||||
paste! {
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct [< $mode CurveParams >] {
|
||||
pub _ffi_guard: [u8; 0],
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! declare_params {
|
||||
( Common { $($common_param:tt),+$(,)? } , $( $mode:tt { $($param:tt),*$(,)? }, )+) => {
|
||||
declare_common_params! {
|
||||
$( $common_param, )+
|
||||
$( $( $param, )* )+
|
||||
}
|
||||
|
||||
/// Array of all the common parameters for convenience.
|
||||
pub const ALL_COMMON_PARAMS: &[Param] = &[ $( Param::$common_param),+ ];
|
||||
|
||||
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[derive(Debug, Default, PartialEq, Clone, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum AccelMode {
|
||||
#[default]
|
||||
$( $mode, )+
|
||||
}
|
||||
|
||||
pub const ALL_MODES: &[AccelMode] = &[ $( AccelMode::$mode, )+ ];
|
||||
|
||||
paste! {
|
||||
/// Define the complete shape (and memory layout) of the argument
|
||||
/// of the sensitivity function as it is expected to be in `C`
|
||||
#[repr(C)]
|
||||
pub struct AccelParams {
|
||||
$( pub [< $common_param:snake:lower >] : Fpt, )+
|
||||
pub by_mode: AccelParamsByMode,
|
||||
}
|
||||
|
||||
/// Represents the tagged union of curve-specific parameters.
|
||||
#[repr(C, u8)]
|
||||
pub enum AccelParamsByMode {
|
||||
$(
|
||||
$mode([< $mode CurveParams >] ),
|
||||
)+
|
||||
}
|
||||
|
||||
/// Represents the common parameters and their float values.
|
||||
/// Use it to bulk set the common parameters.
|
||||
#[cfg_attr(feature = "clap", derive(clap::Args))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct CommonParamArgs {
|
||||
$( pub [< $common_param:snake:lower >]: f64 ),+
|
||||
}
|
||||
}
|
||||
|
||||
paste! {
|
||||
$(
|
||||
// Use the helper macro to define the struct
|
||||
make_curve_params_struct!($mode, $($param),*);
|
||||
|
||||
#[doc = "Array of all parameters for the `" $mode "` mode for convenience." ]
|
||||
pub const [< ALL_ $mode:upper _PARAMS >]: &[Param] = &[ $( Param::$param),* ];
|
||||
|
||||
#[doc = "Represents the parameters for `" $mode "` curve and their float values"]
|
||||
/// Use it to bulk set the curve's parameters.
|
||||
#[cfg_attr(feature = "clap", derive(clap::Args))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct [< $mode ParamArgs >] {
|
||||
$( pub [< $param:snake:lower >]: f64 ),*
|
||||
}
|
||||
)+
|
||||
|
||||
/// Subcommands for the CLI
|
||||
#[cfg(feature = "clap")]
|
||||
pub mod subcommads {
|
||||
#[derive(clap::Subcommand)]
|
||||
pub enum SetParamByModesSubcommands {
|
||||
/// Set all the common parameters
|
||||
Common(super::CommonParamArgs),
|
||||
$(
|
||||
#[doc = "Set all the parameters for the " $mode " curve" ]
|
||||
$mode(super::[< $mode ParamArgs >]),
|
||||
)+
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
pub enum CliSubcommandSetParams {
|
||||
/// Set the value for a single parameter
|
||||
Param { name: crate::params::Param, value: f64 },
|
||||
/// Set the acceleration mode (curve)
|
||||
Mode { mode: crate::params::AccelMode },
|
||||
/// Set the values for all parameters for a curve in order
|
||||
All {
|
||||
#[clap(subcommand)]
|
||||
command: SetParamByModesSubcommands
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
pub enum GetParamsByModesSubcommands {
|
||||
/// Get all the common parameters
|
||||
Common,
|
||||
$(
|
||||
#[doc = "Get all the parameters for the " $mode " curve" ]
|
||||
$mode
|
||||
),+
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
pub enum CliSubcommandGetParams {
|
||||
/// Get the value for a single parameter
|
||||
Param { name: crate::params::Param },
|
||||
/// Get the current acceleration mode (curve)
|
||||
Mode,
|
||||
/// Get the values for all parameters for a curve in order
|
||||
All {
|
||||
/// Print the values in one line, separated by a space
|
||||
#[arg(long)]
|
||||
oneline: bool,
|
||||
#[arg(short, long)]
|
||||
/// Print only the values
|
||||
quiet: bool,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: GetParamsByModesSubcommands
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
declare_params!(
|
||||
Common {
|
||||
SensMult,
|
||||
YxRatio,
|
||||
InputDpi,
|
||||
AngleRotation
|
||||
},
|
||||
Linear {
|
||||
Accel,
|
||||
OffsetLinear,
|
||||
OutputCap,
|
||||
},
|
||||
Natural {
|
||||
DecayRate,
|
||||
OffsetNatural,
|
||||
Limit,
|
||||
},
|
||||
Synchronous {
|
||||
Gamma,
|
||||
Smooth,
|
||||
Motivity,
|
||||
SyncSpeed,
|
||||
},
|
||||
NoAccel {},
|
||||
);
|
||||
|
||||
impl AccelMode {
|
||||
pub fn as_title(&self) -> &'static str {
|
||||
match self {
|
||||
AccelMode::Linear => "Linear Acceleration",
|
||||
AccelMode::Natural => "Natural (w/ Gain)",
|
||||
AccelMode::Synchronous => "Synchronous",
|
||||
AccelMode::NoAccel => "No Acceleration",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AccelMode {
|
||||
pub const PARAM_NAME: &'static str = "MODE";
|
||||
|
||||
pub fn ordinal(&self) -> i64 {
|
||||
(*self as i8).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Param {
|
||||
/// The canonical internal name of the parameter.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Param::SensMult => "SENS_MULT",
|
||||
Param::YxRatio => "YX_RATIO",
|
||||
Param::InputDpi => "INPUT_DPI",
|
||||
Param::Accel => "ACCEL",
|
||||
Param::OffsetLinear => "OFFSET",
|
||||
Param::OffsetNatural => "OFFSET",
|
||||
Param::OutputCap => "OUTPUT_CAP",
|
||||
Param::DecayRate => "DECAY_RATE",
|
||||
Param::Limit => "LIMIT",
|
||||
Param::Gamma => "GAMMA",
|
||||
Param::Smooth => "SMOOTH",
|
||||
Param::Motivity => "MOTIVITY",
|
||||
Param::SyncSpeed => "SYNC_SPEED",
|
||||
Param::AngleRotation => "ANGLE_ROTATION",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
Param::SensMult => "Sens-Multiplier",
|
||||
Param::Accel => "Accel",
|
||||
Param::InputDpi => "Input DPI",
|
||||
Param::OffsetLinear => "Offset",
|
||||
Param::OffsetNatural => "Offset",
|
||||
Param::OutputCap => "Output-Cap",
|
||||
Param::YxRatio => "Y/x Ratio",
|
||||
Param::DecayRate => "Decay-Rate",
|
||||
Param::Limit => "Limit",
|
||||
Param::Gamma => "Gamma",
|
||||
Param::Smooth => "Smooth",
|
||||
Param::Motivity => "Motivity",
|
||||
Param::SyncSpeed => "Sync Speed",
|
||||
Param::AngleRotation => "Angle Rotation",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Param::SensMult => "Base sensitivity multiplier. Adjusts overall mouse speed.",
|
||||
Param::YxRatio => {
|
||||
"Y to X axis sensitivity ratio. Values > 1 increase vertical sensitivity."
|
||||
}
|
||||
Param::InputDpi => {
|
||||
"Mouse DPI. Used to normalize to 1000 DPI equivalent for consistent acceleration."
|
||||
}
|
||||
Param::AngleRotation => "Rotation angle in degrees for sensitivity direction.",
|
||||
Param::Accel => "Acceleration strength. Higher values = faster cursor at high speeds.",
|
||||
Param::OffsetLinear => "Speed threshold (counts/ms) before acceleration begins.",
|
||||
Param::OutputCap => "Maximum sensitivity multiplier cap. Prevents excessive speed.",
|
||||
Param::DecayRate => "How quickly acceleration decays. Higher = faster decay.",
|
||||
Param::OffsetNatural => "Speed threshold (counts/ms) for natural curve activation.",
|
||||
Param::Limit => "Maximum gain limit for natural acceleration.",
|
||||
Param::Gamma => "Exponent controlling curve shape. Higher = more aggressive ramp-up.",
|
||||
Param::Smooth => "Smoothing factor (0-1). Higher = more gradual transitions.",
|
||||
Param::Motivity => "Degree of acceleration effect. Must be > 1.",
|
||||
Param::SyncSpeed => "Synchronization speed. Controls how fast sync responds.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn format_param_value(value: f64) -> String {
|
||||
let mut number = format!("{value:.5}");
|
||||
|
||||
for idx in (1..number.len()).rev() {
|
||||
let this_char = &number[idx..idx + 1];
|
||||
if this_char != "0" {
|
||||
if this_char == "." {
|
||||
number.remove(idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
number.remove(idx);
|
||||
}
|
||||
|
||||
number
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn format_param_value_works() {
|
||||
assert_eq!(format_param_value(1.5), "1.5");
|
||||
assert_eq!(format_param_value(1.50), "1.5");
|
||||
assert_eq!(format_param_value(100.0), "100");
|
||||
assert_eq!(format_param_value(0.0600), "0.06");
|
||||
assert_eq!(format_param_value(0.055000), "0.055");
|
||||
}
|
||||
|
||||
pub(crate) fn validate_param_value(param_tag: Param, value: f64) -> anyhow::Result<()> {
|
||||
match param_tag {
|
||||
Param::SensMult => {}
|
||||
Param::YxRatio => {}
|
||||
Param::InputDpi => {
|
||||
if value <= 0.0 {
|
||||
anyhow::bail!("Input DPI must be positive");
|
||||
}
|
||||
}
|
||||
Param::AngleRotation => {}
|
||||
Param::Accel => {}
|
||||
Param::OutputCap => {}
|
||||
Param::OffsetLinear | Param::OffsetNatural => {
|
||||
if value < 0.0 {
|
||||
anyhow::bail!("offset cannot be less than 0");
|
||||
}
|
||||
}
|
||||
Param::DecayRate => {
|
||||
if value <= 0.0 {
|
||||
anyhow::bail!("decay rate must be positive");
|
||||
}
|
||||
}
|
||||
Param::Limit => {
|
||||
if value < 1.0 {
|
||||
anyhow::bail!("limit cannot be less than 1");
|
||||
}
|
||||
}
|
||||
Param::Gamma => {
|
||||
if value <= 0.0 {
|
||||
anyhow::bail!("Gamma must be positive");
|
||||
}
|
||||
}
|
||||
Param::Smooth => {
|
||||
if !(0.0..=1.0).contains(&value) {
|
||||
anyhow::bail!("Smooth must be between 0 and 1");
|
||||
}
|
||||
}
|
||||
Param::Motivity => {
|
||||
if value <= 1.0 {
|
||||
anyhow::bail!("Motivity must be greater than 1");
|
||||
}
|
||||
}
|
||||
Param::SyncSpeed => {
|
||||
if value <= 0.0 {
|
||||
anyhow::bail!("'Synchronous speed' must be positive");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use crate::{
|
||||
AccelParams, AccelParamsByMode, LinearCurveParams, NaturalCurveParams, SynchronousCurveParams,
|
||||
libmaccel::{self, fixedptc::Fpt},
|
||||
params::AllParamArgs,
|
||||
};
|
||||
|
||||
use crate::AccelMode;
|
||||
|
||||
impl AllParamArgs {
|
||||
fn convert_to_accel_args(&self, mode: AccelMode) -> AccelParams {
|
||||
let params_by_mode = match mode {
|
||||
AccelMode::Linear => AccelParamsByMode::Linear(LinearCurveParams {
|
||||
accel: self.accel,
|
||||
offset_linear: self.offset_linear,
|
||||
output_cap: self.output_cap,
|
||||
}),
|
||||
AccelMode::Natural => AccelParamsByMode::Natural(NaturalCurveParams {
|
||||
decay_rate: self.decay_rate,
|
||||
offset_natural: self.offset_natural,
|
||||
limit: self.limit,
|
||||
}),
|
||||
AccelMode::Synchronous => AccelParamsByMode::Synchronous(SynchronousCurveParams {
|
||||
gamma: self.gamma,
|
||||
smooth: self.smooth,
|
||||
motivity: self.motivity,
|
||||
sync_speed: self.sync_speed,
|
||||
}),
|
||||
AccelMode::NoAccel => {
|
||||
AccelParamsByMode::NoAccel(crate::params::NoAccelCurveParams { _ffi_guard: [] })
|
||||
}
|
||||
};
|
||||
|
||||
AccelParams {
|
||||
sens_mult: self.sens_mult,
|
||||
yx_ratio: self.yx_ratio,
|
||||
input_dpi: self.input_dpi,
|
||||
angle_rotation: self.angle_rotation,
|
||||
by_mode: params_by_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type SensXY = (f64, f64);
|
||||
|
||||
/// Ratio of Output speed to Input speed
|
||||
pub fn sensitivity(s_in: f64, mode: AccelMode, params: &AllParamArgs) -> SensXY {
|
||||
let sens =
|
||||
unsafe { libmaccel::sensitivity_rs(s_in.into(), params.convert_to_accel_args(mode)) };
|
||||
let ratio_x: f64 = Fpt(sens.x).into();
|
||||
let ratio_y: f64 = Fpt(sens.y).into();
|
||||
|
||||
(ratio_x, ratio_y)
|
||||
}
|
||||
Reference in New Issue
Block a user