summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common/accel-classic.hpp133
-rw-r--r--common/accel-invoke.hpp52
-rw-r--r--common/accel-jump.hpp18
-rw-r--r--common/accel-lookup.hpp139
-rw-r--r--common/accel-motivity.hpp79
-rw-r--r--common/accel-natural.hpp16
-rw-r--r--common/accel-noaccel.hpp2
-rw-r--r--common/accel-power.hpp66
-rw-r--r--common/accel-union.hpp123
-rw-r--r--common/common.vcxitems1
-rw-r--r--common/rawaccel-base.hpp84
-rw-r--r--common/rawaccel-validate.hpp150
-rw-r--r--common/rawaccel.hpp221
-rw-r--r--common/utility.hpp6
-rw-r--r--driver/driver.cpp299
-rw-r--r--driver/driver.h22
-rw-r--r--wrapper/input.cpp119
-rw-r--r--wrapper/input.h110
-rw-r--r--wrapper/wrapper.cpp874
-rw-r--r--wrapper/wrapper.vcxproj7
-rw-r--r--writer/Program.cs101
21 files changed, 1467 insertions, 1155 deletions
diff --git a/common/accel-classic.hpp b/common/accel-classic.hpp
index 4385897..ce343e7 100644
--- a/common/accel-classic.hpp
+++ b/common/accel-classic.hpp
@@ -10,77 +10,135 @@ namespace rawaccel {
/// <summary> Struct to hold "classic" (linear raised to power) acceleration implementation. </summary>
struct classic_base {
- double offset;
- double power;
- double accel_raised;
-
- classic_base(const accel_args& args) :
- offset(args.offset),
- power(args.power),
- accel_raised(pow(args.accel_classic, power - 1)) {}
+ double base_fn(double x, double accel_raised, const accel_args& args) const
+ {
+ return accel_raised * pow(x - args.offset, args.exponent_classic) / x;
+ }
- double base_fn(double x) const
+ static double base_accel(double x, double y, double power, double offset)
{
- return accel_raised * pow(x - offset, power) / x;
+ return pow(x * y * pow(x - offset, -power), 1 / (power + 1));
}
};
- struct classic_legacy : classic_base {
- double sens_cap = DBL_MAX;
+ template <bool Gain> struct classic;
+
+ template<>
+ struct classic<LEGACY> : classic_base {
+ double accel_raised;
+ double cap = DBL_MAX;
double sign = 1;
- classic_legacy(const accel_args& args) :
- classic_base(args)
+ classic(const accel_args& args)
{
- if (args.cap > 0) {
- sens_cap = args.cap - 1;
+ switch (args.cap_mode) {
+ case classic_cap_mode::io:
+ cap = args.cap.y - 1;
- if (sens_cap < 0) {
- sens_cap = -sens_cap;
+ if (cap < 0) {
+ cap = -cap;
sign = -sign;
}
+
+ {
+ double a = base_accel(args.cap.x, cap, args.exponent_classic, args.offset);
+ accel_raised = pow(a, args.exponent_classic - 1);
+ }
+ break;
+ case classic_cap_mode::in:
+ accel_raised = pow(args.acceleration, args.exponent_classic - 1);
+ if (args.cap.x > 0) {
+ cap = base_fn(args.cap.x, accel_raised, args);
+ }
+ break;
+ case classic_cap_mode::out:
+ default:
+ accel_raised = pow(args.acceleration, args.exponent_classic - 1);
+
+ if (args.cap.y > 0) {
+ cap = args.cap.y - 1;
+
+ if (cap < 0) {
+ cap = -cap;
+ sign = -sign;
+ }
+ }
+
+ break;
}
}
- double operator()(double x) const
+ double operator()(double x, const accel_args& args) const
{
- if (x <= offset) return 1;
- return sign * minsd(base_fn(x), sens_cap) + 1;
- }
+ if (x <= args.offset) return 1;
+ return sign * minsd(base_fn(x, accel_raised, args), cap) + 1;
+ }
+
};
- struct classic : classic_base {
- vec2d gain_cap = { DBL_MAX, DBL_MAX };
+ template<>
+ struct classic<GAIN> : classic_base {
+ double accel_raised;
+ vec2d cap = { DBL_MAX, DBL_MAX };
double constant = 0;
double sign = 1;
- classic(const accel_args& args) :
- classic_base(args)
+ classic(const accel_args& args)
{
- if (args.cap > 0) {
- gain_cap.y = args.cap - 1;
+ switch (args.cap_mode) {
+ case classic_cap_mode::io:
+ cap.x = args.cap.x;
+ cap.y = args.cap.y - 1;
- if (gain_cap.y < 0) {
- gain_cap.y = -gain_cap.y;
+ if (cap.y < 0) {
+ cap.y = -cap.y;
sign = -sign;
}
- gain_cap.x = gain_inverse(gain_cap.y, args.accel_classic, power, offset);
- constant = (base_fn(gain_cap.x) - gain_cap.y) * gain_cap.x;
+ {
+ double a = gain_accel(cap.x, cap.y, args.exponent_classic, args.offset);
+ accel_raised = pow(a, args.exponent_classic - 1);
+ }
+ constant = (base_fn(cap.x, accel_raised, args) - cap.y) * cap.x;
+ break;
+ case classic_cap_mode::in:
+ if (args.cap.x > 0) {
+ cap.x = args.cap.x;
+ cap.y = gain(cap.x, args.acceleration, args.exponent_classic, args.offset);
+ constant = (base_fn(cap.x, accel_raised, args) - cap.y) * cap.x;
+ }
+ accel_raised = pow(args.acceleration, args.exponent_classic - 1);
+ break;
+ case classic_cap_mode::out:
+ default:
+ accel_raised = pow(args.acceleration, args.exponent_classic - 1);
+
+ if (args.cap.y > 0) {
+ cap.y = args.cap.y - 1;
+
+ if (cap.y < 0) {
+ cap.y = -cap.y;
+ sign = -sign;
+ }
+
+ cap.x = gain_inverse(cap.y, args.acceleration, args.exponent_classic, args.offset);
+ constant = (base_fn(cap.x, accel_raised, args) - cap.y) * cap.x;
+ }
+ break;
}
}
- double operator()(double x) const
+ double operator()(double x, const accel_args& args) const
{
double output;
- if (x <= offset) return 1;
+ if (x <= args.offset) return 1;
- if (x < gain_cap.x) {
- output = base_fn(x);
+ if (x < cap.x) {
+ output = base_fn(x, accel_raised, args);
}
else {
- output = constant / x + gain_cap.y;
+ output = constant / x + cap.y;
}
return sign * output + 1;
@@ -100,6 +158,7 @@ namespace rawaccel {
{
return -pow(y / power, 1 / (power - 1)) / (offset - x);
}
+
};
}
diff --git a/common/accel-invoke.hpp b/common/accel-invoke.hpp
deleted file mode 100644
index f2a95dc..0000000
--- a/common/accel-invoke.hpp
+++ /dev/null
@@ -1,52 +0,0 @@
-#pragma once
-
-#include "accel-union.hpp"
-
-namespace rawaccel {
-
- class accel_invoker {
- using callback_t = double (*)(const accel_union&, double, double);
-
- callback_t cb = &invoke_impl<accel_noaccel>;
-
- template <typename T>
- static double invoke_impl(const accel_union& u, double x, double w)
- {
- return apply_weighted(reinterpret_cast<const T&>(u), x, w);
- }
-
- public:
-
- accel_invoker(const accel_args& args)
- {
- cb = visit_accel([](auto&& arg) {
- using T = remove_ref_t<decltype(arg)>;
-
- if constexpr (is_same_v<T, motivity>) {
- static_assert(sizeof motivity == sizeof binlog_lut);
- return &invoke_impl<binlog_lut>;
- }
- else {
- return &invoke_impl<T>;
- }
-
- }, make_mode(args), accel_union{});
- }
-
- accel_invoker() = default;
-
- double invoke(const accel_union& u, double x, double weight = 1) const
- {
- return (*cb)(u, x, weight);
- }
- };
-
- inline vec2<accel_invoker> invokers(const settings& args)
- {
- return {
- accel_invoker(args.argsv.x),
- accel_invoker(args.argsv.y)
- };
- }
-
-}
diff --git a/common/accel-jump.hpp b/common/accel-jump.hpp
index 95fa461..c036810 100644
--- a/common/accel-jump.hpp
+++ b/common/accel-jump.hpp
@@ -2,6 +2,8 @@
#include "rawaccel-base.hpp"
+#include <math.h>
+
namespace rawaccel {
struct jump_base {
@@ -12,7 +14,7 @@ namespace rawaccel {
// requirements: args.smooth in range [0, 1]
jump_base(const accel_args& args) :
- step({ args.offset, args.cap - 1 })
+ step({ args.cap.x, args.cap.y - 1 })
{
double rate_inverse = args.smooth * step.x;
@@ -43,12 +45,16 @@ namespace rawaccel {
{
return step.y * (x + log(1 + decay(x)) / smooth_rate);
}
+
};
- struct jump_legacy : jump_base {
+ template <bool Gain> struct jump;
+
+ template<>
+ struct jump<LEGACY> : jump_base {
using jump_base::jump_base;
- double operator()(double x) const
+ double operator()(double x, const accel_args&) const
{
if (is_smooth()) return smooth(x) + 1;
else if (x < step.x) return 1;
@@ -56,14 +62,15 @@ namespace rawaccel {
}
};
- struct jump : jump_base {
+ template<>
+ struct jump<GAIN> : jump_base {
double C;
jump(const accel_args& args) :
jump_base(args),
C(-smooth_antideriv(0)) {}
- double operator()(double x) const
+ double operator()(double x, const accel_args&) const
{
if (x <= 0) return 1;
@@ -72,6 +79,7 @@ namespace rawaccel {
if (x < step.x) return 1;
else return 1 + step.y * (x - step.x) / x;
}
+
};
}
diff --git a/common/accel-lookup.hpp b/common/accel-lookup.hpp
index 99f39e9..b7e8b68 100644
--- a/common/accel-lookup.hpp
+++ b/common/accel-lookup.hpp
@@ -7,27 +7,6 @@
namespace rawaccel {
- struct linear_range {
- double start;
- double stop;
- int num;
-
- template <typename Func>
- void for_each(Func fn) const
- {
- double interval = (stop - start) / (num - 1);
- for (int i = 0; i < num; i++) {
- fn(i * interval + start);
- }
- }
-
- int size() const
- {
- return num;
- }
- };
-
-
// represents the range [2^start, 2^stop], with num - 1
// elements linearly spaced between each exponential step
struct fp_rep_range {
@@ -55,103 +34,7 @@ namespace rawaccel {
}
};
- template <typename Lookup>
- struct lut_base {
- enum { capacity = SPACED_LUT_CAPACITY };
- using value_t = float;
-
- template <typename Func>
- void fill(Func fn)
- {
- auto* self = static_cast<Lookup*>(this);
-
- self->range.for_each([&, fn, i = 0](double x) mutable {
- self->data[i++] = static_cast<value_t>(fn(x));
- });
- }
-
- };
-
- struct linear_lut : lut_base<linear_lut> {
- linear_range range;
- bool transfer = false;
- value_t data[capacity] = {};
-
- double operator()(double x) const
- {
- if (x > range.start) {
- double range_dist = range.stop - range.start;
- double idx_f = (x - range.start) * (range.num - 1) / range_dist;
-
- unsigned idx = min(static_cast<int>(idx_f), range.size() - 2);
-
- if (idx < capacity - 1) {
- double y = lerp(data[idx], data[idx + 1], idx_f - idx);
- if (transfer) y /= x;
- return y;
- }
- }
-
- double y = data[0];
- if (transfer) y /= range.start;
- return y;
- }
-
- linear_lut(const spaced_lut_args& args) :
- range({
- args.start,
- args.stop,
- args.num_elements
- }),
- transfer(args.transfer) {}
-
- linear_lut(const accel_args& args) :
- linear_lut(args.spaced_args) {}
- };
-
- struct binlog_lut : lut_base<binlog_lut> {
- fp_rep_range range;
- double x_start;
- bool transfer = false;
- value_t data[capacity] = {};
-
- double operator()(double x) const
- {
- int e = min(ilogb(x), range.stop - 1);
-
- if (e >= range.start) {
- int idx_int_log_part = e - range.start;
- double idx_frac_lin_part = scalbn(x, -e) - 1;
- double idx_f = range.num * (idx_int_log_part + idx_frac_lin_part);
-
- unsigned idx = min(static_cast<int>(idx_f), range.size() - 2);
-
- if (idx < capacity - 1) {
- double y = lerp(data[idx], data[idx + 1], idx_f - idx);
- if (transfer) y /= x;
- return y;
- }
- }
-
- double y = data[0];
- if (transfer) y /= x_start;
- return y;
- }
-
- binlog_lut(const spaced_lut_args& args) :
- range({
- static_cast<int>(args.start),
- static_cast<int>(args.stop),
- args.num_elements
- }),
- x_start(scalbn(1, range.start)),
- transfer(args.transfer) {}
-
- binlog_lut(const accel_args& args) :
- binlog_lut(args.spaced_args) {}
- };
-
- struct si_pair {
+ struct si_pair {
float slope = 0;
float intercept = 0;
};
@@ -161,10 +44,11 @@ namespace rawaccel {
si_pair slope_intercept = {};
};
- struct arbitrary_lut {
- enum { capacity = ARB_LUT_CAPACITY };
+ struct lookup {
+ enum { capacity = LUT_POINTS_CAPACITY };
fp_rep_range range;
+ bool velocity_points;
arbitrary_lut_point data[capacity] = {};
int log_lookup[capacity] = {};
double first_point_speed;
@@ -173,9 +57,8 @@ namespace rawaccel {
int last_log_lookup_index;
double last_log_lookup_speed;
double first_log_lookup_speed;
- bool velocity_points;
- double operator()(double speed) const
+ double operator()(double speed, const accel_args&) const
{
int index = 0;
int last_arb_index = last_arbitrary_index;
@@ -247,8 +130,11 @@ namespace rawaccel {
}
}
- void fill(const vec2<float>* points, int length)
+ void fill(const float* raw_data, int raw_length)
{
+ auto* points = reinterpret_cast<const vec2<float>*>(raw_data);
+ int length = raw_length / 2;
+
first_point_speed = points[0].x;
last_arbitrary_index = length - 1;
// -2 because the last index in the arbitrary array is used for slope-intercept only
@@ -297,10 +183,11 @@ namespace rawaccel {
}
}
- arbitrary_lut(const accel_args& args)
+ lookup(const accel_args& args)
{
- velocity_points = args.arb_args.velocity;
- fill(args.arb_args.data, args.arb_args.length);
+ velocity_points = args.gain;
+ fill(args.data, args.length);
}
};
+
}
diff --git a/common/accel-motivity.hpp b/common/accel-motivity.hpp
index 0efe7ea..0cd60f8 100644
--- a/common/accel-motivity.hpp
+++ b/common/accel-motivity.hpp
@@ -6,39 +6,50 @@
namespace rawaccel {
- struct sigmoid {
+ template <bool Gain> struct loglog_sigmoid;
+
+ template <>
+ struct loglog_sigmoid<LEGACY> {
double accel;
double motivity;
double midpoint;
double constant;
- sigmoid(const accel_args& args) :
+ loglog_sigmoid(const accel_args& args) :
accel(exp(args.growth_rate)),
motivity(2 * log(args.motivity)),
midpoint(log(args.midpoint)),
constant(-motivity / 2) {}
- double operator()(double x) const
+ double operator()(double x, const accel_args&) const
{
double denom = exp(accel * (midpoint - log(x))) + 1;
return exp(motivity / denom + constant);
}
+
};
- /// <summary> Struct to hold sigmoid (s-shaped) gain implementation. </summary>
- struct motivity : binlog_lut {
+ template <>
+ struct loglog_sigmoid<GAIN> {
+ enum { capacity = LUT_RAW_DATA_CAPACITY };
- using binlog_lut::operator();
+ bool velocity;
+ fp_rep_range range;
+ double x_start;
- motivity(const accel_args& args) :
- binlog_lut(args)
+ loglog_sigmoid(const accel_args& args)
{
+ init({ 0, 8, 8 }, args.gain);
+
double sum = 0;
double a = 0;
- auto sigmoid_sum = [&, sig = sigmoid(args)](double b) mutable {
- double interval = (b - a) / args.spaced_args.partitions;
- for (int i = 1; i <= args.spaced_args.partitions; i++) {
- sum += sig(a + i * interval) * interval;
+ auto sig = loglog_sigmoid<LEGACY>(args);
+ auto sigmoid_sum = [&](double b) {
+ int partitions = 2;
+
+ double interval = (b - a) / partitions;
+ for (int i = 1; i <= partitions; i++) {
+ sum += sig(a + i * interval, args) * interval;
}
a = b;
return sum;
@@ -46,9 +57,49 @@ namespace rawaccel {
fill([&](double x) {
double y = sigmoid_sum(x);
- if (!this->transfer) y /= x;
+ if (!velocity) y /= x;
return y;
- });
+ }, args, range);
+ }
+
+ double operator()(double x, const accel_args& args) const
+ {
+ auto* data = args.data;
+
+ int e = min(ilogb(x), range.stop - 1);
+
+ if (e >= range.start) {
+ int idx_int_log_part = e - range.start;
+ double idx_frac_lin_part = scalbn(x, -e) - 1;
+ double idx_f = range.num * (idx_int_log_part + idx_frac_lin_part);
+
+ unsigned idx = min(static_cast<int>(idx_f), range.size() - 2);
+
+ if (idx < capacity - 1) {
+ double y = lerp(data[idx], data[idx + 1], idx_f - idx);
+ if (velocity) y /= x;
+ return y;
+ }
+ }
+
+ double y = data[0];
+ if (velocity) y /= x_start;
+ return y;
+ }
+
+ void init(const fp_rep_range& r, bool vel)
+ {
+ velocity = vel;
+ range = r;
+ x_start = scalbn(1, range.start);
+ }
+
+ template <typename Func>
+ static void fill(Func fn, const accel_args& args, const fp_rep_range& range)
+ {
+ range.for_each([&, fn, i = 0](double x) mutable {
+ args.data[i++] = static_cast<float>(fn(x));
+ });
}
};
diff --git a/common/accel-natural.hpp b/common/accel-natural.hpp
index 9f76d1a..c5e1c32 100644
--- a/common/accel-natural.hpp
+++ b/common/accel-natural.hpp
@@ -18,11 +18,15 @@ namespace rawaccel {
{
accel = args.decay_rate / fabs(limit);
}
+
};
- struct natural_legacy : natural_base {
+ template<bool Gain> struct natural;
+
+ template<>
+ struct natural<LEGACY> : natural_base {
- double operator()(double x) const
+ double operator()(double x, const accel_args&) const
{
if (x <= offset) return 1;
@@ -34,10 +38,11 @@ namespace rawaccel {
using natural_base::natural_base;
};
- struct natural : natural_base {
+ template<>
+ struct natural<GAIN> : natural_base {
double constant;
- double operator()(double x) const
+ double operator()(double x, const accel_args&) const
{
if (x <= offset) return 1;
@@ -50,6 +55,7 @@ namespace rawaccel {
natural(const accel_args& args) :
natural_base(args),
constant(-limit / accel) {}
- };
+ natural() = default;
+ };
}
diff --git a/common/accel-noaccel.hpp b/common/accel-noaccel.hpp
index 8d1e758..b307d99 100644
--- a/common/accel-noaccel.hpp
+++ b/common/accel-noaccel.hpp
@@ -10,7 +10,7 @@ namespace rawaccel {
accel_noaccel(const accel_args&) {}
accel_noaccel() = default;
- double operator()(double) const { return 1; }
+ double operator()(double, const accel_args&) const { return 1; }
};
}
diff --git a/common/accel-power.hpp b/common/accel-power.hpp
index c8faabb..f727369 100644
--- a/common/accel-power.hpp
+++ b/common/accel-power.hpp
@@ -3,25 +3,67 @@
#include "rawaccel-base.hpp"
#include <math.h>
+#include <float.h>
namespace rawaccel {
- /// <summary> Struct to hold power (non-additive) acceleration implementation. </summary>
- struct power {
- double pre_scale;
- double exponent;
- double post_scale;
+ struct power_base {
+ static double base_fn(double x, const accel_args& args)
+ {
+ // f(x) = w(mx)^k
+ return args.weight * pow(args.scale * x, args.exponent_power);
+ }
+ };
- power(const accel_args& args) :
- pre_scale(args.scale),
- exponent(args.exponent),
- post_scale(args.weight) {}
+ template <bool Gain> struct power;
- double operator()(double speed) const
+ template <>
+ struct power<LEGACY> : power_base {
+ vec2d cap = { DBL_MAX, DBL_MAX };
+
+ power(const accel_args& args)
{
- // f(x) = (mx)^k
- return post_scale * pow(speed * pre_scale, exponent);
+ if (args.cap.x > 0) {
+ cap.x = args.cap.x;
+ cap.y = base_fn(cap.x, args);
+ }
}
+
+ double operator()(double speed, const accel_args& args) const
+ {
+ if (speed < cap.x) {
+ return base_fn(speed, args);
+ }
+ return cap.y;
+ }
+
+ };
+
+ template <>
+ struct power<GAIN> : power_base {
+ vec2d cap = { DBL_MAX, DBL_MAX };
+ double constant = 0;
+
+ power(const accel_args& args)
+ {
+ if (args.cap.x > 0) {
+ cap.x = args.cap.x;
+ double output = base_fn(cap.x, args);
+ cap.y = output * (args.exponent_power + 1);
+ constant = -args.exponent_power * output * args.cap.x;
+ }
+ }
+
+ double operator()(double speed, const accel_args& args) const
+ {
+ if (speed < cap.x) {
+ return base_fn(speed, args);
+ }
+ else {
+ return cap.y + constant / speed;
+ }
+ }
+
};
}
diff --git a/common/accel-union.hpp b/common/accel-union.hpp
index 8495a62..19fd9fe 100644
--- a/common/accel-union.hpp
+++ b/common/accel-union.hpp
@@ -2,101 +2,56 @@
#include "accel-classic.hpp"
#include "accel-jump.hpp"
-#include "accel-natural.hpp"
-#include "accel-power.hpp"
+#include "accel-lookup.hpp"
#include "accel-motivity.hpp"
+#include "accel-natural.hpp"
#include "accel-noaccel.hpp"
+#include "accel-power.hpp"
namespace rawaccel {
- enum class internal_mode {
- classic_lgcy,
- classic_gain,
- jump_lgcy,
- jump_gain,
- natural_lgcy,
- natural_gain,
- motivity_lgcy,
- motivity_gain,
- power,
- lut_arb,
- lut_log,
- lut_lin,
- noaccel
- };
-
- constexpr internal_mode make_mode(accel_mode mode, spaced_lut_mode lut_mode, bool legacy)
- {
- if (lut_mode != spaced_lut_mode::off) {
- switch (lut_mode) {
- case spaced_lut_mode::binlog: return internal_mode::lut_log;
- case spaced_lut_mode::linear: return internal_mode::lut_lin;
- default: return internal_mode::noaccel;
- }
- }
- else if (mode == accel_mode::power) {
- return internal_mode::power;
- }
- else if (mode == accel_mode::arb_lookup) {
- return internal_mode::lut_arb;
- }
- else if (mode >= accel_mode::noaccel) {
- return internal_mode::noaccel;
- }
- else {
- int im = static_cast<int>(mode) * 2 + (legacy ? 0 : 1);
- return static_cast<internal_mode>(im);
+ union accel_union {
+ accel_noaccel noaccel;
+ lookup lut;
+ classic<GAIN> classic_g;
+ classic<LEGACY> classic_l;
+ jump<GAIN> jump_g;
+ jump<LEGACY> jump_l;
+ natural<GAIN> natural_g;
+ natural<LEGACY> natural_l;
+ power<GAIN> power_g;
+ power<LEGACY> power_l;
+ loglog_sigmoid<GAIN> loglog_sigmoid_g;
+ loglog_sigmoid<LEGACY> loglog_sigmoid_l;
+
+ void init(const accel_args& args)
+ {
+ visit([&](auto& impl) {
+ impl = { args };
+ }, args);
}
- }
- constexpr internal_mode make_mode(const accel_args& args)
- {
- return make_mode(args.mode, args.spaced_args.mode, args.legacy);
- }
-
- template <typename Visitor, typename AccelUnion>
- constexpr auto visit_accel(Visitor vis, internal_mode mode, AccelUnion&& u)
- {
- switch (mode) {
- case internal_mode::classic_lgcy: return vis(u.classic_l);
- case internal_mode::classic_gain: return vis(u.classic_g);
- case internal_mode::jump_lgcy: return vis(u.jump_l);
- case internal_mode::jump_gain: return vis(u.jump_g);
- case internal_mode::natural_lgcy: return vis(u.natural_l);
- case internal_mode::natural_gain: return vis(u.natural_g);
- case internal_mode::motivity_lgcy: return vis(u.motivity_l);
- case internal_mode::motivity_gain: return vis(u.motivity_g);
- case internal_mode::power: return vis(u.power);
- case internal_mode::lut_arb: return vis(u.arb_lut);
- case internal_mode::lut_log: return vis(u.log_lut);
- case internal_mode::lut_lin: return vis(u.lin_lut);
- default: return vis(u.noaccel);
+ template <typename Visitor>
+ auto visit(Visitor vis, const accel_args& args)
+ {
+ switch (args.mode) {
+ case accel_mode::classic: return visit_helper<classic>(vis, args.gain);
+ case accel_mode::jump: return visit_helper<jump>(vis, args.gain);
+ case accel_mode::natural: return visit_helper<natural>(vis, args.gain);
+ case accel_mode::motivity: return visit_helper<loglog_sigmoid>(vis, args.gain);
+ case accel_mode::power: return visit_helper<power>(vis, args.gain);
+ case accel_mode::lookup: return vis(lut);
+ default: return vis(noaccel);
+ }
}
- }
-
- union accel_union {
- classic classic_g;
- classic_legacy classic_l;
- jump jump_g;
- jump_legacy jump_l;
- natural natural_g;
- natural_legacy natural_l;
- power power;
- sigmoid motivity_l;
- motivity motivity_g;
- linear_lut lin_lut;
- binlog_lut log_lut;
- arbitrary_lut arb_lut;
- accel_noaccel noaccel = {};
- accel_union(const accel_args& args)
+ private:
+ template <template <bool> class AccelTemplate, typename Visitor>
+ auto visit_helper(Visitor vis, bool gain)
{
- visit_accel([&](auto& impl) {
- impl = { args };
- }, make_mode(args), *this);
+ if (gain) return vis(reinterpret_cast<AccelTemplate<GAIN>&>(*this));
+ else return vis(reinterpret_cast<AccelTemplate<LEGACY>&>(*this));
}
-
- accel_union() = default;
};
}
diff --git a/common/common.vcxitems b/common/common.vcxitems
index 296fbfd..85af72e 100644
--- a/common/common.vcxitems
+++ b/common/common.vcxitems
@@ -15,7 +15,6 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="$(MSBuildThisFileDirectory)accel-classic.hpp" />
- <ClInclude Include="$(MSBuildThisFileDirectory)accel-invoke.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)accel-jump.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)accel-lookup.hpp" />
<ClInclude Include="$(MSBuildThisFileDirectory)accel-motivity.hpp" />
diff --git a/common/rawaccel-base.hpp b/common/rawaccel-base.hpp
index c1b2db3..ce6c103 100644
--- a/common/rawaccel-base.hpp
+++ b/common/rawaccel-base.hpp
@@ -13,97 +13,81 @@ namespace rawaccel {
inline constexpr milliseconds WRITE_DELAY = 1000;
+ inline constexpr size_t POOL_SIZE = 1024 * 512;
+
inline constexpr size_t MAX_DEV_ID_LEN = 200;
+ inline constexpr size_t MAX_NAME_LEN = 256;
- inline constexpr size_t SPACED_LUT_CAPACITY = 1025;
- inline constexpr size_t ARB_LUT_CAPACITY = SPACED_LUT_CAPACITY / 4;
+ inline constexpr size_t LUT_RAW_DATA_CAPACITY = 258;
+ inline constexpr size_t LUT_POINTS_CAPACITY = LUT_RAW_DATA_CAPACITY / 2;
inline constexpr double MAX_NORM = 16;
inline constexpr double PI = 3.14159265358979323846;
+ inline constexpr bool LEGACY = 0;
+ inline constexpr bool GAIN = 1;
+
enum class accel_mode {
classic,
jump,
natural,
motivity,
power,
- arb_lookup,
+ lookup,
noaccel
};
- enum class spaced_lut_mode {
- off,
- binlog,
- linear
- };
-
- struct spaced_lut_args {
- spaced_lut_mode mode = spaced_lut_mode::off;
- bool transfer = true;
- unsigned char partitions = 2;
- short num_elements = 8;
- double start = 0;
- double stop = 8;
- };
-
- struct table_args {
- bool velocity = true;
- int length = 0;
- vec2<float> data[ARB_LUT_CAPACITY] = {};
+ enum class classic_cap_mode {
+ io, in, out
};
struct accel_args {
accel_mode mode = accel_mode::noaccel;
- bool legacy = false;
+ bool gain = 1;
double offset = 0;
- double cap = 1.5;
- double accel_classic = 0.005;
+ double acceleration = 0.005;
double decay_rate = 0.1;
double growth_rate = 1;
double motivity = 1.5;
- double power = 2;
+ double exponent_classic = 2;
double scale = 1;
double weight = 1;
- double exponent = 0.05;
+ double exponent_power = 0.05;
double limit = 1.5;
double midpoint = 5;
double smooth = 0.5;
+ vec2d cap = { 15, 1.5 };
+ classic_cap_mode cap_mode = classic_cap_mode::out;
- spaced_lut_args spaced_args;
- table_args arb_args;
+ int length = 0;
+ mutable float data[LUT_RAW_DATA_CAPACITY] = {};
};
- struct domain_args {
- vec2d domain_weights = { 1, 1 };
+
+ struct profile {
+ wchar_t name[MAX_NAME_LEN] = L"default";
+
+ bool whole = true;
double lp_norm = 2;
- };
+ vec2d domain_weights = { 1, 1 };
+ vec2d range_weights = { 1, 1 };
+
+ double sensitivity = 1;
+ double yx_sens_ratio = 1;
+
+ accel_args accel_x;
+ accel_args accel_y;
- struct settings {
- double degrees_rotation = 0;
- double degrees_snap = 0;
- bool combine_mags = true;
- double dpi = 1000;
double speed_min = 0;
double speed_max = 0;
- vec2<accel_args> argsv;
- vec2d sens = { 1, 1 };
vec2d dir_multipliers = { 1, 1 };
- domain_args dom_args = {};
- vec2d range_weights = { 1, 1 };
- milliseconds time_min = DEFAULT_TIME_MIN;
- milliseconds time_max = DEFAULT_TIME_MAX;
+ double degrees_rotation = 0;
- bool ignore = false;
- wchar_t device_id[MAX_DEV_ID_LEN] = {};
+ double degrees_snap = 0;
};
- template <typename AccelFunc>
- inline double apply_weighted(AccelFunc&& f, double x, double w)
- {
- return 1 + (f(x) - 1) * w;
- }
}
diff --git a/common/rawaccel-validate.hpp b/common/rawaccel-validate.hpp
index a03f56a..e901a8a 100644
--- a/common/rawaccel-validate.hpp
+++ b/common/rawaccel-validate.hpp
@@ -5,7 +5,7 @@
namespace rawaccel {
- struct valid_ret_t {
+ struct valid_profile_ret_t {
int last_x = 0;
int last_y = 0;
int count = 0;
@@ -16,10 +16,19 @@ namespace rawaccel {
}
};
+ struct valid_device_ret_t {
+ int count = 0;
+
+ explicit operator bool() const
+ {
+ return count == 0;
+ }
+ };
+
template <typename MsgHandler = noop>
- valid_ret_t valid(const settings& args, MsgHandler fn = {})
+ valid_profile_ret_t valid(const profile& args, MsgHandler fn = {})
{
- valid_ret_t ret;
+ valid_profile_ret_t ret;
auto error = [&](auto msg) {
++ret.count;
@@ -27,56 +36,18 @@ namespace rawaccel {
};
auto check_accel = [&error](const accel_args& args) {
- static_assert(SPACED_LUT_CAPACITY == 1025, "update error msg");
-
- const auto& lut_args = args.spaced_args;
-
- if (lut_args.partitions <= 0) {
- error("lut partitions"" must be positive");
- }
-
- if (lut_args.mode == spaced_lut_mode::linear) {
- if (lut_args.start <= 0) {
- error("start"" must be positive");
- }
-
- if (lut_args.stop <= lut_args.start) {
- error("stop must be greater than start");
- }
-
- if (lut_args.num_elements < 2 ||
- lut_args.num_elements > 1025) {
- error("num must be between 2 and 1025");
- }
- }
- else if (lut_args.mode == spaced_lut_mode::binlog) {
- int istart = static_cast<int>(lut_args.start);
- int istop = static_cast<int>(lut_args.stop);
+ static_assert(LUT_POINTS_CAPACITY == 129, "update error msg");
- if (lut_args.start < -99) {
- error("start is too small");
- }
- else if (lut_args.stop > 99) {
- error("stop is too large");
- }
- else if (istart != lut_args.start || istop != lut_args.stop) {
- error("start and stop must be integers");
- }
- else if (istop <= istart) {
- error("stop must be greater than start");
- }
- else if (lut_args.num_elements <= 0) {
- error("num"" must be positive");
+ if (args.mode == accel_mode::lookup) {
+ if (args.length < 4) {
+ error("lookup mode requires at least 2 points");
}
- else if (((lut_args.stop - lut_args.start) * lut_args.num_elements) >= 1025) {
- error("binlog mode requires (num * (stop - start)) < 1025");
+ else if (args.length > ra::LUT_RAW_DATA_CAPACITY) {
+ error("too many data points (max=129)");
}
}
-
- if (args.mode == accel_mode::arb_lookup) {
- if (args.arb_args.length < 2) {
- error("lookup mode requires at least 2 points");
- }
+ else if (args.length > ra::LUT_RAW_DATA_CAPACITY) {
+ error("data size > max");
}
if (args.offset < 0) {
@@ -86,16 +57,28 @@ namespace rawaccel {
error("offset can not be 0");
}
- if (args.cap < 0) {
- error("cap"" must not be negative");
+ bool jump_or_io_cap =
+ (args.mode == accel_mode::jump ||
+ (args.mode == accel_mode::classic &&
+ args.cap_mode == classic_cap_mode::io));
+
+ if (args.cap.x < 0) {
+ error("cap (input) can not be negative");
+ }
+ else if (args.cap.x == 0 && jump_or_io_cap) {
+ error("cap (input) can not be 0");
+ }
+
+ if (args.cap.y < 0) {
+ error("cap (output) can not be negative");
}
- else if (args.mode == accel_mode::jump && args.cap == 0) {
- error("cap can not be 0");
+ else if (args.cap.y == 0 && jump_or_io_cap) {
+ error("cap (output) can not be 0");
}
if (args.growth_rate <= 0 ||
args.decay_rate <= 0 ||
- args.accel_classic <= 0) {
+ args.acceleration <= 0) {
error("acceleration"" must be positive");
}
@@ -103,8 +86,8 @@ namespace rawaccel {
error("motivity must be greater than 1");
}
- if (args.power <= 1) {
- error("power must be greater than 1");
+ if (args.exponent_classic <= 1) {
+ error("exponent must be greater than 1");
}
if (args.scale <= 0) {
@@ -115,7 +98,7 @@ namespace rawaccel {
error("weight"" must be positive");
}
- if (args.exponent <= 0) {
+ if (args.exponent_power <= 0) {
error("exponent"" must be positive");
}
@@ -133,16 +116,16 @@ namespace rawaccel {
};
- check_accel(args.argsv.x);
+ check_accel(args.accel_x);
- if (!args.combine_mags) {
+ if (!args.whole) {
ret.last_x = ret.count;
- check_accel(args.argsv.y);
+ check_accel(args.accel_y);
ret.last_y = ret.count;
}
- if (args.dpi <= 0) {
- error("dpi"" must be positive");
+ if (args.name[0] == L'\0') {
+ error("profile name can not be empty");
}
if (args.speed_max < 0) {
@@ -156,36 +139,61 @@ namespace rawaccel {
error("snap angle must be between 0 and 45 degrees");
}
- if (args.sens.x == 0 || args.sens.y == 0) {
+ if (args.sensitivity == 0) {
error("sens multiplier is 0");
}
+
+ if (args.yx_sens_ratio == 0) {
+ error("Y/X sens ratio is 0");
+ }
- if (args.dom_args.domain_weights.x <= 0 ||
- args.dom_args.domain_weights.y <= 0) {
+ if (args.domain_weights.x <= 0 ||
+ args.domain_weights.y <= 0) {
error("domain weights"" must be positive");
}
if (args.dir_multipliers.x <= 0 || args.dir_multipliers.y <= 0) {
- error("directional multipliers must be positive");
+ error("negative directional multipliers must be positive");
}
- if (args.dom_args.lp_norm < 2) {
- error("Lp norm is less than 2 (default=2)");
+ if (args.lp_norm <= 0) {
+ error("Lp norm must be positive (default=2)");
}
if (args.range_weights.x <= 0 || args.range_weights.y <= 0) {
error("range weights"" must be positive");
}
- if (args.time_min <= 0) {
+ return ret;
+ }
+
+ template <typename MsgHandler = noop>
+ valid_device_ret_t valid(const device_settings& args, MsgHandler fn = {})
+ {
+ valid_device_ret_t ret;
+
+ auto error = [&](auto msg) {
+ ++ret.count;
+ fn(msg);
+ };
+
+
+ if (args.config.dpi < 0) {
+ error("dpi"" can not be negative");
+ }
+
+ if (args.config.polling_rate < 0) {
+ error("polling rate"" can not be negative");
+ }
+
+ if (args.config.clamp.min <= 0) {
error("minimum time"" must be positive");
}
- if (args.time_max < args.time_min) {
+ if (args.config.clamp.max < args.config.clamp.min) {
error("max time is less than min time");
}
return ret;
}
-
}
diff --git a/common/rawaccel.hpp b/common/rawaccel.hpp
index 4e8b46c..4f98c8d 100644
--- a/common/rawaccel.hpp
+++ b/common/rawaccel.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include "accel-invoke.hpp"
+#include "accel-union.hpp"
namespace rawaccel {
@@ -28,45 +28,75 @@ namespace rawaccel {
return pow(pow(v.x, p) + pow(v.y, p), 1 / p);
}
- class mouse_modifier {
- public:
- enum accel_distance_mode : unsigned char {
- separate,
- max,
- Lp,
- euclidean,
+ struct time_clamp {
+ milliseconds min = DEFAULT_TIME_MIN;
+ milliseconds max = DEFAULT_TIME_MAX;
+ };
+
+ struct device_config {
+ bool disable = false;
+ bool set_extra_info = false;
+ int dpi = 0;
+ int polling_rate = 0;
+ time_clamp clamp;
+ };
+
+ struct device_settings {
+ wchar_t name[MAX_NAME_LEN] = {};
+ wchar_t profile[MAX_NAME_LEN] = {};
+ wchar_t id[MAX_DEV_ID_LEN] = {};
+ device_config config;
+ };
+
+ struct driver_settings {
+ profile prof;
+
+ struct data_t {
+ vec2d rot_direction;
+ accel_union accel_x;
+ accel_union accel_y;
+ } data = {};
+ };
+
+ inline void init_data(driver_settings& settings)
+ {
+ auto set_accel = [](accel_union& u, const accel_args& args) {
+ u.visit([&](auto& impl) {
+ impl = { args };
+ }, args);
};
- bool apply_rotate = false;
- bool compute_ref_angle = false;
- bool apply_snap = false;
- bool cap_speed = false;
- accel_distance_mode dist_mode = euclidean;
- bool apply_directional_weight = false;
- bool apply_dir_mul_x = false;
- bool apply_dir_mul_y = false;
-
- vec2d rot_vec = { 1, 0 };
- double snap = 0;
- double dpi_norm_factor = 1;
- double speed_min = 0;
- double speed_max = 0;
- vec2d domain_weights = { 1, 1 };
- double p = 2;
- vec2d range_weights = { 1, 1 };
- vec2d directional_multipliers = { 1, 1 };
- vec2d sensitivity = { 1, 1 };
- vec2<accel_union> accel;
+ set_accel(settings.data.accel_x, settings.prof.accel_x);
+ set_accel(settings.data.accel_y, settings.prof.accel_y);
+
+ settings.data.rot_direction = direction(settings.prof.degrees_rotation);
+ }
+
+ inline constexpr unsigned DRIVER_CAPACITY = POOL_SIZE / sizeof(driver_settings);
+ inline constexpr unsigned DEVICE_CAPACITY = POOL_SIZE / sizeof(device_settings);
+ struct io_t {
+ device_config default_dev_cfg;
+ unsigned driver_data_size;
+ unsigned device_data_size;
+ driver_settings driver_data[DRIVER_CAPACITY];
+ device_settings device_data[DEVICE_CAPACITY];
+ };
+
+ class modifier {
+ public:
#ifdef _KERNEL_MODE
__forceinline
#endif
- void modify(vec2d& in, const vec2<accel_invoker>& inv, milliseconds time = 1) const
+ void modify(vec2d& in, const driver_settings& settings, double dpi_factor, milliseconds time) const
{
- double ips_factor = dpi_norm_factor / time;
+ auto& args = settings.prof;
+ auto& data = settings.data;
+
double reference_angle = 0;
+ double ips_factor = dpi_factor / time;
- if (apply_rotate) in = rotate(in, rot_vec);
+ if (apply_rotate) in = rotate(in, data.rot_direction);
if (compute_ref_angle && in.y != 0) {
if (in.x == 0) {
@@ -76,6 +106,8 @@ namespace rawaccel {
reference_angle = atan(fabs(in.y / in.x));
if (apply_snap) {
+ double snap = args.degrees_snap * PI / 180;
+
if (reference_angle > PI / 2 - snap) {
reference_angle = PI / 2;
in = { 0, _copysign(magnitude(in), in.y) };
@@ -88,92 +120,127 @@ namespace rawaccel {
}
}
- if (cap_speed) {
+ if (clamp_speed) {
double speed = magnitude(in) * ips_factor;
- double ratio = clampsd(speed, speed_min, speed_max) / speed;
+ double ratio = clampsd(speed, args.speed_min, args.speed_max) / speed;
in.x *= ratio;
in.y *= ratio;
}
vec2d abs_weighted_vel = {
- fabs(in.x * ips_factor * domain_weights.x),
- fabs(in.y * ips_factor * domain_weights.y)
+ fabs(in.x * ips_factor * args.domain_weights.x),
+ fabs(in.y * ips_factor * args.domain_weights.y)
};
- if (dist_mode == separate) {
- in.x *= inv.x.invoke(accel.x, abs_weighted_vel.x, range_weights.x);
- in.y *= inv.y.invoke(accel.y, abs_weighted_vel.y, range_weights.y);
+ if (distance_mode == separate) {
+ in.x *= (*cb_x)(data.accel_x, args.accel_x, abs_weighted_vel.x, args.range_weights.x);
+ in.y *= (*cb_y)(data.accel_y, args.accel_y, abs_weighted_vel.y, args.range_weights.y);
}
- else {
+ else {
double speed;
- if (dist_mode == max) {
+ if (distance_mode == max) {
speed = maxsd(abs_weighted_vel.x, abs_weighted_vel.y);
}
- else if (dist_mode == Lp) {
- speed = lp_distance(abs_weighted_vel, p);
+ else if (distance_mode == Lp) {
+ speed = lp_distance(abs_weighted_vel, args.lp_norm);
}
else {
speed = magnitude(abs_weighted_vel);
}
- double weight = range_weights.x;
+ double weight = args.range_weights.x;
if (apply_directional_weight) {
- double diff = range_weights.y - range_weights.x;
+ double diff = args.range_weights.y - args.range_weights.x;
weight += 2 / PI * reference_angle * diff;
}
- double scale = inv.x.invoke(accel.x, speed, weight);
+ double scale = (*cb_x)(data.accel_x, args.accel_x, speed, weight);
in.x *= scale;
in.y *= scale;
}
+ double dpi_adjusted_sens = args.sensitivity * dpi_factor;
+ in.x *= dpi_adjusted_sens;
+ in.y *= dpi_adjusted_sens * args.yx_sens_ratio;
+
if (apply_dir_mul_x && in.x < 0) {
- in.x *= directional_multipliers.x;
+ in.x *= args.dir_multipliers.x;
}
if (apply_dir_mul_y && in.y < 0) {
- in.y *= directional_multipliers.y;
+ in.y *= args.dir_multipliers.y;
}
-
- in.x *= sensitivity.x;
- in.y *= sensitivity.y;
}
- mouse_modifier(const settings& args) :
- rot_vec(direction(args.degrees_rotation)),
- snap(args.degrees_snap * PI / 180),
- dpi_norm_factor(1000 / args.dpi),
- speed_min(args.speed_min),
- speed_max(args.speed_max),
- p(args.dom_args.lp_norm),
- domain_weights(args.dom_args.domain_weights),
- range_weights(args.range_weights),
- directional_multipliers(args.dir_multipliers),
- sensitivity(args.sens),
- accel({ { args.argsv.x }, { args.argsv.y } })
+ modifier(driver_settings& settings)
{
- cap_speed = speed_max > 0 && speed_min <= speed_max;
- apply_rotate = rot_vec.x != 1;
- apply_snap = snap != 0;
- apply_directional_weight = range_weights.x != range_weights.y;
+ auto& args = settings.prof;
+
+ clamp_speed = args.speed_max > 0 && args.speed_min <= args.speed_max;
+ apply_rotate = args.degrees_rotation != 0;
+ apply_snap = args.degrees_snap != 0;
+ apply_directional_weight = args.range_weights.x != args.range_weights.y;
compute_ref_angle = apply_snap || apply_directional_weight;
- apply_dir_mul_x = directional_multipliers.x != 1;
- apply_dir_mul_y = directional_multipliers.y != 1;
+ apply_dir_mul_x = args.dir_multipliers.x != 1;
+ apply_dir_mul_y = args.dir_multipliers.y != 1;
- if (!args.combine_mags) dist_mode = separate;
- else if (p >= MAX_NORM || p <= 0) dist_mode = max;
- else if (p != 2) dist_mode = Lp;
- else dist_mode = euclidean;
+ if (!args.whole) {
+ distance_mode = distance_mode::separate;
+ }
+ else if (args.lp_norm >= MAX_NORM || args.lp_norm <= 0) {
+ distance_mode = distance_mode::max;
+ }
+ else if (args.lp_norm != 2) {
+ distance_mode = distance_mode::Lp;
+ }
+ else {
+ distance_mode = distance_mode::euclidean;
+ }
+
+ set_callback(cb_x, settings.data.accel_x, args.accel_x);
+ set_callback(cb_y, settings.data.accel_y, args.accel_y);
}
- mouse_modifier() = default;
- };
+ modifier() = default;
- struct io_t {
- settings args;
- mouse_modifier mod;
+ private:
+ using callback_t = double (*)(const accel_union&, const accel_args&, double, double);
+
+ void set_callback(callback_t& cb, accel_union& u, const accel_args& args)
+ {
+ u.visit([&](auto& impl) {
+ cb = &callback_template<remove_ref_t<decltype(impl)>>;
+ }, args);
+ }
+
+ template <typename AccelFunc>
+ static double callback_template(const accel_union& u,
+ const accel_args& args,
+ double x,
+ double range_weight)
+ {
+ auto& accel_fn = reinterpret_cast<const AccelFunc&>(u);
+ return 1 + (accel_fn(x, args) - 1) * range_weight;
+ }
+
+ bool apply_rotate = 0;
+ bool compute_ref_angle = 0;
+ bool apply_snap = 0;
+ bool clamp_speed = 0;
+ enum distance_mode : unsigned char {
+ separate,
+ max,
+ Lp,
+ euclidean,
+ } distance_mode = {};
+ bool apply_directional_weight = 0;
+ bool apply_dir_mul_x = 0;
+ bool apply_dir_mul_y = 0;
+
+ callback_t cb_x = &callback_template<accel_noaccel>;
+ callback_t cb_y = &callback_template<accel_noaccel>;
};
} // rawaccel
diff --git a/common/utility.hpp b/common/utility.hpp
index cbd19e3..63026c3 100644
--- a/common/utility.hpp
+++ b/common/utility.hpp
@@ -85,4 +85,10 @@ namespace rawaccel {
template <typename T, typename U>
inline constexpr bool is_same_v = is_same<T, U>::value;
+ template <class T> struct is_rvalue_ref { static constexpr bool value = false; };
+ template <class T> struct is_rvalue_ref<T&&> { static constexpr bool value = true; };
+
+ template <class T>
+ inline constexpr bool is_rvalue_ref_v = is_rvalue_ref<T>::value;
+
}
diff --git a/driver/driver.cpp b/driver/driver.cpp
index feace77..82a56c0 100644
--- a/driver/driver.cpp
+++ b/driver/driver.cpp
@@ -7,22 +7,37 @@
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
+#pragma alloc_text (INIT, RawaccelInit)
+#pragma alloc_text (INIT, CreateControlDevice)
#pragma alloc_text (PAGE, EvtDeviceAdd)
#pragma alloc_text (PAGE, EvtIoInternalDeviceControl)
#pragma alloc_text (PAGE, RawaccelControl)
+#pragma alloc_text (PAGE, DeviceCleanup)
+#pragma alloc_text (PAGE, DeviceSetup)
+#pragma alloc_text (PAGE, WriteDelay)
#endif
using milliseconds = double;
struct {
- ra::settings args;
+ WDFCOLLECTION device_collection;
+ WDFWAITLOCK collection_lock;
+ ra::device_config default_dev_cfg;
+ unsigned device_data_size;
+ unsigned driver_data_size;
+ ra::device_settings* device_data;
+ ra::driver_settings* driver_data;
milliseconds tick_interval;
- vec2<ra::accel_invoker> invokers;
- ra::mouse_modifier modifier;
+ ra::modifier modifier_data[ra::DRIVER_CAPACITY];
} global = {};
extern "C" PULONG InitSafeBootMode;
+bool init_failed()
+{
+ return global.driver_data_size == 0;
+};
+
__declspec(guard(ignore))
VOID
RawaccelCallback(
@@ -58,26 +73,39 @@ Arguments:
auto num_packets = InputDataEnd - InputDataStart;
if (num_packets > 0 &&
- !(InputDataStart->Flags & MOUSE_MOVE_ABSOLUTE) &&
- (global.args.device_id[0] == 0 ||
- bool(wcsncmp(devExt->dev_id, global.args.device_id, ra::MAX_DEV_ID_LEN)) ==
- global.args.ignore)) {
- counter_t now = KeQueryPerformanceCounter(NULL).QuadPart;
- counter_t ticks = now - devExt->counter;
- devExt->counter = now;
- milliseconds raw_elapsed = ticks * global.tick_interval;
- milliseconds time = ra::clampsd(raw_elapsed / num_packets,
- global.args.time_min,
- global.args.time_max);
+ !(InputDataStart->Flags & MOUSE_MOVE_ABSOLUTE) &&
+ devExt->enable) {
+
+ milliseconds time;
+ if (devExt->keep_time) {
+ counter_t now = KeQueryPerformanceCounter(NULL).QuadPart;
+ counter_t ticks = now - devExt->counter;
+ devExt->counter = now;
+ milliseconds raw = ticks * global.tick_interval / num_packets;
+ time = ra::clampsd(raw, devExt->clamp.min, devExt->clamp.max);
+ }
+ else {
+ time = devExt->clamp.min;
+ }
+
auto it = InputDataStart;
do {
+ if (devExt->set_extra_info) {
+ union {
+ short input[2];
+ ULONG data;
+ } u = { short(it->LastX), short(it->LastY) };
+
+ it->ExtraInformation = u.data;
+ }
+
if (it->LastX || it->LastY) {
vec2d input = {
static_cast<double>(it->LastX),
static_cast<double>(it->LastY)
};
- global.modifier.modify(input, global.invokers, time);
+ devExt->mod_ptr->modify(input, *devExt->drv_ptr, devExt->dpi_factor, time);
double carried_result_x = input.x + devExt->carry.x;
double carried_result_y = input.y + devExt->carry.y;
@@ -89,8 +117,8 @@ Arguments:
double carry_y = carried_result_y - out_y;
if (!ra::infnan(carry_x + carry_y)) {
- devExt->carry.x = carried_result_x - out_x;
- devExt->carry.y = carried_result_y - out_y;
+ devExt->carry.x = carry_x;
+ devExt->carry.y = carry_y;
it->LastX = out_x;
it->LastY = out_y;
}
@@ -149,6 +177,11 @@ Return Value:
DebugPrint(("Ioctl received into filter control object.\n"));
+ if (init_failed()) {
+ WdfRequestCompleteWithInformation(Request, STATUS_CANCELLED, 0);
+ return;
+ }
+
switch (IoControlCode) {
case RA_READ:
status = WdfRequestRetrieveOutputBuffer(
@@ -161,10 +194,15 @@ Return Value:
DebugPrint(("RetrieveOutputBuffer failed: 0x%x\n", status));
}
else {
- ra::io_t& output = *reinterpret_cast<ra::io_t*>(buffer);
+ ra::io_t& output = *static_cast<ra::io_t*>(buffer);
- output.args = global.args;
- output.mod = global.modifier;
+ output.default_dev_cfg = global.default_dev_cfg;
+
+ output.device_data_size = global.device_data_size;
+ output.driver_data_size = global.driver_data_size;
+
+ RtlCopyMemory(output.device_data, global.device_data, sizeof(output.device_data));
+ RtlCopyMemory(output.driver_data, global.driver_data, sizeof(output.driver_data));
bytes_out = sizeof(ra::io_t);
}
@@ -180,16 +218,38 @@ Return Value:
DebugPrint(("RetrieveInputBuffer failed: 0x%x\n", status));
}
else {
- LARGE_INTEGER interval;
- interval.QuadPart = static_cast<LONGLONG>(ra::WRITE_DELAY) * -10000;
- KeDelayExecutionThread(KernelMode, FALSE, &interval);
+ WriteDelay();
- ra::io_t& input = *reinterpret_cast<ra::io_t*>(buffer);
+ ra::io_t& input = *static_cast<ra::io_t*>(buffer);
- global.args = input.args;
- global.invokers = ra::invokers(input.args);
- global.modifier = input.mod;
+ if (input.driver_data_size == 0) {
+ status = STATUS_CANCELLED;
+ break;
+ }
+
+ WdfWaitLockAcquire(global.collection_lock, NULL);
+
+ global.default_dev_cfg = input.default_dev_cfg;
+
+ global.device_data_size = ra::min(input.device_data_size, ra::DEVICE_CAPACITY);
+ global.driver_data_size = ra::min(input.driver_data_size, ra::DRIVER_CAPACITY);
+
+ RtlCopyMemory(global.device_data, input.device_data, sizeof(input.device_data));
+ RtlCopyMemory(global.driver_data, input.driver_data, sizeof(input.driver_data));
+
+ for (auto i = 0u; i < global.driver_data_size; i++) {
+ global.modifier_data[i] = { global.driver_data[i] };
+ }
+
+ auto count = WdfCollectionGetCount(global.device_collection);
+
+ for (auto i = 0u; i < count; i++) {
+ DeviceSetup(WdfCollectionGetItem(global.device_collection, i));
+ }
+
+ WdfWaitLockRelease(global.collection_lock);
}
+
break;
case RA_GET_VERSION:
status = WdfRequestRetrieveOutputBuffer(
@@ -202,7 +262,7 @@ Return Value:
DebugPrint(("RetrieveOutputBuffer failed: 0x%x\n", status));
}
else {
- *reinterpret_cast<ra::version_t*>(buffer) = ra::version;
+ *static_cast<ra::version_t*>(buffer) = ra::version;
bytes_out = sizeof(ra::version_t);
}
break;
@@ -216,6 +276,135 @@ Return Value:
}
#pragma warning(pop) // enable 28118 again
+VOID
+RawaccelInit(WDFDRIVER driver)
+{
+ NTSTATUS status;
+
+ if (*InitSafeBootMode > 0) return;
+
+ status = CreateControlDevice(driver);
+
+ if (!NT_SUCCESS(status)) {
+ DebugPrint(("CreateControlDevice failed with status 0x%x\n", status));
+ return;
+ }
+
+ status = WdfCollectionCreate(
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &global.device_collection
+ );
+
+ if (!NT_SUCCESS(status)) {
+ DebugPrint(("WdfCollectionCreate failed with status 0x%x\n", status));
+ return;
+ }
+
+ status = WdfWaitLockCreate(
+ WDF_NO_OBJECT_ATTRIBUTES,
+ &global.collection_lock
+ );
+
+ if (!NT_SUCCESS(status)) {
+ DebugPrint(("WdfWaitLockCreate failed with status 0x%x\n", status));
+ return;
+ }
+
+ void* paged_p = ExAllocatePoolWithTag(PagedPool, ra::POOL_SIZE, 'g');
+ if (paged_p) {
+ RtlZeroMemory(paged_p, ra::POOL_SIZE);
+ global.device_data = static_cast<ra::device_settings*>(paged_p);
+ }
+ else {
+ DebugPrint(("ExAllocatePoolWithTag (PagedPool) failed"));
+ return;
+ }
+
+ void* nonpaged_p = ExAllocatePoolWithTag(NonPagedPool, ra::POOL_SIZE, 'g');
+ if (nonpaged_p) {
+ RtlZeroMemory(nonpaged_p, ra::POOL_SIZE);
+ global.driver_data = static_cast<ra::driver_settings*>(nonpaged_p);
+ *global.driver_data = {};
+ *global.modifier_data = { *global.driver_data };
+ global.driver_data_size = 1;
+ }
+ else {
+ DebugPrint(("ExAllocatePoolWithTag (NonPagedPool) failed"));
+ return;
+ }
+
+ LARGE_INTEGER freq;
+ KeQueryPerformanceCounter(&freq);
+ global.tick_interval = 1e3 / freq.QuadPart;
+}
+
+VOID
+DeviceSetup(WDFOBJECT hDevice)
+{
+ auto* devExt = FilterGetData(hDevice);
+
+ auto set_ext_from_cfg = [devExt](const ra::device_config& cfg) {
+ devExt->enable = !cfg.disable;
+ devExt->set_extra_info = cfg.set_extra_info;
+ devExt->keep_time = cfg.polling_rate <= 0;
+ devExt->dpi_factor = (cfg.dpi > 0) ? (1000.0 / cfg.dpi) : 1;
+
+ if (devExt->keep_time) {
+ devExt->clamp = cfg.clamp;
+ }
+ else {
+ milliseconds interval = 1000.0 / cfg.polling_rate;
+ devExt->clamp = { interval, interval };
+ }
+ };
+
+ set_ext_from_cfg(global.default_dev_cfg);
+ devExt->counter = 0;
+ devExt->carry = {};
+ devExt->drv_ptr = global.driver_data;
+ devExt->mod_ptr = global.modifier_data;
+
+ for (auto i = 0u; i < global.device_data_size; i++) {
+ auto& dev_settings = global.device_data[i];
+
+ if (wcsncmp(devExt->dev_id, dev_settings.id, ra::MAX_DEV_ID_LEN) == 0) {
+ set_ext_from_cfg(dev_settings.config);
+
+ if (dev_settings.profile[0] != L'\0') {
+ for (auto j = 0u; j < global.driver_data_size; j++) {
+ auto& profile = global.driver_data[j].prof;
+
+ if (wcsncmp(dev_settings.profile, profile.name, ra::MAX_NAME_LEN) == 0) {
+ devExt->drv_ptr = &global.driver_data[j];
+ devExt->mod_ptr = &global.modifier_data[j];
+ return;
+ }
+ }
+ }
+
+ return;
+ }
+ }
+}
+
+VOID
+DeviceCleanup(WDFOBJECT hDevice)
+{
+ PAGED_CODE();
+ DebugPrint(("Removing device from collection\n"));
+
+ WdfWaitLockAcquire(global.collection_lock, NULL);
+ WdfCollectionRemove(global.device_collection, hDevice);
+ WdfWaitLockRelease(global.collection_lock);
+}
+
+VOID
+WriteDelay()
+{
+ LARGE_INTEGER interval;
+ interval.QuadPart = static_cast<LONGLONG>(ra::WRITE_DELAY) * -10000;
+ KeDelayExecutionThread(KernelMode, FALSE, &interval);
+}
NTSTATUS
DriverEntry(
@@ -259,14 +448,10 @@ Routine Description:
WDF_NO_OBJECT_ATTRIBUTES,
&config,
&driver);
-
- if (NT_SUCCESS(status)) {
- LARGE_INTEGER freq;
- KeQueryPerformanceCounter(&freq);
- global.tick_interval = 1e3 / freq.QuadPart;
- CreateControlDevice(driver);
+ if (NT_SUCCESS(status)) {
+ RawaccelInit(driver);
}
else {
DebugPrint(("WdfDriverCreate failed with status 0x%x\n", status));
@@ -276,8 +461,7 @@ Routine Description:
}
-inline
-VOID
+NTSTATUS
CreateControlDevice(WDFDRIVER Driver)
/*++
Routine Description:
@@ -378,7 +562,7 @@ Return Value:
//
WdfControlFinishInitializing(controlDevice);
- return;
+ return STATUS_SUCCESS;
Error:
@@ -393,8 +577,9 @@ Error:
}
DebugPrint(("CreateControlDevice failed with status code 0x%x\n", status));
-}
+ return status;
+}
NTSTATUS
EvtDeviceAdd(
@@ -437,7 +622,7 @@ Return Value:
DebugPrint(("Enter FilterEvtDeviceAdd \n"));
- if (*InitSafeBootMode > 0) {
+ if (init_failed()) {
return STATUS_SUCCESS;
}
@@ -453,6 +638,7 @@ Return Value:
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes,
DEVICE_EXTENSION);
+ deviceAttributes.EvtCleanupCallback = DeviceCleanup;
//
// Create a framework device object. This call will in turn create
@@ -469,7 +655,7 @@ Return Value:
// get device id from bus driver
//
DEVICE_OBJECT* pdo = WdfDeviceWdmGetPhysicalDevice(hDevice);
-
+
KEVENT ke;
KeInitializeEvent(&ke, NotificationEvent, FALSE);
IO_STATUS_BLOCK iosb = {};
@@ -480,18 +666,36 @@ Return Value:
stack->MinorFunction = IRP_MN_QUERY_ID;
stack->Parameters.QueryId.IdType = BusQueryDeviceID;
- NTSTATUS nts = IoCallDriver(pdo, Irp);
+ NTSTATUS tmp = IoCallDriver(pdo, Irp);
- if (nts == STATUS_PENDING) {
+ if (tmp == STATUS_PENDING) {
KeWaitForSingleObject(&ke, Executive, KernelMode, FALSE, NULL);
+ tmp = iosb.Status;
}
- if (NT_SUCCESS(nts)) {
- auto* id_ptr = reinterpret_cast<WCHAR*>(iosb.Information);
- wcsncpy(FilterGetData(hDevice)->dev_id, id_ptr, ra::MAX_DEV_ID_LEN);
- DebugPrint(("Device ID = %ws\n", id_ptr));
+ auto* devExt = FilterGetData(hDevice);
+
+ if (NT_SUCCESS(tmp)) {
+ auto* id_ptr = reinterpret_cast<WCHAR*>(iosb.Information);
+ wcsncpy(devExt->dev_id, id_ptr, ra::MAX_DEV_ID_LEN);
ExFreePool(id_ptr);
}
+ else {
+ DebugPrint(("IoCallDriver failed with status 0x%x\n", tmp));
+ *devExt->dev_id = L'\0';
+ }
+
+ WdfWaitLockAcquire(global.collection_lock, NULL);
+
+ DeviceSetup(hDevice);
+
+ tmp = WdfCollectionAdd(global.device_collection, hDevice);
+
+ if (!NT_SUCCESS(tmp)) {
+ DebugPrint(("WdfCollectionAdd failed with status 0x%x\n", tmp));
+ }
+
+ WdfWaitLockRelease(global.collection_lock);
//
// Configure the default queue to be Parallel. Do not use sequential queue
@@ -597,8 +801,6 @@ Routine Description:
break;
}
- devExt->counter = 0;
- devExt->carry = {};
devExt->UpperConnectData = *connectData;
//
@@ -636,7 +838,6 @@ Routine Description:
}
-inline
VOID
DispatchPassThrough(
_In_ WDFREQUEST Request,
diff --git a/driver/driver.h b/driver/driver.h
index 6184a69..4818367 100644
--- a/driver/driver.h
+++ b/driver/driver.h
@@ -1,6 +1,6 @@
#pragma once
-#include "rawaccel-base.hpp"
+#include "rawaccel.hpp"
#include "rawaccel-io-def.h"
#include <kbdmou.h>
@@ -19,7 +19,14 @@ using counter_t = long long;
namespace ra = rawaccel;
typedef struct _DEVICE_EXTENSION {
+ bool enable;
+ bool keep_time;
+ bool set_extra_info;
+ double dpi_factor;
counter_t counter;
+ ra::time_clamp clamp;
+ ra::driver_settings* drv_ptr;
+ ra::modifier* mod_ptr;
vec2d carry;
CONNECT_DATA UpperConnectData;
WCHAR dev_id[ra::MAX_DEV_ID_LEN];
@@ -30,9 +37,18 @@ WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DEVICE_EXTENSION, FilterGetData)
EXTERN_C_START
DRIVER_INITIALIZE DriverEntry;
+
EVT_WDF_DRIVER_DEVICE_ADD EvtDeviceAdd;
EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL EvtIoInternalDeviceControl;
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL RawaccelControl;
+EVT_WDF_OBJECT_CONTEXT_CLEANUP DeviceCleanup;
+
+VOID DeviceSetup(WDFOBJECT);
+VOID WriteDelay(VOID);
+VOID RawaccelInit(WDFDRIVER);
+NTSTATUS CreateControlDevice(WDFDRIVER);
+
+EXTERN_C_END
VOID RawaccelCallback(
IN PDEVICE_OBJECT DeviceObject,
@@ -41,10 +57,6 @@ VOID RawaccelCallback(
IN OUT PULONG InputDataConsumed
);
-EXTERN_C_END
-
-VOID CreateControlDevice(WDFDRIVER Driver);
-
VOID DispatchPassThrough(
_In_ WDFREQUEST Request,
_In_ WDFIOTARGET Target
diff --git a/wrapper/input.cpp b/wrapper/input.cpp
index 3ce257a..e34af4a 100644
--- a/wrapper/input.cpp
+++ b/wrapper/input.cpp
@@ -1,79 +1,90 @@
#include "input.h"
-#include "interop-exception.h"
-
-#include <msclr\marshal_cppstd.h>
-#include <algorithm>
using namespace System;
using namespace System::Collections::Generic;
+using namespace System::Runtime::InteropServices;
-std::vector<HANDLE> rawinput_handles_from_id(const std::wstring& device_id)
-{
- std::vector<HANDLE> handles;
+[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)]
+public ref struct RawInputDevice {
+ System::IntPtr handle;
- rawinput_foreach([&](const auto& dev) {
- if (dev.id == device_id) handles.push_back(dev.handle);
- });
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = MAX_NAME_LEN)]
+ System::String^ name;
- return handles;
-}
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = MAX_DEV_ID_LEN)]
+ System::String^ id;
+};
-std::vector<std::wstring> rawinput_id_list()
+static int CompareByID(RawInputDevice^ x, RawInputDevice^ y)
{
- std::vector<std::wstring> ids;
-
- rawinput_foreach([&](const auto& dev) {
- ids.push_back(dev.id);
- });
-
- std::sort(ids.begin(), ids.end());
- ids.erase(std::unique(ids.begin(), ids.end()), ids.end());
- return ids;
+ return String::Compare(x->id, y->id);
}
-public ref struct RawInputInteropException : InteropException {
- RawInputInteropException(System::String^ what) :
- InteropException(what) {}
- RawInputInteropException(const char* what) :
- InteropException(what) {}
- RawInputInteropException(const std::exception& e) :
- InteropException(e) {}
-};
+public ref struct MultiHandleDevice {
+ System::String^ name;
+ System::String^ id;
+ List<System::IntPtr>^ handles;
-public ref struct RawInputInterop
-{
- static void AddHandlesFromID(String^ deviceID, List<IntPtr>^ rawInputHandles)
+ // Each element in the list returned has a distinct id
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/device-ids
+ static List<MultiHandleDevice^>^ GetList()
{
- try
- {
- std::vector<HANDLE> nativeHandles = rawinput_handles_from_id(
- msclr::interop::marshal_as<std::wstring>(deviceID));
+ return ListMaker::MakeList();
+ }
- for (auto nh : nativeHandles) rawInputHandles->Add(IntPtr(nh));
- }
- catch (const std::exception& e)
+ ref class ListMaker {
+ List<RawInputDevice^>^ devices = gcnew List<RawInputDevice^>();
+
+ delegate void NativeDevHandler(rawinput_device&);
+
+ void Add(rawinput_device& dev)
{
- throw gcnew RawInputInteropException(e);
+ devices->Add(Marshal::PtrToStructure<RawInputDevice^>(IntPtr(&dev)));
}
- }
- static List<String^>^ GetDeviceIDs()
- {
- try
+ ListMaker() {}
+ public:
+ static List<MultiHandleDevice^>^ MakeList()
{
- auto ids = gcnew List<String^>();
+ auto maker = gcnew ListMaker();
+ NativeDevHandler^ del = gcnew NativeDevHandler(maker, &Add);
+ GCHandle gch = GCHandle::Alloc(del);
+ auto fp = static_cast<void (*)(rawinput_device&)>(
+ Marshal::GetFunctionPointerForDelegate(del).ToPointer());
+ rawinput_foreach(fp);
+ gch.Free();
- for (auto&& name : rawinput_id_list())
- {
- ids->Add(msclr::interop::marshal_as<String^>(name));
+ auto ret = gcnew List<MultiHandleDevice^>();
+ auto count = maker->devices->Count;
+ auto first = 0;
+ auto last = 0;
+
+ if (count > 0) {
+ maker->devices->Sort(gcnew Comparison<RawInputDevice^>(&CompareByID));
+ while (++last != count) {
+ if (!String::Equals(maker->devices[first]->id, maker->devices[last]->id)) {
+ auto range = maker->devices->GetRange(first, last - first);
+ ret->Add(gcnew MultiHandleDevice(range));
+ first = last;
+ }
+ }
+ auto range = maker->devices->GetRange(first, last - first);
+ ret->Add(gcnew MultiHandleDevice(range));
}
- return ids;
+ return ret;
}
- catch (const std::exception& e)
- {
- throw gcnew RawInputInteropException(e);
+ };
+
+private:
+ MultiHandleDevice(IEnumerable<RawInputDevice^>^ seq)
+ {
+ auto it = seq->GetEnumerator();
+ if (it->MoveNext()) {
+ name = it->Current->name;
+ id = it->Current->id;
+ handles = gcnew List<IntPtr>();
+ do handles->Add(it->Current->handle); while (it->MoveNext());
}
}
-
};
diff --git a/wrapper/input.h b/wrapper/input.h
index c83dca8..a931904 100644
--- a/wrapper/input.h
+++ b/wrapper/input.h
@@ -1,77 +1,127 @@
#pragma once
#pragma comment(lib, "cfgmgr32.lib")
+#pragma comment(lib, "hid.lib")
+#pragma comment(lib, "User32.lib")
-#include <string>
-#include <system_error>
+#include <string_view>
#include <vector>
#include <Windows.h>
#include <cfgmgr32.h>
#include <initguid.h> // needed for devpkey.h to parse properly
#include <devpkey.h>
+#include <hidsdi.h>
+
+inline constexpr size_t MAX_DEV_ID_LEN = 200;
+inline constexpr size_t MAX_NAME_LEN = 256;
+inline constexpr UINT RI_ERROR = -1;
struct rawinput_device {
- HANDLE handle;
- std::wstring id;
+ HANDLE handle = nullptr; // rawinput handle
+ WCHAR name[MAX_NAME_LEN] = {}; // manufacturer + product
+ WCHAR id[MAX_DEV_ID_LEN] = {}; // hwid formatted device id
};
template <typename Func>
void rawinput_foreach(Func fn, DWORD device_type = RIM_TYPEMOUSE)
{
- const UINT RI_ERROR = -1;
-
- // get number of devices
- UINT num_devs = 0;
- if (GetRawInputDeviceList(NULL, &num_devs, sizeof(RAWINPUTDEVICELIST)) != 0) {
- throw std::system_error(GetLastError(), std::system_category(), "GetRawInputDeviceList failed");
- }
-
- auto dev_list = std::vector<RAWINPUTDEVICELIST>(num_devs);
+ const size_t HID_STR_MAX_LEN = 127;
+
+ auto starts_with = [](auto&& a, auto&& b) {
+ return b.size() <= a.size() && std::equal(b.begin(), b.end(), a.begin());
+ };
+
+ auto get_dev_list = []() -> std::vector<RAWINPUTDEVICELIST> {
+ UINT elem_size = sizeof(RAWINPUTDEVICELIST);
+ UINT num_devs = 0;
+
+ if (GetRawInputDeviceList(NULL, &num_devs, elem_size) == 0) {
+ auto dev_list = std::vector<RAWINPUTDEVICELIST>(num_devs);
+
+ if (GetRawInputDeviceList(&dev_list[0], &num_devs, elem_size) != RI_ERROR) {
+ return dev_list;
+ }
+ }
- if (GetRawInputDeviceList(&dev_list[0], &num_devs, sizeof(RAWINPUTDEVICELIST)) == RI_ERROR) {
- return;
- }
+ return {};
+ };
- std::wstring name;
+ std::wstring interface_name;
rawinput_device dev;
DEVPROPTYPE prop_type;
CONFIGRET cm_res;
+ WCHAR product_str_buf[HID_STR_MAX_LEN] = {};
- for (auto [handle, dev_type] : dev_list) {
+ for (auto [handle, dev_type] : get_dev_list()) {
if (dev_type != device_type) continue;
- // get interface name length
+ dev.handle = handle;
+
+ // get interface name
UINT name_len = 0;
if (GetRawInputDeviceInfoW(handle, RIDI_DEVICENAME, NULL, &name_len) == RI_ERROR) {
continue;
}
- name.resize(name_len);
+ interface_name.resize(name_len);
- if (GetRawInputDeviceInfoW(handle, RIDI_DEVICENAME, &name[0], &name_len) == RI_ERROR) {
+ if (GetRawInputDeviceInfoW(handle, RIDI_DEVICENAME, &interface_name[0], &name_len) == RI_ERROR) {
continue;
}
- // get sizeof device instance id
+ // make name from vendor + product
+ HANDLE hid_dev_object = CreateFileW(
+ &interface_name[0], 0, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
+
+ if (hid_dev_object != INVALID_HANDLE_VALUE) {
+
+ if (HidD_GetProductString(hid_dev_object, product_str_buf, HID_STR_MAX_LEN)) {
+ auto product_sv = std::wstring_view(product_str_buf);
+
+ if (HidD_GetManufacturerString(hid_dev_object, dev.name, HID_STR_MAX_LEN)) {
+ auto manufacturer_sv = std::wstring_view(dev.name);
+
+ if (starts_with(product_sv, manufacturer_sv)) {
+ wcsncpy_s(dev.name, product_str_buf, HID_STR_MAX_LEN);
+ }
+ else {
+ auto last = manufacturer_sv.size();
+ dev.name[last] = L' ';
+ wcsncpy_s(dev.name + last + 1, HID_STR_MAX_LEN, product_str_buf, HID_STR_MAX_LEN);
+ }
+ }
+ else {
+ wcsncpy_s(dev.name, product_str_buf, HID_STR_MAX_LEN);
+ }
+ }
+ else {
+ dev.name[0] = L'\0';
+ }
+
+ CloseHandle(hid_dev_object);
+ }
+ else {
+ dev.name[0] = L'\0';
+ }
+
+ // get device instance id
ULONG id_size = 0;
- cm_res = CM_Get_Device_Interface_PropertyW(&name[0], &DEVPKEY_Device_InstanceId,
+ cm_res = CM_Get_Device_Interface_PropertyW(&interface_name[0], &DEVPKEY_Device_InstanceId,
&prop_type, NULL, &id_size, 0);
if (cm_res != CR_BUFFER_SMALL && cm_res != CR_SUCCESS) continue;
- dev.id.resize((id_size + 1) / 2);
-
- cm_res = CM_Get_Device_Interface_PropertyW(&name[0], &DEVPKEY_Device_InstanceId,
+ cm_res = CM_Get_Device_Interface_PropertyW(&interface_name[0], &DEVPKEY_Device_InstanceId,
&prop_type, reinterpret_cast<PBYTE>(&dev.id[0]), &id_size, 0);
if (cm_res != CR_SUCCESS) continue;
// pop instance id
- auto instance_delim_pos = dev.id.find_last_of('\\');
- if (instance_delim_pos != std::string::npos) dev.id.resize(instance_delim_pos);
-
- dev.handle = handle;
+ auto instance_delim_pos = std::wstring_view(dev.id).find_last_of(L'\\');
+ if (instance_delim_pos != std::string::npos) {
+ dev.id[instance_delim_pos] = L'\0';
+ }
fn(dev);
}
diff --git a/wrapper/wrapper.cpp b/wrapper/wrapper.cpp
index 4e6c4f1..fba66fa 100644
--- a/wrapper/wrapper.cpp
+++ b/wrapper/wrapper.cpp
@@ -15,38 +15,36 @@ using namespace Newtonsoft::Json::Linq;
namespace ra = rawaccel;
-ra::settings default_settings;
+ra::driver_settings default_driver_settings;
+ra::device_settings default_device_settings;
-[JsonConverter(Converters::StringEnumConverter::typeid)]
-public enum class AccelMode
+public ref struct VersionHelper
{
- classic, jump, natural, motivity, power, lut, noaccel
+ literal String^ VersionString = RA_VER_STRING;
+
+ static Version^ ValidOrThrow()
+ {
+ try {
+ ra::version_t v = ra::valid_version_or_throw();
+ return gcnew Version(v.major, v.minor, v.patch, 0);
+ }
+ catch (const ra::error& e) {
+ throw gcnew InteropException(e);
+ }
+ }
};
[JsonConverter(Converters::StringEnumConverter::typeid)]
-public enum class SpacedTableMode
+public enum class AccelMode
{
- off, binlog, linear
+ classic, jump, natural, motivity, power, lut, noaccel
};
-[StructLayout(LayoutKind::Sequential)]
-public value struct SpacedTableArgs
-{
- [JsonIgnore]
- SpacedTableMode mode;
-
- [MarshalAs(UnmanagedType::U1)]
- bool transfer;
-
- [MarshalAs(UnmanagedType::U1)]
- unsigned char partitions;
-
- short num;
- double start;
- double stop;
+[JsonConverter(Converters::StringEnumConverter::typeid)]
+public enum class ClassicCapMode {
+ in_out, input, output
};
-
generic <typename T>
[StructLayout(LayoutKind::Sequential)]
public value struct Vec2
@@ -56,442 +54,317 @@ public value struct Vec2
};
[StructLayout(LayoutKind::Sequential)]
-public value struct TableArgs
-{
- [JsonProperty("Whether points affect velocity (true) or sensitivity (false)")]
- [MarshalAs(UnmanagedType::U1)]
- bool velocity;
-
- [JsonIgnore]
- int length;
-
- [MarshalAs(UnmanagedType::ByValArray, SizeConst = ra::ARB_LUT_CAPACITY)]
- array<Vec2<float>>^ points;
-
- virtual bool Equals(Object^ ob) override {
- if (ob->GetType() == this->GetType()) {
- TableArgs^ other = (TableArgs^)ob;
-
- if (this->length != other->length) return false;
- if (this->points == other->points) return true;
-
- if (unsigned(length) >= ra::ARB_LUT_CAPACITY ||
- points == nullptr ||
- other->points == nullptr) {
- throw gcnew InteropException("invalid table args");
- }
-
- for (int i = 0; i < length; i++) {
- if (points[i].x != other->points[i].x ||
- points[i].y != other->points[i].y)
- return false;
- }
-
- return true;
- }
- else {
- return false;
- }
- }
-
- virtual int GetHashCode() override {
- return points->GetHashCode() ^ length.GetHashCode();
- }
-};
-
-[StructLayout(LayoutKind::Sequential)]
public value struct AccelArgs
{
+ literal int MaxLutPoints = ra::LUT_POINTS_CAPACITY;
+
AccelMode mode;
+ [JsonProperty("Gain / Velocity")]
[MarshalAs(UnmanagedType::U1)]
- bool legacy;
+ bool gain;
double offset;
- double cap;
- double accelClassic;
+ double acceleration;
double decayRate;
double growthRate;
double motivity;
- double power;
+ double exponentClassic;
double scale;
double weight;
- double exponent;
+ double exponentPower;
double limit;
double midpoint;
double smooth;
- [JsonProperty(Required = Required::Default)]
- SpacedTableArgs spacedTableArgs;
+ [JsonProperty("Cap / Jump")]
+ Vec2<double> cap;
- TableArgs tableData;
-};
+ [JsonProperty("Cap mode")]
+ ClassicCapMode capMode;
-[StructLayout(LayoutKind::Sequential)]
-public value struct DomainArgs
-{
- Vec2<double> domainXY;
- double lpNorm;
+ [JsonIgnore]
+ int length;
+
+ [MarshalAs(UnmanagedType::ByValArray, SizeConst = ra::LUT_RAW_DATA_CAPACITY)]
+ array<float>^ data;
+
+ [OnDeserialized]
+ void OnDeserializedMethod(StreamingContext context)
+ {
+ // data->Length must match SizeConst when marshalling
+ length = data->Length;
+ array<float>::Resize(data, ra::LUT_RAW_DATA_CAPACITY);
+ }
};
[JsonObject(ItemRequired = Required::Always)]
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)]
-public ref struct DriverSettings
+public ref struct Profile
{
- literal double WriteDelayMs = ra::WRITE_DELAY;
- literal String^ Key = "Driver settings";
-
- [JsonProperty("Degrees of rotation")]
- double rotation;
-
- [JsonProperty("Degrees of angle snapping")]
- double snap;
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
+ System::String^ name;
- [JsonProperty("Use x as whole/combined accel")]
+ [JsonProperty("Whole/combined accel (set false for 'by component' mode)")]
[MarshalAs(UnmanagedType::U1)]
bool combineMagnitudes;
- double dpi;
-
- [JsonIgnore]
- double minimumSpeed;
- [JsonProperty("Input Speed Cap")]
- double maximumSpeed;
-
- [JsonProperty("Accel parameters")]
- Vec2<AccelArgs> args;
-
- [JsonProperty("Sensitivity multipliers")]
- Vec2<double> sensitivity;
-
- [JsonProperty("Negative directional multipliers")]
- Vec2<double> directionalMultipliers;
+ double lpNorm;
[JsonProperty("Stretches domain for horizontal vs vertical inputs")]
- DomainArgs domainArgs;
-
+ Vec2<double> domainXY;
[JsonProperty("Stretches accel range for horizontal vs vertical inputs")]
Vec2<double> rangeXY;
- [JsonProperty(Required = Required::Default)]
- double minimumTime;
+ [JsonProperty("Sensitivity multiplier")]
+ double sensitivity;
- [JsonProperty(Required = Required::Default)]
- double maximumTime;
+ [JsonProperty("Y/X sensitivity ratio (vertical sens multiplier)")]
+ double yxSensRatio;
- [JsonProperty("Ignore devices with matching ID")]
- [MarshalAs(UnmanagedType::U1)]
- bool ignore;
-
- [JsonProperty("Device ID")]
- [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_DEV_ID_LEN)]
- String^ deviceID = "";
+ [JsonProperty("Whole or horizontal accel parameters")]
+ AccelArgs argsX;
+ [JsonProperty("Vertical accel parameters")]
+ AccelArgs argsY;
- bool ShouldSerializeminimumTime()
- {
- return minimumTime != ra::DEFAULT_TIME_MIN;
- }
-
- bool ShouldSerializemaximumTime()
- {
- return maximumTime != ra::DEFAULT_TIME_MAX;
- }
-
- DriverSettings()
- {
- Marshal::PtrToStructure(IntPtr(&default_settings), this);
- }
+ [JsonIgnore]
+ double minimumSpeed;
+ [JsonProperty("Input Speed Cap")]
+ double maximumSpeed;
-private:
- [OnDeserialized]
- void OnDeserializedMethod(StreamingContext context)
- {
- args.x.tableData.length = args.x.tableData.points->Length;
- args.y.tableData.length = args.y.tableData.points->Length;
+ [JsonProperty("Negative directional multipliers")]
+ Vec2<double> directionalMultipliers;
- array<Vec2<float>>::Resize(args.x.tableData.points, ra::ARB_LUT_CAPACITY);
- array<Vec2<float>>::Resize(args.y.tableData.points, ra::ARB_LUT_CAPACITY);
- }
+ [JsonProperty("Degrees of rotation")]
+ double rotation;
- [OnSerializing]
- void OnSerializingMethod(StreamingContext context)
- {
- array<Vec2<float>>::Resize(args.x.tableData.points, args.x.tableData.length);
- array<Vec2<float>>::Resize(args.y.tableData.points, args.y.tableData.length);
- }
+ [JsonProperty("Degrees of angle snapping")]
+ double snap;
- [OnSerialized]
- void OnSerializedMethod(StreamingContext context)
+ Profile(ra::profile& args)
{
- array<Vec2<float>>::Resize(args.x.tableData.points, ra::ARB_LUT_CAPACITY);
- array<Vec2<float>>::Resize(args.y.tableData.points, ra::ARB_LUT_CAPACITY);
+ Marshal::PtrToStructure(IntPtr(&args), this);
}
+ Profile() :
+ Profile(default_driver_settings.prof) {}
};
[JsonObject(ItemRequired = Required::Always)]
-public ref struct LutBase
-{
- [JsonConverter(Converters::StringEnumConverter::typeid)]
- enum class Mode
- {
- logarithmic, linear
- } mode;
-
- virtual void SetArgs(AccelArgs%) {}
- virtual void SetData(ra::accel_union&) {}
-};
-
+[StructLayout(LayoutKind::Sequential)]
+public value struct DeviceConfig {
+ [MarshalAs(UnmanagedType::U1)]
+ bool disable;
-[JsonObject(ItemRequired = Required::Always)]
-public ref struct SpacedLut abstract : public LutBase
-{
- [JsonProperty("Whether points affect velocity (true) or sensitivity (false)")]
- bool transfer;
+ [MarshalAs(UnmanagedType::U1)]
+ [JsonProperty(Required = Required::Default)]
+ bool setExtraInfo;
- double start;
- double stop;
- array<float>^ data;
+ [JsonProperty("DPI (normalizes sens to 1000dpi and converts input speed unit: counts/ms -> in/s)")]
+ int dpi;
- void SetArgsBase(AccelArgs% args)
- {
- args.spacedTableArgs.transfer = transfer;
- args.spacedTableArgs.start = start;
- args.spacedTableArgs.stop = stop;
- }
+ [JsonProperty("Polling rate Hz (keep at 0 for automatic adjustment)")]
+ int pollingRate;
+
+ [ComponentModel::DefaultValue(ra::DEFAULT_TIME_MIN)]
+ [JsonProperty(Required = Required::Default)]
+ double minimumTime;
- void SetDataBase(ra::accel_union& accel)
- {
- if (size_t(data->LongLength) > ra::SPACED_LUT_CAPACITY) {
- throw gcnew InteropException("data is too large");
- }
- }
-};
+ [ComponentModel::DefaultValue(ra::DEFAULT_TIME_MAX)]
+ [JsonProperty(Required = Required::Default)]
+ double maximumTime;
-[JsonObject(ItemRequired = Required::Always)]
-public ref struct LinearLut sealed : public SpacedLut
-{
- LinearLut()
+ bool ShouldSerializesetExtraInfo()
{
+ return setExtraInfo == true;
}
- LinearLut(const ra::linear_lut& table)
+ bool ShouldSerializeminimumTime()
{
- mode = Mode::linear;
- transfer = table.transfer;
- start = table.range.start;
- stop = table.range.stop;
- data = gcnew array<float>(table.range.num);
-
- pin_ptr<float> pdata = &data[0];
- std::memcpy(pdata, &table.data, sizeof(float) * data->Length);
+ return minimumTime != ra::DEFAULT_TIME_MIN;
}
- virtual void SetArgs(AccelArgs% args) override
+ bool ShouldSerializemaximumTime()
{
- SetArgsBase(args);
- args.spacedTableArgs.num = data->Length;
- args.spacedTableArgs.mode = SpacedTableMode::linear;
+ return maximumTime != ra::DEFAULT_TIME_MAX;
}
- virtual void SetData(ra::accel_union& accel) override
+ void Init(const ra::device_config& cfg)
{
- SetDataBase(accel);
- pin_ptr<float> pdata = &data[0];
- std::memcpy(&accel.lin_lut.data, pdata, sizeof(float) * data->Length);
+ disable = cfg.disable;
+ setExtraInfo = cfg.set_extra_info;
+ dpi = cfg.dpi;
+ pollingRate = cfg.polling_rate;
+ minimumTime = cfg.clamp.min;
+ maximumTime = cfg.clamp.max;
}
};
[JsonObject(ItemRequired = Required::Always)]
-public ref struct BinLogLut sealed : public SpacedLut
+[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)]
+public ref struct DeviceSettings
{
- short num;
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
+ String^ name;
- BinLogLut()
- {
- }
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
+ String^ profile;
- BinLogLut(const ra::binlog_lut& table)
- {
- mode = Mode::logarithmic;
- transfer = table.transfer;
- start = table.range.start;
- stop = table.range.stop;
- num = table.range.num;
- data = gcnew array<float>(1 + num * (int(stop) - int(start)));
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_DEV_ID_LEN)]
+ String^ id;
- pin_ptr<float> pdata = &data[0];
- std::memcpy(pdata, &table.data, sizeof(float) * data->Length);
- }
+ DeviceConfig config;
- virtual void SetArgs(AccelArgs% args) override
+ DeviceSettings(ra::device_settings& args)
{
- SetArgsBase(args);
- args.spacedTableArgs.num = num;
- args.spacedTableArgs.mode = SpacedTableMode::binlog;
+ Marshal::PtrToStructure(IntPtr(&args), this);
}
- virtual void SetData(ra::accel_union& accel) override
- {
- SetDataBase(accel);
-
- if (data->Length != 1 + num * (int(stop) - int(start))) {
- throw gcnew InteropException("size of data does not match args");
- }
-
- pin_ptr<float> pdata = &data[0];
- std::memcpy(&accel.log_lut.data, pdata, sizeof(float) * data->Length);
- }
+ DeviceSettings() :
+ DeviceSettings(default_device_settings) {}
};
-public ref struct RaConvert {
- static DriverSettings^ Settings(String^ json)
- {
- return NonNullable<DriverSettings^>(json);
- }
+public ref class ProfileErrors
+{
+ List<String^>^ tmp;
+ bool single;
- static String^ Settings(DriverSettings^ settings)
- {
- JObject^ jObject = JObject::FromObject(settings);
- String^ modes = String::Join(" | ", Enum::GetNames(AccelMode::typeid));
- jObject->AddFirst(gcnew JProperty("### Accel Modes ###", modes));
- return jObject->ToString(Formatting::Indented);
- }
+ delegate void MsgHandler(const char*);
- static LutBase^ Table(String^ json)
+ void Add(const char* msg)
{
- JObject^ jObject = JObject::Parse(json);
-
- if ((Object^)jObject == nullptr) {
- throw gcnew JsonException("bad json");
- }
-
- LutBase^ base = NonNullable<LutBase^>(jObject);
-
- switch (base->mode) {
- case LutBase::Mode::logarithmic:
- return NonNullable<BinLogLut^>(jObject);
- case LutBase::Mode::linear:
- return NonNullable<LinearLut^>(jObject);
- default:
- throw gcnew NotImplementedException();
- }
+ tmp->Add(gcnew String(msg));
}
- static String^ Table(LutBase^ lut)
+public:
+ ref struct SingleProfileErrors
{
- auto serializerSettings = gcnew JsonSerializerSettings();
- return JsonConvert::SerializeObject(
- lut, lut->GetType(), Formatting::Indented, serializerSettings);
+ Profile^ prof;
+ array<String^>^ messages;
+ int lastX;
+ int lastY;
};
- generic <typename T>
- static T NonNullable(String^ json)
+ List<SingleProfileErrors^>^ list;
+
+ ProfileErrors(List<Profile^>^ profiles)
{
- T obj = JsonConvert::DeserializeObject<T>(json);
- if (obj == nullptr) throw gcnew JsonException("invalid JSON");
- return obj;
+ single = profiles->Count == 1;
+ list = gcnew List<SingleProfileErrors^>();
+ tmp = gcnew List<String^>();
+ MsgHandler^ del = gcnew MsgHandler(this, &ProfileErrors::Add);
+ GCHandle gch = GCHandle::Alloc(del);
+ auto fp = static_cast<void (*)(const char*)>(
+ Marshal::GetFunctionPointerForDelegate(del).ToPointer());
+ ra::profile* native_ptr = new ra::profile();
+
+ for each (auto prof in profiles) {
+ Marshal::StructureToPtr(prof, IntPtr(native_ptr), false);
+ auto [last_x, last_y, _] = ra::valid(*native_ptr, fp);
+
+ if (tmp->Count != 0) {
+ auto singleErrors = gcnew SingleProfileErrors();
+ singleErrors->messages = tmp->ToArray();
+ singleErrors->lastX = last_x;
+ singleErrors->lastY = last_y;
+ singleErrors->prof = prof;
+ list->Add(singleErrors);
+ tmp->Clear();
+ }
+ }
+
+ tmp = nullptr;
+ gch.Free();
+ delete native_ptr;
}
- generic <typename T>
- static T NonNullable(JObject^ jObject)
+ bool Empty()
{
- T obj = jObject->ToObject<T>();
- if (obj == nullptr) throw gcnew JsonException("invalid JSON");
- return obj;
+ return list->Count == 0;
}
-};
-public ref struct ExtendedSettings {
- DriverSettings^ baseSettings;
- Vec2<LutBase^> tables;
-
- using JSON = String^;
-
- ExtendedSettings(DriverSettings^ driverSettings) :
- baseSettings(driverSettings) {}
-
- ExtendedSettings() :
- ExtendedSettings(gcnew DriverSettings()) {}
-
- ExtendedSettings(JSON settingsJson) :
- ExtendedSettings(settingsJson, nullptr, nullptr, false) {}
-
- ExtendedSettings(JSON settingsJson, JSON tableJson) :
- ExtendedSettings(settingsJson, tableJson, nullptr, false) {}
-
- ExtendedSettings(JSON settingsJson, JSON xTableJson, JSON yTableJson) :
- ExtendedSettings(settingsJson, xTableJson, yTableJson, true) {}
-
-private:
- ExtendedSettings(JSON settingsJson, JSON xTableJson, JSON yTableJson, bool byComponent)
+ virtual String^ ToString() override
{
- if (settingsJson) {
- baseSettings = RaConvert::Settings(settingsJson);
- }
- else {
- baseSettings = gcnew DriverSettings();
- }
-
- if (xTableJson || yTableJson) {
- baseSettings->combineMagnitudes = !byComponent;
- }
+ Text::StringBuilder^ sb = gcnew Text::StringBuilder();
- if (xTableJson) {
- tables.x = RaConvert::Table(xTableJson);
- tables.x->SetArgs(baseSettings->args.x);
- }
+ for each (auto elem in list) {
+ if (!single) {
+ sb->AppendFormat("profile: {0}\n", elem->prof->name);
+ }
- if (yTableJson) {
- if (Object::ReferenceEquals(yTableJson, xTableJson)) {
- tables.y = tables.x;
+ auto msgs = elem->messages;
+ if (elem->prof->combineMagnitudes) {
+ for (int i = 0; i < elem->lastX; i++) {
+ sb->AppendFormat("\t{0}\n", msgs[i]);
+ }
}
else {
- tables.y = RaConvert::Table(yTableJson);
+ for (int i = 0; i < elem->lastX; i++) {
+ sb->AppendFormat("\tx: {0}\n", msgs[i]);
+ }
+ for (int i = elem->lastX; i < elem->lastY; i++) {
+ sb->AppendFormat("\ty: {0}\n", msgs[i]);
+ }
}
- tables.y->SetArgs(baseSettings->args.y);
+ for (int i = elem->lastY; i < msgs->Length; i++) {
+ sb->AppendFormat("\t{0}\n", msgs[i]);
+ }
}
- }
+ return sb->ToString();
+ }
};
-public ref class SettingsErrors
+public ref class DeviceErrors
{
-public:
-
- List<String^>^ list;
- int lastX;
- int lastY;
+ List<String^>^ tmp;
+ bool single;
delegate void MsgHandler(const char*);
void Add(const char* msg)
{
- list->Add(gcnew String(msg));
+ tmp->Add(gcnew String(msg));
}
- SettingsErrors(ExtendedSettings^ settings) :
- SettingsErrors(settings->baseSettings) {}
+public:
+ ref struct SingleDeviceErrors
+ {
+ DeviceSettings^ settings;
+ array<String^>^ messages;
+ };
+
+ List<SingleDeviceErrors^>^ list;
- SettingsErrors(DriverSettings^ settings)
+ DeviceErrors(List<DeviceSettings^>^ devSettings)
{
- MsgHandler^ del = gcnew MsgHandler(this, &SettingsErrors::Add);
+ single = devSettings->Count == 1;
+ list = gcnew List<SingleDeviceErrors^>();
+ tmp = gcnew List<String^>();
+ MsgHandler^ del = gcnew MsgHandler(this, &DeviceErrors::Add);
GCHandle gch = GCHandle::Alloc(del);
auto fp = static_cast<void (*)(const char*)>(
Marshal::GetFunctionPointerForDelegate(del).ToPointer());
+ ra::device_settings* native_ptr = new ra::device_settings();
+
+ for each (auto dev in devSettings) {
+ Marshal::StructureToPtr(dev, IntPtr(native_ptr), false);
+ ra::valid(*native_ptr, fp);
+
+ if (tmp->Count != 0) {
+ auto singleErrors = gcnew SingleDeviceErrors();
+ singleErrors->messages = tmp->ToArray();
+ singleErrors->settings = dev;
+ list->Add(singleErrors);
+ tmp->Clear();
+ }
+ }
- ra::settings* args_ptr = new ra::settings();
- Marshal::StructureToPtr(settings, (IntPtr)args_ptr, false);
-
- list = gcnew List<String^>();
- auto [last_x, last_y, _] = ra::valid(*args_ptr, fp);
- lastX = last_x;
- lastY = last_y;
-
+ tmp = nullptr;
gch.Free();
- delete args_ptr;
+ delete native_ptr;
}
bool Empty()
@@ -503,36 +376,52 @@ public:
{
Text::StringBuilder^ sb = gcnew Text::StringBuilder();
- for each (auto s in list->GetRange(0, lastX))
- {
- sb->AppendFormat("x: {0}\n", s);
- }
- for each (auto s in list->GetRange(lastX, lastY - lastX))
- {
- sb->AppendFormat("y: {0}\n", s);
- }
- for each (auto s in list->GetRange(lastY, list->Count - lastY))
- {
- sb->AppendLine(s);
+ for each (auto elem in list) {
+ if (!single) {
+ sb->AppendFormat("device: {0}\n", elem->settings->id);
+ if (!String::IsNullOrWhiteSpace(elem->settings->name)) {
+ sb->AppendFormat(" name: {0}\n", elem->settings->name);
+ }
+ }
+
+ for each (auto msg in elem->messages) {
+ sb->AppendFormat("\tx: {0}\n", msg);
+ }
}
return sb->ToString();
}
};
-struct instance_t {
- ra::io_t data;
- vec2<ra::accel_invoker> inv;
+struct accel_instance_t {
+ ra::modifier mod;
+ ra::driver_settings settings;
+
+ accel_instance_t() = default;
+
+ accel_instance_t(ra::driver_settings& args) :
+ settings(args),
+ mod(args) {}
+
+ void init(Profile^ args)
+ {
+ Marshal::StructureToPtr(args, IntPtr(&settings.prof), false);
+ ra::init_data(settings);
+ mod = { settings };
+ }
};
public ref class ManagedAccel
{
- instance_t* const instance = new instance_t();
-
+ accel_instance_t* const instance = new accel_instance_t();
public:
+
ManagedAccel() {}
- ManagedAccel(ExtendedSettings^ settings)
+ ManagedAccel(ra::driver_settings& settings) :
+ instance(new accel_instance_t(settings)) {}
+
+ ManagedAccel(Profile^ settings)
{
Settings = settings;
}
@@ -547,95 +436,266 @@ public:
delete instance;
}
- Tuple<double, double>^ Accelerate(int x, int y, double time)
+ Tuple<double, double>^ Accelerate(int x, int y, double dpi_factor, double time)
{
vec2d in_out_vec = {
(double)x,
(double)y
};
- instance->data.mod.modify(in_out_vec, instance->inv, time);
+ instance->mod.modify(in_out_vec, instance->settings, dpi_factor, time);
return gcnew Tuple<double, double>(in_out_vec.x, in_out_vec.y);
}
+ property Profile^ Settings
+ {
+ Profile^ get()
+ {
+ return gcnew Profile(instance->settings.prof);
+ }
+
+ void set(Profile^ val)
+ {
+ instance->init(val);
+ }
+
+ }
+
+ ra::driver_settings GetSettings()
+ {
+ return instance->settings;
+ }
+
+};
+
+
+[JsonObject(ItemRequired = Required::Always)]
+public ref class DriverConfig {
+public:
+ literal double WriteDelayMs = ra::WRITE_DELAY;
+ literal int MaxProfiles = ra::DRIVER_CAPACITY;
+ literal int MaxDevices = ra::DEVICE_CAPACITY;
+ literal String^ Key = "Driver settings";
+
+ String^ version = RA_VER_STRING;
+
+ DeviceConfig defaultDeviceConfig;
+
+ List<Profile^>^ profiles;
+
+ [NonSerialized]
+ List<ManagedAccel^>^ accels;
+
+ List<DeviceSettings^>^ devices;
+
void Activate()
{
+ ra::io_t* data = static_cast<ra::io_t*>(malloc(sizeof(ra::io_t)));
+
+ if (!data) throw gcnew Exception("bad alloc");
+
+ data->default_dev_cfg.disable = defaultDeviceConfig.disable;
+ data->default_dev_cfg.set_extra_info = defaultDeviceConfig.setExtraInfo;
+ data->default_dev_cfg.dpi = defaultDeviceConfig.dpi;
+ data->default_dev_cfg.polling_rate = defaultDeviceConfig.pollingRate;
+ data->default_dev_cfg.clamp.min = defaultDeviceConfig.minimumTime;
+ data->default_dev_cfg.clamp.max = defaultDeviceConfig.maximumTime;
+
+ data->driver_data_size = profiles->Count;
+ data->device_data_size = devices->Count;
+
+ for (auto i = 0; i < profiles->Count; i++) {
+ auto& drv_settings = data->driver_data[i];
+ drv_settings = accels[i]->GetSettings();
+ }
+
+ for (auto i = 0; i < devices->Count; i++) {
+ auto& dev_settings = data->device_data[i];
+ Marshal::StructureToPtr(devices[i], IntPtr(&dev_settings), false);
+ }
+
try {
- ra::write(instance->data);
+ ra::write(*data);
+ free(data);
}
catch (const ra::error& e) {
+ free(data);
throw gcnew InteropException(e);
}
}
- property ExtendedSettings^ Settings
+ // returns null or a joined list of error messages
+ String^ Errors()
{
- ExtendedSettings^ get()
- {
- auto settings = gcnew ExtendedSettings();
- Marshal::PtrToStructure(IntPtr(&instance->data.args), settings->baseSettings);
- settings->tables.x = extract(instance->data.args.argsv.x.spaced_args.mode,
- instance->data.mod.accel.x);
- settings->tables.y = extract(instance->data.args.argsv.y.spaced_args.mode,
- instance->data.mod.accel.y);
- return settings;
+ Text::StringBuilder^ sb = gcnew Text::StringBuilder();
+
+ if (profiles->Count > MaxProfiles) {
+ sb->AppendFormat("Number of profiles ({0}) exceeds max ({1})\n", profiles->Count, MaxProfiles);
}
- void set(ExtendedSettings^ val)
- {
- Marshal::StructureToPtr(val->baseSettings, IntPtr(&instance->data.args), false);
- instance->data.mod = { instance->data.args };
- instance->inv = ra::invokers(instance->data.args);
+ if (devices->Count > MaxDevices) {
+ sb->AppendFormat("Number of devices ({0}) exceeds max ({1})\n", devices->Count, MaxDevices);
+ }
- if (val->tables.x) {
- val->tables.x->SetData(instance->data.mod.accel.x);
- }
+ ProfileErrors^ profErrors = gcnew ProfileErrors(profiles);
+ if (!profErrors->Empty()) {
+ sb->Append(profErrors->ToString());
+ }
+
+ DeviceSettings^ defaultDev = gcnew DeviceSettings();
+ defaultDev->config = defaultDeviceConfig;
+ defaultDev->id = "Default";
+ devices->Add(defaultDev);
- if (val->tables.y) {
- val->tables.y->SetData(instance->data.mod.accel.y);
- }
+ DeviceErrors^ devErrors = gcnew DeviceErrors(devices);
+ if (!devErrors->Empty()) {
+ sb->Append(profErrors->ToString());
}
+ devices->RemoveAt(devices->Count - 1);
+
+ if (sb->Length == 0) {
+ return nullptr;
+ }
+ else {
+ return sb->ToString();
+ }
}
- static ManagedAccel^ GetActive()
+ JObject^ ToJObject()
{
- try {
- auto active = gcnew ManagedAccel();
- ra::read(active->instance->data);
- active->instance->inv = ra::invokers(active->instance->data.args);
- return active;
+ auto dataQueue = gcnew Queue<array<float>^>();
+ auto empty = Array::Empty<float>();
+
+ for each (auto prof in profiles) {
+ if (prof->argsX.mode == AccelMode::lut) {
+ // data->Length is fixed for interop,
+ // temporary resize avoids serializing a bunch of zeros
+ Array::Resize(prof->argsX.data, prof->argsX.length);
+ }
+ else {
+ // table data may be used internally in any mode,
+ // so hide it when it's not needed for deserialization
+ dataQueue->Enqueue(prof->argsX.data);
+ prof->argsX.data = empty;
+ }
+
+ if (prof->argsY.mode == AccelMode::lut) {
+ Array::Resize(prof->argsY.data, prof->argsY.length);
+ }
+ else {
+ dataQueue->Enqueue(prof->argsY.data);
+ prof->argsY.data = empty;
+ }
}
- catch (const ra::error& e) {
- throw gcnew InteropException(e);
+
+ JObject^ jObject = JObject::FromObject(this);
+ String^ capModes = String::Join(" | ", Enum::GetNames(ClassicCapMode::typeid));
+ String^ accelModes = String::Join(" | ", Enum::GetNames(AccelMode::typeid));
+ jObject->AddFirst(gcnew JProperty("### Cap modes (applies to classic only) ###", capModes));
+ jObject->AddFirst(gcnew JProperty("### Accel modes ###", accelModes));
+
+ for each (auto prof in profiles) {
+ if (prof->argsX.mode == AccelMode::lut) {
+ Array::Resize(prof->argsX.data, ra::LUT_RAW_DATA_CAPACITY);
+ }
+ else {
+ prof->argsX.data = dataQueue->Dequeue();
+ }
+
+ if (prof->argsY.mode == AccelMode::lut) {
+ Array::Resize(prof->argsY.data, ra::LUT_RAW_DATA_CAPACITY);
+ }
+ else {
+ prof->argsY.data = dataQueue->Dequeue();
+ }
}
+
+ return jObject;
}
-private:
- LutBase^ extract(ra::spaced_lut_mode mode, ra::accel_union& au)
+ String^ ToJSON()
{
- switch (mode) {
- case ra::spaced_lut_mode::off: return nullptr;
- case ra::spaced_lut_mode::linear: return gcnew LinearLut(au.lin_lut);
- case ra::spaced_lut_mode::binlog: return gcnew BinLogLut(au.log_lut);
- default: throw gcnew NotImplementedException();
- }
+ return ToJObject()->ToString();
}
-};
-public ref struct VersionHelper
-{
- literal String^ VersionString = RA_VER_STRING;
+ // returns (config, null) or (null, error message)
+ static Tuple<DriverConfig^, String^>^ Convert(String^ json)
+ {
+ auto jss = gcnew JsonSerializerSettings();
+ jss->DefaultValueHandling = DefaultValueHandling::Populate;
+ auto cfg = JsonConvert::DeserializeObject<DriverConfig^>(json, jss);
+ if (cfg == nullptr) throw gcnew JsonException("invalid JSON");
- static Version^ ValidOrThrow()
+ auto message = cfg->Errors();
+ if (message != nullptr) {
+ return gcnew Tuple<DriverConfig^, String^>(nullptr, message);
+ }
+ else {
+ cfg->accels = gcnew List<ManagedAccel^>();
+
+ if (cfg->profiles->Count == 0) {
+ cfg->profiles->Add(gcnew Profile());
+ }
+
+ for each (auto prof in cfg->profiles) {
+ cfg->accels->Add(gcnew ManagedAccel(prof));
+ }
+ return gcnew Tuple<DriverConfig^, String^>(cfg, nullptr);
+ }
+ }
+
+ static DriverConfig^ GetActive()
{
+ ra::io_t* data = static_cast<ra::io_t*>(malloc(sizeof(ra::io_t)));
+
+ if (!data) throw gcnew Exception("io_t alloc failed");
+
try {
- ra::version_t v = ra::valid_version_or_throw();
- return gcnew Version(v.major, v.minor, v.patch, 0);
+ ra::read(*data);
}
catch (const ra::error& e) {
+ free(data);
throw gcnew InteropException(e);
+ }
+
+ auto cfg = gcnew DriverConfig();
+ cfg->profiles = gcnew List<Profile^>();
+ cfg->accels = gcnew List<ManagedAccel^>();
+ cfg->devices = gcnew List<DeviceSettings^>();
+
+ for (auto i = 0u; i < data->driver_data_size; i++) {
+ auto& drv_settings = data->driver_data[i];
+ cfg->profiles->Add(gcnew Profile(drv_settings.prof));
+ cfg->accels->Add(gcnew ManagedAccel(drv_settings));
}
+
+ for (auto i = 0u; i < data->device_data_size; i++) {
+ auto& dev_settings = data->device_data[i];
+ cfg->devices->Add(gcnew DeviceSettings(dev_settings));
+ }
+
+ cfg->defaultDeviceConfig.Init(data->default_dev_cfg);
+
+ free(data);
+ return cfg;
+ }
+
+ static DriverConfig^ GetDefault()
+ {
+ auto cfg = gcnew DriverConfig();
+ cfg->profiles = gcnew List<Profile^>();
+ cfg->accels = gcnew List<ManagedAccel^>();
+ cfg->devices = gcnew List<DeviceSettings^>();
+
+ cfg->profiles->Add(gcnew Profile());
+ cfg->accels->Add(gcnew ManagedAccel(default_driver_settings));
+ cfg->defaultDeviceConfig.Init(default_device_settings.config);
+ return cfg;
}
+
+private:
+ DriverConfig() {}
};
+
diff --git a/wrapper/wrapper.vcxproj b/wrapper/wrapper.vcxproj
index 5d58614..256b978 100644
--- a/wrapper/wrapper.vcxproj
+++ b/wrapper/wrapper.vcxproj
@@ -60,7 +60,8 @@
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
- <AdditionalDependencies>User32.lib;</AdditionalDependencies>
+ <AdditionalDependencies>
+ </AdditionalDependencies>
</Link>
<ResourceCompile>
<AdditionalIncludeDirectories>$(SolutionDir)/common;</AdditionalIncludeDirectories>
@@ -74,7 +75,8 @@
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
- <AdditionalDependencies>User32.lib;</AdditionalDependencies>
+ <AdditionalDependencies>
+ </AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy /Y "$(TargetPath)" "$(SolutionDir)signed\$(TargetFileName)" &amp;
@@ -98,6 +100,7 @@ copy /Y "$(TargetDir)Newtonsoft.Json.dll" "$(SolutionDir)signed\Newtonsoft.Json.
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
+ <Reference Include="System" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="wrapper.rc" />
diff --git a/writer/Program.cs b/writer/Program.cs
index 724fa23..83daed1 100644
--- a/writer/Program.cs
+++ b/writer/Program.cs
@@ -11,74 +11,16 @@ namespace writer
class Program
{
+ static readonly string DefaultPath = "settings.json";
+ static readonly string Usage =
+ $"Usage: {AppDomain.CurrentDomain.FriendlyName} <settings file path>\n";
- static void ExitWithMessage(string msg)
+ static void Exit(string msg)
{
MessageBox.Show(msg, "Raw Accel writer");
Environment.Exit(1);
}
- static void ExitWithUsage()
- {
- ExitWithMessage($"Usage: {System.AppDomain.CurrentDomain.FriendlyName} <settings file path>");
- }
-
- delegate string PopOption(params string[] aliases);
-
- static string Read(string path)
- {
- return path == null ? null : File.ReadAllText(path);
- }
-
- static ExtendedSettings Parse(List<string> args)
- {
- PopOption maybePop = aliases =>
- {
- int idx = args.FindIndex(aliases.Contains);
-
- if (idx == -1) return null;
-
- if (idx == args.Count - 1) ExitWithUsage();
-
- string val = args[idx + 1];
- args.RemoveRange(idx, 2);
- return val;
- };
-
- string settingsPath = null;
-
- string tablePath = maybePop("/table", "/t");
-
- if (tablePath != null)
- {
- if (args.Count > 1) ExitWithUsage();
- else if (args.Count == 1) settingsPath = args[0];
-
- return new ExtendedSettings(Read(settingsPath), Read(tablePath));
- }
-
- string xTablePath = maybePop("/xtable", "/xt");
- string yTablePath = maybePop("/ytable", "/yt");
-
- if (args.Count > 1) ExitWithUsage();
- else if (args.Count == 1) settingsPath = args[0];
- else if (xTablePath == null && yTablePath == null) ExitWithUsage();
-
- string xTableJson = Read(xTablePath);
- string yTableJson = null;
-
- if (xTablePath != null && xTablePath.Equals(yTablePath))
- {
- yTableJson = xTableJson;
- }
- else
- {
- yTableJson = Read(yTablePath);
- }
-
- return new ExtendedSettings(Read(settingsPath), xTableJson, yTableJson);
- }
-
static void Main(string[] args)
{
try
@@ -87,34 +29,47 @@ namespace writer
}
catch (InteropException e)
{
- ExitWithMessage(e.Message);
+ Exit(e.Message);
}
try
{
- var settings = Parse(new List<string>(args));
- var errors = new SettingsErrors(settings);
-
- if (errors.Empty())
+ if (args.Length != 1)
{
- new ManagedAccel(settings).Activate();
+ if (File.Exists(DefaultPath))
+ {
+ Exit(Usage);
+ }
+ else
+ {
+ File.WriteAllText(DefaultPath, DriverConfig.GetDefault().ToJSON());
+ Exit($"{Usage}\n(generated default settings file '{DefaultPath}')");
+ }
}
else
{
- ExitWithMessage($"Bad settings:\n\n{errors}");
+ var result = DriverConfig.Convert(File.ReadAllText(args[0]));
+ if (result.Item2 == null)
+ {
+ result.Item1.Activate();
+ }
+ else
+ {
+ Exit($"Bad settings:\n\n{result.Item2}");
+ }
}
}
- catch (System.IO.FileNotFoundException e)
+ catch (FileNotFoundException e)
{
- ExitWithMessage(e.Message);
+ Exit(e.Message);
}
catch (JsonException e)
{
- ExitWithMessage($"Settings invalid:\n\n{e.Message}");
+ Exit($"Settings format invalid:\n\n{e.Message}");
}
catch (Exception e)
{
- ExitWithMessage($"Error:\n\n{e}");
+ Exit($"Error:\n\n{e}");
}
}
}