rawaccel/wrapper/wrapper.cpp
a1xd 1964548acb port to .NET 5
dependency changes
  - Newtonsoft.JSON
  + System.Windows.Forms.DataVisualization
  + System.Data.SqlClient (indirect, but was not added automatically by NuGet)

added ARM64 target
2021-09-09 17:28:10 -04:00

855 lines
23 KiB
C++

#pragma once
#include "interop-exception.h"
#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 System::Text::Json;
using namespace System::Text::Json::Serialization;
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(JsonStringEnumConverter::typeid)]
public enum class AccelMode
{
classic, jump, natural, motivity, power, lut, noaccel
};
[JsonConverter(JsonStringEnumConverter::typeid)]
public enum class ClassicCapMode {
in_out, input, output
};
generic <typename T>
[StructLayout(LayoutKind::Sequential)]
public value struct Vec2
{
[JsonInclude]
T x;
[JsonInclude]
T y;
};
[StructLayout(LayoutKind::Sequential)]
public value struct AccelArgs
{
literal int MaxLutPoints = ra::LUT_POINTS_CAPACITY;
[JsonInclude]
AccelMode mode;
[JsonPropertyName("Gain / Velocity")]
[MarshalAs(UnmanagedType::U1)]
[JsonInclude]
bool gain;
[JsonInclude]
double offset;
[JsonInclude]
double acceleration;
[JsonInclude]
double decayRate;
[JsonInclude]
double growthRate;
[JsonInclude]
double motivity;
[JsonInclude]
double exponentClassic;
[JsonInclude]
double scale;
[JsonInclude]
double weight;
[JsonInclude]
double exponentPower;
[JsonInclude]
double limit;
[JsonInclude]
double midpoint;
[JsonInclude]
double smooth;
[JsonInclude]
[JsonPropertyName("Cap / Jump")]
Vec2<double> cap;
[JsonInclude]
[JsonPropertyName("Cap mode")]
ClassicCapMode capMode;
[JsonIgnore]
int length;
[JsonInclude]
[MarshalAs(UnmanagedType::ByValArray, SizeConst = ra::LUT_RAW_DATA_CAPACITY)]
array<float>^ data;
};
public ref struct AccelArgsConverter : JsonConverter<AccelArgs>
{
virtual void Write(Utf8JsonWriter^ writer, AccelArgs args, JsonSerializerOptions^ options) override
{
if (args.mode == AccelMode::lut) {
// data->Length must be fixed for interop,
// temporary resize avoids serializing a bunch of zeros
Array::Resize(args.data, args.length);
JsonSerializer::Serialize(writer, args, options);
Array::Resize(args.data, ra::LUT_RAW_DATA_CAPACITY);
}
else {
// data may be used internally in any mode,
// so hide it when it's not needed for deserialization
auto tmp = args.data;
args.data = Array::Empty<float>();
JsonSerializer::Serialize(writer, args, options);
args.data = tmp;
}
}
virtual AccelArgs Read(Utf8JsonReader% reader, Type^ type, JsonSerializerOptions^ options) override
{
AccelArgs args = JsonSerializer::Deserialize<AccelArgs>(reader, options);
if (args.data == nullptr) args.data = Array::Empty<float>();
args.length = args.data->Length;
array<float>::Resize(args.data, ra::LUT_RAW_DATA_CAPACITY);
return args;
}
};
public ref struct NullToEmptyStringConverter : JsonConverter<String^>
{
virtual String^ Read(Utf8JsonReader% reader, Type^ type, JsonSerializerOptions^ options) override
{
String^ s = JsonSerializer::Deserialize<String^>(reader, options);
return s ? s : String::Empty;
}
virtual void Write(Utf8JsonWriter^ writer, String^ s, JsonSerializerOptions^ options) override
{
JsonSerializer::Serialize(writer, s, options);
}
property bool HandleNull
{
virtual bool get() override { return true; }
}
};
template <typename T>
public ref struct NullToDefaultRefClassConverter : JsonConverter<T^>
{
virtual T^ Read(Utf8JsonReader% reader, Type^ type, JsonSerializerOptions^ options) override
{
T^ val = JsonSerializer::Deserialize<T^>(reader, options);
return val ? val : gcnew T();
}
virtual void Write(Utf8JsonWriter^ writer, T^ val, JsonSerializerOptions^ options) override
{
JsonSerializer::Serialize(writer, val, options);
}
property bool HandleNull
{
virtual bool get() override { return true; }
}
};
template <typename T>
using NullToEmptyListConverter = NullToDefaultRefClassConverter<List<T>>;
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)]
public ref struct Profile
{
[JsonInclude]
[JsonConverter(NullToEmptyStringConverter::typeid)]
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
System::String^ name;
[JsonInclude]
[JsonPropertyName("Whole/combined accel (set false for By-component mode)")]
[MarshalAs(UnmanagedType::U1)]
bool combineMagnitudes;
[JsonInclude]
double lpNorm;
[JsonInclude]
[JsonPropertyName("Stretches domain for horizontal vs vertical inputs")]
Vec2<double> domainXY;
[JsonInclude]
[JsonPropertyName("Stretches accel range for horizontal vs vertical inputs")]
Vec2<double> rangeXY;
[JsonInclude]
[JsonPropertyName("Sensitivity multiplier")]
double sensitivity;
[JsonInclude]
[JsonPropertyName("Y/X sensitivity ratio (vertical sens multiplier)")]
double yxSensRatio;
[JsonConverter(AccelArgsConverter::typeid)]
[JsonInclude]
[JsonPropertyName("Whole or horizontal accel parameters")]
AccelArgs argsX;
[JsonConverter(AccelArgsConverter::typeid)]
[JsonInclude]
[JsonPropertyName("Vertical accel parameters")]
AccelArgs argsY;
[JsonIgnore]
double minimumSpeed;
[JsonInclude]
[JsonPropertyName("Input Speed Cap")]
double maximumSpeed;
[JsonInclude]
[JsonPropertyName("Negative directional multipliers")]
Vec2<double> directionalMultipliers;
[JsonInclude]
[JsonPropertyName("Degrees of rotation")]
double rotation;
[JsonInclude]
[JsonPropertyName("Degrees of angle snapping")]
double snap;
Profile(ra::profile& args)
{
Marshal::PtrToStructure(IntPtr(&args), this);
}
Profile() :
Profile(default_modifier_settings.prof) {}
};
[StructLayout(LayoutKind::Sequential)]
public value struct DeviceConfig {
literal String^ dpiProperty =
"DPI (normalizes sens to 1000dpi and converts input speed unit (counts/ms to in/s)";
literal String^ pollingRateProperty =
"Polling rate Hz (keep at 0 for automatic adjustment)";
[JsonInclude]
[MarshalAs(UnmanagedType::U1)]
bool disable;
[MarshalAs(UnmanagedType::U1)]
[JsonInclude]
bool setExtraInfo;
[JsonInclude]
[JsonPropertyName(dpiProperty)]
int dpi;
[JsonInclude]
[JsonPropertyName(pollingRateProperty)]
int pollingRate;
[JsonInclude]
double minimumTime;
[JsonInclude]
double maximumTime;
void Init(const ra::device_config& cfg)
{
disable = cfg.disable;
setExtraInfo = cfg.set_extra_info;
dpi = cfg.dpi;
pollingRate = cfg.polling_rate;
minimumTime = cfg.clamp.min;
maximumTime = cfg.clamp.max;
}
};
public ref struct DeviceConfigConverter : JsonConverter<DeviceConfig>
{
virtual DeviceConfig Read(Utf8JsonReader% reader, Type^ type, JsonSerializerOptions^ options) override
{
DeviceConfig cfg = JsonSerializer::Deserialize<DeviceConfig>(reader, options);
if (cfg.maximumTime == 0) cfg.maximumTime = ra::DEFAULT_TIME_MAX;
if (cfg.minimumTime == 0) cfg.minimumTime = ra::DEFAULT_TIME_MIN;
return cfg;
}
virtual void Write(Utf8JsonWriter^ writer, DeviceConfig cfg, JsonSerializerOptions^ options) override
{
writer->WriteStartObject();
writer->WriteBoolean("disable", cfg.disable);
if (cfg.setExtraInfo) {
writer->WriteBoolean("setExtraInfo", cfg.setExtraInfo);
}
writer->WriteNumber(DeviceConfig::dpiProperty, cfg.dpi);
writer->WriteNumber(DeviceConfig::pollingRateProperty, cfg.pollingRate);
if (cfg.minimumTime != ra::DEFAULT_TIME_MIN) {
writer->WriteNumber("minimumTime", cfg.minimumTime);
}
if (cfg.maximumTime != ra::DEFAULT_TIME_MAX) {
writer->WriteNumber("maximumTime", cfg.maximumTime);
}
writer->WriteEndObject();
}
};
[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)]
public ref struct DeviceSettings
{
[JsonInclude]
[JsonConverter(NullToEmptyStringConverter::typeid)]
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
String^ name;
[JsonInclude]
[JsonConverter(NullToEmptyStringConverter::typeid)]
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
String^ profile;
[JsonInclude]
[JsonConverter(NullToEmptyStringConverter::typeid)]
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_DEV_ID_LEN)]
String^ id;
[JsonInclude]
[JsonConverter(DeviceConfigConverter::typeid)]
DeviceConfig config;
DeviceSettings(ra::device_settings& args)
{
Marshal::PtrToStructure(IntPtr(&args), this);
}
DeviceSettings() :
DeviceSettings(default_device_settings) {}
};
public ref class ProfileErrors
{
List<String^>^ 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<String^>^ messages;
int lastX;
int lastY;
};
List<SingleProfileErrors^>^ list;
ProfileErrors(List<Profile^>^ profiles)
{
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;
}
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->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<String^>^ 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<String^>^ messages;
};
List<SingleDeviceErrors^>^ list;
DeviceErrors(List<DeviceSettings^>^ devSettings)
{
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();
}
}
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 };
}
};
public ref class ManagedAccel
{
accel_instance_t* const instance = new accel_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;
}
Tuple<double, double>^ Accelerate(int x, int y, double dpi_factor, double time)
{
vec2d in_out_vec = {
(double)x,
(double)y
};
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::modifier_settings NativeSettings()
{
return instance->settings;
}
};
public ref class DriverConfig {
public:
literal double WriteDelayMs = ra::WRITE_DELAY;
initonly static JsonSerializerOptions^ DefaultOptions = DefaultSerializerOptions();
[JsonInclude]
[JsonConverter(NullToEmptyStringConverter::typeid)]
String^ version = RA_VER_STRING;
[JsonInclude]
[JsonConverter(DeviceConfigConverter::typeid)]
DeviceConfig defaultDeviceConfig;
[JsonInclude]
[JsonConverter(NullToEmptyListConverter<Profile^>::typeid)]
List<Profile^>^ profiles;
[JsonIgnore]
List<ManagedAccel^>^ accels;
[JsonInclude]
[JsonConverter(NullToEmptyListConverter<DeviceSettings^>::typeid)]
List<DeviceSettings^>^ 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<ra::io_base*>(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.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<ra::modifier_settings*>(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<ra::device_settings*>(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();
}
}
String^ ToJSON()
{
using namespace System::Text;
StringBuilder^ sb = gcnew StringBuilder();
sb->AppendFormat(
"/*\n* Accel modes: {0}\n* Cap modes (classic only): {1}\n*/\n",
String::Join(" | ", Enum::GetNames(AccelMode::typeid)),
String::Join(" | ", Enum::GetNames(ClassicCapMode::typeid)));
sb->Append(JsonSerializer::Serialize(this, DefaultOptions));
return sb->ToString();
}
// returns (config, null) or (null, error message)
static Tuple<DriverConfig^, String^>^ Convert(String^ json)
{
auto cfg = JsonSerializer::Deserialize<DriverConfig^>(json, DefaultOptions);
if (cfg == nullptr) throw gcnew JsonException("invalid JSON");
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()
{
std::unique_ptr<std::byte[]> bytes;
try {
bytes = ra::read();
}
catch (const std::exception& e) {
throw gcnew InteropException(e);
}
auto cfg = gcnew DriverConfig();
auto* byte_ptr = bytes.get();
ra::io_base* base_data = reinterpret_cast<ra::io_base*>(byte_ptr);
cfg->defaultDeviceConfig.Init(base_data->default_dev_cfg);
byte_ptr += sizeof(ra::io_base);
ra::modifier_settings* modifier_data = reinterpret_cast<ra::modifier_settings*>(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<ra::device_settings*>(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->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);
}
}
DriverConfig()
{
profiles = gcnew List<Profile^>();
accels = gcnew List<ManagedAccel^>();
devices = gcnew List<DeviceSettings^>();
defaultDeviceConfig.Init(default_device_settings.config);
}
private:
static JsonSerializerOptions^ DefaultSerializerOptions()
{
JsonSerializerOptions^ options = gcnew JsonSerializerOptions();
options->WriteIndented = true;
options->ReadCommentHandling = JsonCommentHandling::Skip;
options->AllowTrailingCommas = true;
return options;
}
};