diff options
| -rw-r--r-- | common/accel-classic.hpp | 133 | ||||
| -rw-r--r-- | common/accel-invoke.hpp | 52 | ||||
| -rw-r--r-- | common/accel-jump.hpp | 18 | ||||
| -rw-r--r-- | common/accel-lookup.hpp | 139 | ||||
| -rw-r--r-- | common/accel-motivity.hpp | 79 | ||||
| -rw-r--r-- | common/accel-natural.hpp | 16 | ||||
| -rw-r--r-- | common/accel-noaccel.hpp | 2 | ||||
| -rw-r--r-- | common/accel-power.hpp | 66 | ||||
| -rw-r--r-- | common/accel-union.hpp | 123 | ||||
| -rw-r--r-- | common/common.vcxitems | 1 | ||||
| -rw-r--r-- | common/rawaccel-base.hpp | 84 | ||||
| -rw-r--r-- | common/rawaccel-validate.hpp | 150 | ||||
| -rw-r--r-- | common/rawaccel.hpp | 221 | ||||
| -rw-r--r-- | common/utility.hpp | 6 | ||||
| -rw-r--r-- | driver/driver.cpp | 299 | ||||
| -rw-r--r-- | driver/driver.h | 22 | ||||
| -rw-r--r-- | wrapper/input.cpp | 119 | ||||
| -rw-r--r-- | wrapper/input.h | 110 | ||||
| -rw-r--r-- | wrapper/wrapper.cpp | 874 | ||||
| -rw-r--r-- | wrapper/wrapper.vcxproj | 7 | ||||
| -rw-r--r-- | writer/Program.cs | 101 |
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)" & @@ -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}"); } } } |