#pragma once #include "interop-exception.h" #include #include 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; namespace ra = rawaccel; ra::modifier_settings default_modifier_settings; ra::device_settings default_device_settings; public ref struct VersionHelper { 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 AccelMode { classic, jump, natural, synchronous, power, lut, noaccel }; [JsonConverter(Converters::StringEnumConverter::typeid)] public enum class CapMode { in_out, input, output }; generic [StructLayout(LayoutKind::Sequential)] public value struct Vec2 { T x; T y; }; [StructLayout(LayoutKind::Sequential)] public value struct AccelArgs { literal int MaxLutPoints = ra::LUT_POINTS_CAPACITY; AccelMode mode; [JsonProperty("Gain / Velocity")] [MarshalAs(UnmanagedType::U1)] bool gain; double inputOffset; double outputOffset; double acceleration; double decayRate; double gamma; double motivity; double exponentClassic; double scale; double exponentPower; double limit; double syncSpeed; double smooth; [JsonProperty("Cap / Jump")] Vec2 cap; [JsonProperty("Cap mode")] CapMode capMode; [JsonIgnore] int length; [MarshalAs(UnmanagedType::ByValArray, SizeConst = ra::LUT_RAW_DATA_CAPACITY)] array^ data; [OnDeserialized] void OnDeserializedMethod(StreamingContext context) { // data->Length must match SizeConst when marshalling length = data->Length; array::Resize(data, ra::LUT_RAW_DATA_CAPACITY); } }; [StructLayout(LayoutKind::Sequential)] public value struct SpeedArgs { [JsonProperty("Whole/combined accel (set false for 'by component' mode)")] [MarshalAs(UnmanagedType::U1)] bool combineMagnitudes; double lpNorm; [JsonProperty("Time in ms after which an input is weighted at half its original value.")] double inputSmoothHalflife; [JsonProperty("Time in ms after which scale is weighted at half its original value.")] double scaleSmoothHalflife; [JsonProperty("Time in ms after which an output is weighted at half its original value.")] double outputSmoothHalflife; }; [JsonObject(ItemRequired = Required::Always)] [StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)] public ref struct Profile { [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)] System::String^ name; [JsonProperty("Stretches domain for horizontal vs vertical inputs")] Vec2 domainXY; [JsonProperty("Stretches accel range for horizontal vs vertical inputs")] Vec2 rangeXY; [JsonProperty("Whole or horizontal accel parameters")] AccelArgs argsX; [JsonProperty("Vertical accel parameters")] AccelArgs argsY; [JsonProperty("Input speed calculation parameters")] SpeedArgs inputSpeedArgs; [JsonProperty("Output DPI")] double outputDPI; [JsonProperty("Y/X output DPI ratio (vertical sens multiplier)")] double yxOutputDPIRatio; [JsonProperty("L/R output DPI ratio (left sens multiplier)")] double lrOutputDPIRatio; [JsonProperty("U/D output DPI ratio (up sens multiplier)")] double udOutputDPIRatio; [JsonProperty("Degrees of rotation")] double rotation; [JsonProperty("Degrees of angle snapping")] double snap; [JsonIgnore] double minimumSpeed; [JsonProperty("Input Speed Cap")] double maximumSpeed; Profile(ra::profile& args) { Marshal::PtrToStructure(IntPtr(&args), this); } Profile() : Profile(default_modifier_settings.prof) {} }; [JsonObject(ItemRequired = Required::Always)] [StructLayout(LayoutKind::Sequential)] public value struct DeviceConfig { [MarshalAs(UnmanagedType::U1)] bool disable; [MarshalAs(UnmanagedType::U1)] [JsonProperty(Required = Required::Default)] bool setExtraInfo; [MarshalAs(UnmanagedType::U1)] [JsonProperty("Use constant time interval based on polling rate", Required = Required::Default)] bool pollTimeLock; [JsonProperty("DPI (normalizes input speed unit: counts/ms -> in/s)")] int dpi; [JsonProperty("Polling rate Hz (keep at 0 for automatic adjustment)")] int pollingRate; [ComponentModel::DefaultValue(ra::DEFAULT_TIME_MIN)] [JsonProperty(Required = Required::Default)] double minimumTime; [ComponentModel::DefaultValue(ra::DEFAULT_TIME_MAX)] [JsonProperty(Required = Required::Default)] double maximumTime; bool ShouldSerializesetExtraInfo() { return setExtraInfo == true; } bool ShouldSerializeminimumTime() { return minimumTime != ra::DEFAULT_TIME_MIN; } bool ShouldSerializemaximumTime() { return maximumTime != ra::DEFAULT_TIME_MAX; } void Init(const ra::device_config& cfg) { disable = cfg.disable; setExtraInfo = cfg.set_extra_info; pollTimeLock = cfg.poll_time_lock; dpi = cfg.dpi; pollingRate = cfg.polling_rate; minimumTime = cfg.clamp.min; maximumTime = cfg.clamp.max; } }; [JsonObject(ItemRequired = Required::Always)] [StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)] public ref struct DeviceSettings { [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)] String^ name; [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)] String^ profile; [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_DEV_ID_LEN)] String^ id; DeviceConfig config; DeviceSettings(ra::device_settings& args) { Marshal::PtrToStructure(IntPtr(&args), this); } DeviceSettings() : DeviceSettings(default_device_settings) {} }; public ref class ProfileErrors { List^ tmp; bool single; delegate void MsgHandler(const char*); void Add(const char* msg) { tmp->Add(gcnew String(msg)); } public: ref struct SingleProfileErrors { Profile^ prof; array^ messages; int lastX; int lastY; }; List^ list; ProfileErrors(List^ profiles) { single = profiles->Count == 1; list = gcnew List(); tmp = gcnew List(); MsgHandler^ del = gcnew MsgHandler(this, &ProfileErrors::Add); GCHandle gch = GCHandle::Alloc(del); auto fp = static_cast( 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; } bool Empty() { return list->Count == 0; } virtual String^ ToString() override { Text::StringBuilder^ sb = gcnew Text::StringBuilder(); for each (auto elem in list) { if (!single) { sb->AppendFormat("profile: {0}\n", elem->prof->name); } auto msgs = elem->messages; if (elem->prof->inputSpeedArgs.combineMagnitudes) { for (int i = 0; i < elem->lastX; i++) { sb->AppendFormat("\t{0}\n", msgs[i]); } } else { 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]); } } for (int i = elem->lastY; i < msgs->Length; i++) { sb->AppendFormat("\t{0}\n", msgs[i]); } } return sb->ToString(); } }; public ref class DeviceErrors { List^ tmp; bool single; delegate void MsgHandler(const char*); void Add(const char* msg) { tmp->Add(gcnew String(msg)); } public: ref struct SingleDeviceErrors { DeviceSettings^ settings; array^ messages; }; List^ list; DeviceErrors(List^ devSettings) { single = devSettings->Count == 1; list = gcnew List(); tmp = gcnew List(); MsgHandler^ del = gcnew MsgHandler(this, &DeviceErrors::Add); GCHandle gch = GCHandle::Alloc(del); auto fp = static_cast( 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(); } } tmp = nullptr; gch.Free(); delete native_ptr; } bool Empty() { return list->Count == 0; } virtual String^ ToString() override { Text::StringBuilder^ sb = gcnew Text::StringBuilder(); 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 accel_instance_t { ra::modifier mod; ra::modifier_settings settings; accel_instance_t() = default; accel_instance_t(ra::modifier_settings& args) : settings(args), mod(args) {} void init(Profile^ args) { Marshal::StructureToPtr(args, IntPtr(&settings.prof), false); ra::init_data(settings); mod = { settings }; } }; struct speed_calc_instance_t { ra::speed_processor speed_calculator = {}; }; public ref class ManagedAccel { accel_instance_t* const instance = new accel_instance_t(); speed_calc_instance_t* const speed_instance = new speed_calc_instance_t(); public: ManagedAccel() {} ManagedAccel(ra::modifier_settings& settings) : instance(new accel_instance_t(settings)) {} ManagedAccel(Profile^ settings) { Settings = settings; } virtual ~ManagedAccel() { delete instance; } !ManagedAccel() { delete instance; } /// /// The modifier inside of ManagedAccel is now stateful, which can result in strange graphs when used for graphing purposes. /// This method creates a copy of its instance with any stateful elements turned off. /// /// ManagedAccel^ CreateStatelessCopy() { Profile^ profile = Settings; profile->inputSpeedArgs.inputSmoothHalflife = 0; profile->inputSpeedArgs.scaleSmoothHalflife = 0; profile->inputSpeedArgs.outputSmoothHalflife = 0; return gcnew ManagedAccel(profile); } Tuple^ Accelerate(int x, int y, double dpi_factor, double time) { vec2d in_out_vec = { (double)x, (double)y }; instance->mod.modify(in_out_vec, speed_instance->speed_calculator, instance->settings, dpi_factor, time); return gcnew Tuple(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::modifier_settings NativeSettings() { return instance->settings; } }; public ref class SpeedCalculatorArgs { public: SpeedCalculatorArgs() {} SpeedCalculatorArgs( double lp_norm, double input_speed_smooth_halflife, double scale_smooth_halflife, double output_speed_smooth_halflife) { speed_args->lp_norm = lp_norm; speed_args->input_speed_smooth_halflife = input_speed_smooth_halflife; speed_args->scale_smooth_halflife = scale_smooth_halflife; speed_args->output_speed_smooth_halflife = output_speed_smooth_halflife; } ra::speed_args* const speed_args = new ra::speed_args(); }; public ref class SpeedCalculator { speed_calc_instance_t* const instance = new speed_calc_instance_t(); public: SpeedCalculator() {} void Init(SpeedCalculatorArgs^ args) { instance->speed_calculator.init(*args->speed_args); } double CalculateSpeed(double x, double y, double time) { vec2d in = { x ,y }; return instance->speed_calculator.calc_speed_whole(in, time); } double SmoothScale(double scale, double time) { return instance->speed_calculator.smoother_x.scale_smoother.smooth(scale, time); } double SmoothOutput(double outputSpeed, double time) { return instance->speed_calculator.smoother_x.output_speed_smoother.smooth(outputSpeed, time); } }; [JsonObject(ItemRequired = Required::Always)] public ref class DriverConfig { public: literal double WriteDelayMs = ra::WRITE_DELAY; literal String^ Key = "Driver settings"; String^ version = RA_VER_STRING; DeviceConfig defaultDeviceConfig; List^ profiles; [NonSerialized] List^ accels; List^ devices; void Activate() { if (accels->Count != profiles->Count) { throw gcnew Exception("Profile count does not match ManagedAccel"); } std::byte* buffer; auto modifier_data_bytes = accels->Count * sizeof(ra::modifier_settings); auto device_data_bytes = devices->Count * sizeof(ra::device_settings); try { buffer = new std::byte[sizeof(ra::io_base) + modifier_data_bytes + device_data_bytes]; } catch (const std::exception& e) { throw gcnew InteropException(e); } auto* byte_ptr = buffer; auto* base_data = reinterpret_cast(byte_ptr); base_data->default_dev_cfg.disable = defaultDeviceConfig.disable; base_data->default_dev_cfg.set_extra_info = defaultDeviceConfig.setExtraInfo; base_data->default_dev_cfg.poll_time_lock = defaultDeviceConfig.pollTimeLock; base_data->default_dev_cfg.dpi = defaultDeviceConfig.dpi; base_data->default_dev_cfg.polling_rate = defaultDeviceConfig.pollingRate; base_data->default_dev_cfg.clamp.min = defaultDeviceConfig.minimumTime; base_data->default_dev_cfg.clamp.max = defaultDeviceConfig.maximumTime; base_data->modifier_data_size = accels->Count; base_data->device_data_size = devices->Count; byte_ptr += sizeof(ra::io_base); auto* modifier_data = reinterpret_cast(byte_ptr); for (auto i = 0; i < accels->Count; i++) { auto& mod_settings = modifier_data[i]; mod_settings = accels[i]->NativeSettings(); } byte_ptr += modifier_data_bytes; auto* device_data = reinterpret_cast(byte_ptr); for (auto i = 0; i < devices->Count; i++) { auto& dev_settings = device_data[i]; Marshal::StructureToPtr(devices[i], IntPtr(&dev_settings), false); } try { ra::write(buffer); delete[] buffer; } catch (const std::exception& e) { delete[] buffer; throw gcnew InteropException(e); } } void SetProfileAt(int index, Profile^ val) { profiles[index] = val; accels[index]->Settings = val; } // returns null or a joined list of error messages String^ Errors() { Text::StringBuilder^ sb = gcnew Text::StringBuilder(); 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); 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(); } } JObject^ ToJObject() { auto dataQueue = gcnew Queue^>(); auto empty = Array::Empty(); 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; } } JObject^ jObject = JObject::FromObject(this); String^ capModes = String::Join(" | ", Enum::GetNames(CapMode::typeid)); String^ accelModes = String::Join(" | ", Enum::GetNames(AccelMode::typeid)); jObject->AddFirst(gcnew JProperty("### Cap modes ###", 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; } String^ ToJSON() { return ToJObject()->ToString(); } // returns (config, null) or (null, error message) static Tuple^ Convert(String^ json) { auto jss = gcnew JsonSerializerSettings(); jss->DefaultValueHandling = DefaultValueHandling::Populate; auto cfg = JsonConvert::DeserializeObject(json, jss); if (cfg == nullptr) throw gcnew JsonException("invalid JSON"); auto message = cfg->Errors(); if (message != nullptr) { return gcnew Tuple(nullptr, message); } else { cfg->accels = gcnew List(); 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(cfg, nullptr); } } static DriverConfig^ GetActive() { std::unique_ptr bytes; try { bytes = ra::read(); } catch (const std::exception& e) { throw gcnew InteropException(e); } auto cfg = gcnew DriverConfig(); cfg->profiles = gcnew List(); cfg->accels = gcnew List(); cfg->devices = gcnew List(); auto* byte_ptr = bytes.get(); ra::io_base* base_data = reinterpret_cast(byte_ptr); cfg->defaultDeviceConfig.Init(base_data->default_dev_cfg); byte_ptr += sizeof(ra::io_base); ra::modifier_settings* modifier_data = reinterpret_cast(byte_ptr); for (auto i = 0u; i < base_data->modifier_data_size; i++) { auto& mod_settings = modifier_data[i]; cfg->profiles->Add(gcnew Profile(mod_settings.prof)); cfg->accels->Add(gcnew ManagedAccel(mod_settings)); } byte_ptr += base_data->modifier_data_size * sizeof(ra::modifier_settings); ra::device_settings* device_data = reinterpret_cast(byte_ptr); for (auto i = 0u; i < base_data->device_data_size; i++) { auto& dev_settings = device_data[i]; cfg->devices->Add(gcnew DeviceSettings(dev_settings)); } return cfg; } static DriverConfig^ FromProfile(Profile^ prof) { auto cfg = gcnew DriverConfig(); cfg->profiles = gcnew List(); cfg->accels = gcnew List(); cfg->devices = gcnew List(); cfg->profiles->Add(prof); cfg->accels->Add(gcnew ManagedAccel(prof)); cfg->defaultDeviceConfig.Init(default_device_settings.config); return cfg; } static DriverConfig^ GetDefault() { return FromProfile(gcnew Profile()); } static void Deactivate() { try { ra::reset(); } catch (const std::exception& e) { throw gcnew InteropException(e); } } private: DriverConfig() {} };