rawaccel/wrapper/wrapper.cpp
a1xd 14bde56daf update rest
grapher is still broken

refactored io / error handling a bit
2021-04-01 01:51:31 -04:00

398 lines
9.1 KiB
C++

#pragma once
#include <rawaccel-io.hpp>
#include <rawaccel-validate.hpp>
#include <utility-rawinput.hpp>
#include <algorithm>
#include <type_traits>
#include <msclr\marshal_cppstd.h>
using namespace System;
using namespace System::Collections::Generic;
using namespace System::IO;
using namespace System::Runtime::InteropServices;
using namespace System::Reflection;
using namespace Windows::Forms;
using namespace Newtonsoft::Json;
namespace ra = rawaccel;
ra::settings default_settings;
[JsonConverter(Converters::StringEnumConverter::typeid)]
public enum class AccelMode
{
classic, jump, natural, power, motivity, noaccel
};
public enum class TableMode
{
off, binlog, linear
};
[StructLayout(LayoutKind::Sequential)]
public value struct TableArgs
{
[JsonIgnore]
TableMode mode;
[MarshalAs(UnmanagedType::U1)]
bool transfer;
[MarshalAs(UnmanagedType::U1)]
unsigned char partitions;
short num;
double start;
double stop;
};
generic <typename T>
[StructLayout(LayoutKind::Sequential)]
public value struct Vec2
{
T x;
T y;
};
[StructLayout(LayoutKind::Sequential)]
public value struct AccelArgs
{
AccelMode mode;
[MarshalAs(UnmanagedType::U1)]
bool legacy;
[JsonProperty(Required = Required::Default)]
TableArgs lutArgs;
double offset;
double cap;
double accelClassic;
double accelNatural;
double accelMotivity;
double motivity;
double power;
double scale;
double weight;
double exponent;
double limit;
double midpoint;
double smooth;
};
[StructLayout(LayoutKind::Sequential)]
public value struct DomainArgs
{
Vec2<double> domainXY;
double lpNorm;
};
[JsonObject(ItemRequired = Required::Always)]
[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")]
double snap;
[JsonProperty("Use x as whole/combined accel")]
[MarshalAs(UnmanagedType::U1)]
bool combineMagnitudes;
double dpi;
double speedCap;
[JsonProperty("Accel parameters")]
Vec2<AccelArgs> args;
[JsonProperty("Sensitivity multipliers")]
Vec2<double> sensitivity;
[JsonProperty("Negative directional multipliers")]
Vec2<double> directionalMultipliers;
[JsonProperty("Stretches domain for horizontal vs vertical inputs")]
DomainArgs domainArgs;
[JsonProperty("Stretches accel range for horizontal vs vertical inputs")]
Vec2<double> rangeXY;
[JsonProperty(Required = Required::Default)]
double minimumTime;
[JsonProperty("Device ID")]
[MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_DEV_ID_LEN)]
String^ deviceID = "";
bool ShouldSerializeminimumTime()
{
return minimumTime != ra::DEFAULT_TIME_MIN;
}
DriverSettings()
{
Marshal::PtrToStructure(IntPtr(&default_settings), this);
}
void ToFile(String^ path)
{
using namespace Newtonsoft::Json::Linq;
JObject^ thisJO = JObject::FromObject(this);
String^ modes = String::Join(" | ", Enum::GetNames(AccelMode::typeid));
thisJO->AddFirst(gcnew JProperty("### Accel Modes ###", modes));
File::WriteAllText(path, thisJO->ToString(Formatting::Indented));
}
static DriverSettings^ FromFile(String^ path)
{
if (!File::Exists(path))
{
throw gcnew FileNotFoundException(
String::Format("Settings file not found at {0}", path));
}
auto settings = JsonConvert::DeserializeObject<DriverSettings^>(
File::ReadAllText(path));
if (settings == nullptr) {
throw gcnew JsonException(String::Format("{0} contains invalid JSON", path));
}
return settings;
}
};
public ref struct InteropException : public Exception {
public:
InteropException(String^ what) :
Exception(what) {}
InteropException(const char* what) :
Exception(gcnew String(what)) {}
InteropException(const std::exception& e) :
InteropException(e.what()) {}
};
public ref class SettingsErrors
{
public:
List<String^>^ list;
int countX;
int countY;
delegate void MsgHandler(const char*);
void Add(const char* msg)
{
list->Add(msclr::interop::marshal_as<String^>(msg));
}
SettingsErrors(DriverSettings^ settings)
{
MsgHandler^ del = gcnew MsgHandler(this, &SettingsErrors::Add);
GCHandle gch = GCHandle::Alloc(del);
auto fp = static_cast<void (*)(const char*)>(
Marshal::GetFunctionPointerForDelegate(del).ToPointer());
ra::settings args;
Marshal::StructureToPtr(settings, (IntPtr)&args, false);
list = gcnew List<String^>();
auto [cx, cy, _] = ra::valid(args, fp);
countX = cx;
countY = cy;
gch.Free();
}
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);
}
return sb->ToString();
}
};
struct device_info {
std::wstring name;
std::wstring id;
};
std::vector<device_info> get_unique_device_info() {
std::vector<device_info> info;
rawinput_foreach_with_interface([&](const auto& dev, const WCHAR* name) {
info.push_back({
L"", // get_property_wstr(name, &DEVPKEY_Device_FriendlyName), /* doesn't work */
dev_id_from_interface(name)
});
});
std::sort(info.begin(), info.end(),
[](auto&& l, auto&& r) { return l.id < r.id; });
auto last = std::unique(info.begin(), info.end(),
[](auto&& l, auto&& r) { return l.id == r.id; });
info.erase(last, info.end());
return info;
}
public ref struct RawInputInterop
{
static void AddHandlesFromID(String^ deviceID, List<IntPtr>^ rawInputHandles)
{
try
{
std::vector<HANDLE> nativeHandles = rawinput_handles_from_dev_id(
msclr::interop::marshal_as<std::wstring>(deviceID));
for (auto nh : nativeHandles) rawInputHandles->Add(IntPtr(nh));
}
catch (const std::exception& e)
{
throw gcnew InteropException(e);
}
}
static List<ValueTuple<String^, String^>>^ GetDeviceIDs()
{
try
{
auto managed = gcnew List<ValueTuple<String^, String^>>();
for (auto&& [name, id] : get_unique_device_info())
{
managed->Add(
ValueTuple<String^, String^>(
msclr::interop::marshal_as<String^>(name),
msclr::interop::marshal_as<String^>(id)));
}
return managed;
}
catch (const std::exception& e)
{
throw gcnew InteropException(e);
}
}
};
public ref class ManagedAccel
{
ra::io_t* const instance = new ra::io_t();
public:
ManagedAccel() {};
ManagedAccel(DriverSettings^ settings)
{
Settings = settings;
}
virtual ~ManagedAccel()
{
delete instance;
}
!ManagedAccel()
{
delete instance;
}
Tuple<double, double>^ Accelerate(int x, int y, double time)
{
vec2d in_out_vec = {
(double)x,
(double)y
};
instance->mod.modify(in_out_vec, time);
return gcnew Tuple<double, double>(in_out_vec.x, in_out_vec.y);
}
void Activate()
{
try {
ra::write(*instance);
}
catch (const ra::error& e) {
throw gcnew InteropException(e);
}
}
property DriverSettings^ Settings
{
DriverSettings^ get()
{
DriverSettings^ settings = gcnew DriverSettings();
Marshal::PtrToStructure(IntPtr(&instance->args), settings);
return settings;
}
void set(DriverSettings^ val)
{
Marshal::StructureToPtr(val, IntPtr(&instance->args), false);
instance->mod = { instance->args };
}
}
static ManagedAccel^ GetActive()
{
try {
auto active = gcnew ManagedAccel();
ra::read(*active->instance);
return active;
}
catch (const ra::error& e) {
throw gcnew InteropException(e);
}
}
};
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);
}
}
};