diff options
Diffstat (limited to 'wrapper/wrapper.cpp')
| -rw-r--r-- | wrapper/wrapper.cpp | 740 |
1 files changed, 451 insertions, 289 deletions
diff --git a/wrapper/wrapper.cpp b/wrapper/wrapper.cpp index 50a3596..6344b77 100644 --- a/wrapper/wrapper.cpp +++ b/wrapper/wrapper.cpp @@ -1,51 +1,53 @@ #pragma once -#include <type_traits> -#include <msclr\marshal_cppstd.h> +#include "interop-exception.h" -#include <rawaccel.hpp> -#include <rawaccel-version.h> -#include <utility-rawinput.hpp> - -#include "wrapper_io.hpp" +#include <rawaccel-io.hpp> +#include <rawaccel-validate.hpp> using namespace System; using namespace System::Collections::Generic; using namespace System::Runtime::InteropServices; using namespace System::Reflection; +using namespace System::Runtime::Serialization; +using namespace Newtonsoft::Json; +using namespace Newtonsoft::Json::Linq; -using namespace Windows::Forms; +namespace ra = rawaccel; -using namespace Newtonsoft::Json; +ra::settings default_settings; [JsonConverter(Converters::StringEnumConverter::typeid)] public enum class AccelMode { - linear, classic, natural, naturalgain, power, motivity, noaccel + classic, jump, natural, motivity, power, lut, noaccel +}; + +[JsonConverter(Converters::StringEnumConverter::typeid)] +public enum class SpacedTableMode +{ + off, binlog, linear }; -[JsonObject(ItemRequired = Required::Always)] [StructLayout(LayoutKind::Sequential)] -public value struct AccelArgs +public value struct SpacedTableArgs { - double offset; + [JsonIgnore] + SpacedTableMode mode; + [MarshalAs(UnmanagedType::U1)] - bool legacyOffset; - double acceleration; - double scale; - double limit; - double exponent; - double midpoint; - double weight; - [JsonProperty("legacyCap")] - double scaleCap; - double gainCap; - [JsonProperty(Required = Required::Default)] - double speedCap; + bool transfer; + + [MarshalAs(UnmanagedType::U1)] + unsigned char partitions; + + short num; + double start; + double stop; }; + generic <typename T> -[JsonObject(ItemRequired = Required::Always)] [StructLayout(LayoutKind::Sequential)] public value struct Vec2 { @@ -53,7 +55,78 @@ public value struct Vec2 T y; }; -[JsonObject(ItemRequired = Required::Always)] +[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 +{ + AccelMode mode; + + [MarshalAs(UnmanagedType::U1)] + bool legacy; + + double offset; + double cap; + double accelClassic; + double decayRate; + double growthRate; + double motivity; + double power; + double scale; + double weight; + double exponent; + double limit; + double midpoint; + double smooth; + + [JsonProperty(Required = Required::Default)] + SpacedTableArgs spacedTableArgs; + + TableArgs tableData; +}; + [StructLayout(LayoutKind::Sequential)] public value struct DomainArgs { @@ -65,20 +138,25 @@ public value struct DomainArgs [StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)] public ref struct DriverSettings { + literal double WriteDelayMs = ra::WRITE_DELAY; literal String^ Key = "Driver settings"; [JsonProperty("Degrees of rotation")] double rotation; - [JsonProperty("Degrees of angle snapping", Required = Required::Default)] + [JsonProperty("Degrees of angle snapping")] double snap; [JsonProperty("Use x as whole/combined accel")] [MarshalAs(UnmanagedType::U1)] bool combineMagnitudes; - [JsonProperty("Accel modes")] - Vec2<AccelMode> modes; + double dpi; + + [JsonIgnore] + double minimumSpeed; + [JsonProperty("Input Speed Cap")] + double maximumSpeed; [JsonProperty("Accel parameters")] Vec2<AccelArgs> args; @@ -86,302 +164,387 @@ public ref struct DriverSettings [JsonProperty("Sensitivity multipliers")] Vec2<double> sensitivity; - [JsonProperty("Negative directional multipliers", Required = Required::Default)] + [JsonProperty("Negative directional multipliers")] Vec2<double> directionalMultipliers; - [JsonProperty("Stretches domain for horizontal vs vertical inputs", Required = Required::Default)] + [JsonProperty("Stretches domain for horizontal vs vertical inputs")] DomainArgs domainArgs; - [JsonProperty("Stretches accel range for horizontal vs vertical inputs", Required = Required::Default)] + [JsonProperty("Stretches accel range for horizontal vs vertical inputs")] Vec2<double> rangeXY; [JsonProperty(Required = Required::Default)] double minimumTime; - [JsonProperty("Device ID", Required = Required::Default)] - [MarshalAs(UnmanagedType::ByValTStr, SizeConst = MAX_DEV_ID_LEN)] + [JsonProperty(Required = Required::Default)] + double maximumTime; + + [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 = ""; bool ShouldSerializeminimumTime() { - return minimumTime > 0 && minimumTime != DEFAULT_TIME_MIN; + return minimumTime != ra::DEFAULT_TIME_MIN; + } + + bool ShouldSerializemaximumTime() + { + return maximumTime != ra::DEFAULT_TIME_MAX; } DriverSettings() { - domainArgs = { { 1, 1 }, 2 }; - rangeXY = { 1, 1 }; + Marshal::PtrToStructure(IntPtr(&default_settings), this); } -}; +private: + [OnDeserialized] + void OnDeserializedMethod(StreamingContext context) + { + args.x.tableData.length = args.x.tableData.points->Length; + args.y.tableData.length = args.y.tableData.points->Length; -template <typename NativeSettingsFunc> -void as_native(DriverSettings^ managed_args, NativeSettingsFunc fn) -{ -#ifndef NDEBUG - if (Marshal::SizeOf(managed_args) != sizeof(settings)) - throw gcnew InvalidOperationException("setting sizes differ"); -#endif - settings args; - Marshal::StructureToPtr(managed_args, (IntPtr)&args, false); - fn(args); - if constexpr (!std::is_invocable_v<NativeSettingsFunc, const settings&>) { - Marshal::PtrToStructure((IntPtr)&args, managed_args); - } -} - -DriverSettings^ get_default() -{ - DriverSettings^ managed = gcnew DriverSettings(); - as_native(managed, [](settings& args) { - args = {}; - }); - return managed; -} - -void set_active(DriverSettings^ managed) -{ - as_native(managed, [](const settings& args) { - wrapper_io::writeToDriver(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); + } -DriverSettings^ get_active() -{ - DriverSettings^ managed = gcnew DriverSettings(); - as_native(managed, [](settings& args) { - wrapper_io::readFromDriver(args); - }); - return managed; -} - -void update_modifier(mouse_modifier& mod, DriverSettings^ managed, vec2<si_pair*> luts = {}) -{ - as_native(managed, [&](const settings& args) { - mod = { args, luts }; - }); -} + [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); + } + + [OnSerialized] + void OnSerializedMethod(StreamingContext context) + { + array<Vec2<float>>::Resize(args.x.tableData.points, ra::ARB_LUT_CAPACITY); + array<Vec2<float>>::Resize(args.y.tableData.points, ra::ARB_LUT_CAPACITY); + } -using error_list_t = Collections::Generic::List<String^>; +}; -error_list_t^ get_accel_errors(AccelMode mode, AccelArgs^ args) +[JsonObject(ItemRequired = Required::Always)] +public ref struct LutBase { - accel_mode mode_native = (accel_mode)mode; - - auto is_mode = [mode_native](auto... modes) { - return ((mode_native == modes) || ...); - }; - - using am = accel_mode; + [JsonConverter(Converters::StringEnumConverter::typeid)] + enum class Mode + { + logarithmic, linear + } mode; - auto error_list = gcnew error_list_t(); - - if (args->acceleration > 10 && is_mode(am::natural, am::naturalgain)) - error_list->Add("acceleration can not be greater than 10"); - else if (args->acceleration == 0 && is_mode(am::naturalgain)) - error_list->Add("acceleration must be positive"); - else if (args->acceleration < 0) { - bool additive = mode_native < am::power; - if (additive) error_list->Add("acceleration can not be negative, use a negative weight to compensate"); - else error_list->Add("acceleration can not be negative"); - } - - if (args->scale <= 0) - error_list->Add("scale must be positive"); + virtual void SetArgs(AccelArgs%) {} + virtual void SetData(ra::accel_union&) {} +}; - if (args->exponent <= 1 && is_mode(am::classic)) - error_list->Add("exponent must be greater than 1"); - else if (args->exponent <= 0) - error_list->Add("exponent must be positive"); - if (args->limit <= 1) - error_list->Add("limit must be greater than 1"); +[JsonObject(ItemRequired = Required::Always)] +public ref struct SpacedLut abstract : public LutBase +{ + [JsonProperty("Whether points affect velocity (true) or sensitivity (false)")] + bool transfer; - if (args->midpoint <= 0) - error_list->Add("midpoint must be positive"); + double start; + double stop; + array<float>^ data; - if (args->offset < 0) - error_list->Add("offset can not be negative"); + void SetArgsBase(AccelArgs% args) + { + args.spacedTableArgs.transfer = transfer; + args.spacedTableArgs.start = start; + args.spacedTableArgs.stop = stop; + } - return error_list; -} + void SetDataBase(ra::accel_union& accel) + { + if (size_t(data->LongLength) > ra::SPACED_LUT_CAPACITY) { + throw gcnew InteropException("data is too large"); + } + } +}; -error_list_t^ get_other_errors(DriverSettings^ settings) +[JsonObject(ItemRequired = Required::Always)] +public ref struct LinearLut sealed : public SpacedLut { - auto error_list = gcnew error_list_t(); + LinearLut() + { + } - if (settings->rangeXY.x <= 0 || settings->rangeXY.y <= 0) + LinearLut(const ra::linear_lut& table) { - error_list->Add("range values must be positive"); + 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); } - if (settings->domainArgs.domainXY.x <= 0 || settings->domainArgs.domainXY.y <= 0) + virtual void SetArgs(AccelArgs% args) override { - error_list->Add("domain values must be positive"); + SetArgsBase(args); + args.spacedTableArgs.num = data->Length; + args.spacedTableArgs.mode = SpacedTableMode::linear; } - if (settings->domainArgs.lpNorm <= 0) + virtual void SetData(ra::accel_union& accel) override { - error_list->Add("lp norm must be positive"); + SetDataBase(accel); + pin_ptr<float> pdata = &data[0]; + std::memcpy(&accel.lin_lut.data, pdata, sizeof(float) * data->Length); } - - return error_list; -} +}; -public ref class SettingsErrors +[JsonObject(ItemRequired = Required::Always)] +public ref struct BinLogLut sealed : public SpacedLut { -public: - error_list_t^ x; - error_list_t^ y; - error_list_t^ other; + short num; - bool Empty() + BinLogLut() { - return (x == nullptr || x->Count == 0) && - (y == nullptr || y->Count == 0) && - (other == nullptr || other->Count == 0); } - virtual String^ ToString() override + BinLogLut(const ra::binlog_lut& table) { - if (x == nullptr) throw; + 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))); + + pin_ptr<float> pdata = &data[0]; + std::memcpy(pdata, &table.data, sizeof(float) * data->Length); + } - Text::StringBuilder^ sb = gcnew Text::StringBuilder(); + virtual void SetArgs(AccelArgs% args) override + { + SetArgsBase(args); + args.spacedTableArgs.num = num; + args.spacedTableArgs.mode = SpacedTableMode::binlog; + } - if (y == nullptr) // assume combineMagnitudes - { - for each (String^ str in x) - { - sb->AppendLine(str); - } - } - else - { - for each (String^ str in x) - { - sb->AppendFormat("x: {0}\n", str); - } - for each (String^ str in y) - { - sb->AppendFormat("y: {0}\n", str); - } - } + virtual void SetData(ra::accel_union& accel) override + { + SetDataBase(accel); - for each (String ^ str in other) - { - sb->AppendLine(str); + if (data->Length != 1 + num * (int(stop) - int(start))) { + throw gcnew InteropException("size of data does not match args"); } - - return sb->ToString(); + + pin_ptr<float> pdata = &data[0]; + std::memcpy(&accel.log_lut.data, pdata, sizeof(float) * data->Length); } }; -public ref struct RawInputInterop -{ - static void AddHandlesFromID(String^ deviceID, List<IntPtr>^ rawInputHandles) +public ref struct RaConvert { + + static DriverSettings^ Settings(String^ json) { - try - { - std::vector<HANDLE> nativeHandles = rawinput_handles_from_dev_id( - msclr::interop::marshal_as<std::wstring>(deviceID)); + return NonNullable<DriverSettings^>(json); + } + + 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); + } + + static LutBase^ Table(String^ json) + { + JObject^ jObject = JObject::Parse(json); - for (auto nh : nativeHandles) rawInputHandles->Add(IntPtr(nh)); + if ((Object^)jObject == nullptr) { + throw gcnew JsonException("bad json"); } - catch (const std::exception& e) - { - throw gcnew System::Exception(gcnew String(e.what())); + + 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(); } } - static List<String^>^ GetDeviceIDs() + static String^ Table(LutBase^ lut) { - try - { - auto managed = gcnew List<String^>(); + auto serializerSettings = gcnew JsonSerializerSettings(); + return JsonConvert::SerializeObject( + lut, lut->GetType(), Formatting::Indented, serializerSettings); + }; - for (auto&& id : rawinput_dev_id_list()) - { - managed->Add(msclr::interop::marshal_as<String^>(id)); - } + generic <typename T> + static T NonNullable(String^ json) + { + T obj = JsonConvert::DeserializeObject<T>(json); + if (obj == nullptr) throw gcnew JsonException("invalid JSON"); + return obj; + } + + generic <typename T> + static T NonNullable(JObject^ jObject) + { + T obj = jObject->ToObject<T>(); + if (obj == nullptr) throw gcnew JsonException("invalid JSON"); + return obj; + } +}; + +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) {} - return managed; + ExtendedSettings(JSON settingsJson, JSON xTableJson, JSON yTableJson) : + ExtendedSettings(settingsJson, xTableJson, yTableJson, true) {} + +private: + ExtendedSettings(JSON settingsJson, JSON xTableJson, JSON yTableJson, bool byComponent) + { + if (settingsJson) { + baseSettings = RaConvert::Settings(settingsJson); } - catch (const std::exception& e) - { - throw gcnew System::Exception(gcnew String(e.what())); + else { + baseSettings = gcnew DriverSettings(); + } + + if (xTableJson || yTableJson) { + baseSettings->combineMagnitudes = !byComponent; + } + + if (xTableJson) { + tables.x = RaConvert::Table(xTableJson); + tables.x->SetArgs(baseSettings->args.x); + } + + if (yTableJson) { + if (Object::ReferenceEquals(yTableJson, xTableJson)) { + tables.y = tables.x; + } + else { + tables.y = RaConvert::Table(yTableJson); + } + + tables.y->SetArgs(baseSettings->args.y); } } }; -public ref struct DriverInterop +public ref class SettingsErrors { - literal double WriteDelayMs = WRITE_DELAY; - static initonly DriverSettings^ DefaultSettings = get_default(); +public: - static DriverSettings^ GetActiveSettings() - { - return get_active(); - } + List<String^>^ list; + int countX; + int countY; - static void Write(DriverSettings^ args) - { - set_active(args); - } + delegate void MsgHandler(const char*); - static DriverSettings^ GetDefaultSettings() + void Add(const char* msg) { - return get_default(); + list->Add(gcnew String(msg)); } - static SettingsErrors^ GetSettingsErrors(DriverSettings^ args) + SettingsErrors(ExtendedSettings^ settings) : + SettingsErrors(settings->baseSettings) {} + + SettingsErrors(DriverSettings^ settings) { - auto errors = gcnew SettingsErrors(); + MsgHandler^ del = gcnew MsgHandler(this, &SettingsErrors::Add); + GCHandle gch = GCHandle::Alloc(del); + auto fp = static_cast<void (*)(const char*)>( + Marshal::GetFunctionPointerForDelegate(del).ToPointer()); - errors->x = get_accel_errors(args->modes.x, args->args.x); + ra::settings* args_ptr = new ra::settings(); + Marshal::StructureToPtr(settings, (IntPtr)args_ptr, false); - if (!args->combineMagnitudes) { - errors->y = get_accel_errors(args->modes.y, args->args.y); - } + list = gcnew List<String^>(); + auto [cx, cy, _] = ra::valid(*args_ptr, fp); + countX = cx; + countY = cy; - errors->other = get_other_errors(args); + gch.Free(); + delete args_ptr; + } - return errors; + bool Empty() + { + return list->Count == 0; } + virtual String^ ToString() override + { + Text::StringBuilder^ sb = gcnew Text::StringBuilder(); + for each (auto s in list->GetRange(0, countX)) + { + sb->AppendFormat("x: {0}\n", s); + } + for each (auto s in list->GetRange(countX, countY)) + { + sb->AppendFormat("y: {0}\n", s); + } + for each (auto s in list->GetRange(countY, list->Count)) + { + sb->AppendLine(s); + } - static error_list_t^ GetAccelErrors(AccelMode mode, AccelArgs^ args) - { - return get_accel_errors(mode, args); + return sb->ToString(); } }; +struct instance_t { + ra::io_t data; + vec2<ra::accel_invoker> inv; +}; + public ref class ManagedAccel { - mouse_modifier* const modifier_instance = new mouse_modifier(); -#ifdef RA_LOOKUP - si_pair* const lut_x = new si_pair[LUT_SIZE]; - si_pair* const lut_y = new si_pair[LUT_SIZE]; -#else - si_pair* lut_x = nullptr; - si_pair* lut_y = nullptr; -#endif + instance_t* const instance = new instance_t(); public: + ManagedAccel() {} + + ManagedAccel(ExtendedSettings^ settings) + { + Settings = settings; + } virtual ~ManagedAccel() { - delete modifier_instance; - delete[] lut_x; - delete[] lut_y; + delete instance; } !ManagedAccel() { - delete modifier_instance; - delete[] lut_x; - delete[] lut_y; + delete instance; } Tuple<double, double>^ Accelerate(int x, int y, double time) @@ -390,90 +553,89 @@ public: (double)x, (double)y }; - modifier_instance->modify(in_out_vec, time); + + instance->data.mod.modify(in_out_vec, instance->inv, time); return gcnew Tuple<double, double>(in_out_vec.x, in_out_vec.y); } - void UpdateFromSettings(DriverSettings^ args) + void Activate() { - update_modifier( - *modifier_instance, - args, - vec2<si_pair*>{ lut_x, lut_y } - ); + try { + ra::write(instance->data); + } + catch (const ra::error& e) { + throw gcnew InteropException(e); + } } - static ManagedAccel^ GetActiveAccel() + property ExtendedSettings^ Settings { - settings args; - wrapper_io::readFromDriver(args); - - auto active = gcnew ManagedAccel(); - *active->modifier_instance = { - args - , vec2<si_pair*> { active->lut_x, active->lut_y } - }; - return active; - } -}; - -public ref struct RawAccelVersion -{ - literal String^ value = RA_VER_STRING; -}; - -public ref struct VersionException : public Exception -{ -public: - VersionException() {} - VersionException(String^ what) : Exception(what) {} -}; - -Version^ convert(rawaccel::version_t v) -{ - return gcnew Version(v.major, v.minor, v.patch, 0); -} + 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; + } -public ref struct VersionHelper -{ + 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); - static Version^ ValidateAndGetDriverVersion(Version^ wrapperTarget) - { - Version^ wrapperActual = VersionHelper::typeid->Assembly->GetName()->Version; + if (val->tables.x) { + val->tables.x->SetData(instance->data.mod.accel.x); + } - if (wrapperTarget != wrapperActual) { - throw gcnew VersionException("version mismatch, expected wrapper.dll v" + wrapperActual); + if (val->tables.y) { + val->tables.y->SetData(instance->data.mod.accel.y); + } } - version_t drv_ver; + } + static ManagedAccel^ GetActive() + { try { - wrapper_io::getDriverVersion(drv_ver); + auto active = gcnew ManagedAccel(); + ra::read(active->instance->data); + active->instance->inv = ra::invokers(active->instance->data.args); + return active; } - catch (DriverNotInstalledException^ ex) { - throw gcnew VersionException(ex->Message); + catch (const ra::error& e) { + throw gcnew InteropException(e); } - catch (DriverIOException^) { - // Assume version ioctl is unimplemented (driver version < v1.3.0) - throw gcnew VersionException("driver version is out of date\n\nrun installer.exe to reinstall"); + } + +private: + LutBase^ extract(ra::spaced_lut_mode mode, ra::accel_union& au) + { + 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(); } + } +}; - Version^ drv_ver_managed = convert(drv_ver); +public ref struct VersionHelper +{ + literal String^ VersionString = RA_VER_STRING; - if (drv_ver_managed < convert(min_driver_version)) { - throw gcnew VersionException( - String::Format("driver version is out of date (v{0})\n\nrun installer.exe to reinstall", - drv_ver_managed)); - } - else if (drv_ver_managed > wrapperActual) { - throw gcnew VersionException( - String::Format("newer driver version is installed (v{0})", - drv_ver_managed)); + static Version^ ValidOrThrow() + { + try { + ra::version_t v = ra::valid_version_or_throw(); + return gcnew Version(v.major, v.minor, v.patch, 0); } - else { - return drv_ver_managed; + catch (const ra::error& e) { + throw gcnew InteropException(e); } } - }; |