summaryrefslogtreecommitdiff
path: root/wrapper
diff options
context:
space:
mode:
authora1xd <[email protected]>2021-09-24 02:04:43 -0400
committerGitHub <[email protected]>2021-09-24 02:04:43 -0400
commit2896b8a09ce42e965705c58593b8738adc454f7f (patch)
tree71e4d0cff60b5a1ad11427d78e1f8c7b775e5690 /wrapper
parentMerge pull request #107 from a1xd/1.5.0-fix (diff)
parentmake note clearer (diff)
downloadrawaccel-dark-mode.tar.xz
rawaccel-dark-mode.zip
Merge pull request #108 from a1xd/1.6r2HEADv1.6.0masterdark-mode
v1.6
Diffstat (limited to 'wrapper')
-rw-r--r--wrapper/input.cpp120
-rw-r--r--wrapper/input.h110
-rw-r--r--wrapper/wrapper.cpp912
-rw-r--r--wrapper/wrapper.vcxproj7
4 files changed, 654 insertions, 495 deletions
diff --git a/wrapper/input.cpp b/wrapper/input.cpp
index 3ce257a..334a0ec 100644
--- a/wrapper/input.cpp
+++ b/wrapper/input.cpp
@@ -1,79 +1,91 @@
#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)
+ // Returned list represents the current connected raw input devices,
+ // where each device has a distinct device id
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/install/device-ids
+ static IList<MultiHandleDevice^>^ GetList()
{
- try
- {
- std::vector<HANDLE> nativeHandles = rawinput_handles_from_id(
- msclr::interop::marshal_as<std::wstring>(deviceID));
+ return ListMaker::MakeList()->AsReadOnly();
+ }
- 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..a248010 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::modifier_settings default_modifier_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 CapMode {
+ in_out, input, output
};
-
generic <typename T>
[StructLayout(LayoutKind::Sequential)]
public value struct Vec2
@@ -56,90 +54,81 @@ 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 inputOffset;
+ double outputOffset;
+ 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")]
+ CapMode 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";
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
+ System::String^ name;
+
+ [JsonProperty("Whole/combined accel (set false for 'by component' mode)")]
+ [MarshalAs(UnmanagedType::U1)]
+ bool combineMagnitudes;
+
+ double lpNorm;
+
+ [JsonProperty("Stretches domain for horizontal vs vertical inputs")]
+ Vec2<double> domainXY;
+ [JsonProperty("Stretches accel range for horizontal vs vertical inputs")]
+ Vec2<double> rangeXY;
+
+ [JsonProperty("Whole or horizontal accel parameters")]
+ AccelArgs argsX;
+ [JsonProperty("Vertical accel parameters")]
+ AccelArgs argsY;
+
+ [JsonProperty("Sensitivity multiplier")]
+ double sensitivity;
+ [JsonProperty("Y/X sensitivity ratio (vertical sens multiplier)")]
+ double yxSensRatio;
+ [JsonProperty("L/R sensitivity ratio (left sens multiplier)")]
+ double lrSensRatio;
+ [JsonProperty("U/D sensitivity ratio (up sens multiplier)")]
+ double udSensRatio;
[JsonProperty("Degrees of rotation")]
double rotation;
@@ -147,351 +136,235 @@ public ref struct DriverSettings
[JsonProperty("Degrees of angle snapping")]
double snap;
- [JsonProperty("Use x as whole/combined accel")]
- [MarshalAs(UnmanagedType::U1)]
- bool combineMagnitudes;
-
- double dpi;
-
[JsonIgnore]
double minimumSpeed;
[JsonProperty("Input Speed Cap")]
double maximumSpeed;
- [JsonProperty("Accel parameters")]
- Vec2<AccelArgs> args;
+ Profile(ra::profile& args)
+ {
+ Marshal::PtrToStructure(IntPtr(&args), this);
+ }
- [JsonProperty("Sensitivity multipliers")]
- Vec2<double> sensitivity;
+ Profile() :
+ Profile(default_modifier_settings.prof) {}
+};
- [JsonProperty("Negative directional multipliers")]
- Vec2<double> directionalMultipliers;
+[JsonObject(ItemRequired = Required::Always)]
+[StructLayout(LayoutKind::Sequential)]
+public value struct DeviceConfig {
+ [MarshalAs(UnmanagedType::U1)]
+ bool disable;
- [JsonProperty("Stretches domain for horizontal vs vertical inputs")]
- DomainArgs domainArgs;
+ [MarshalAs(UnmanagedType::U1)]
+ [JsonProperty(Required = Required::Default)]
+ bool setExtraInfo;
- [JsonProperty("Stretches accel range for horizontal vs vertical inputs")]
- Vec2<double> rangeXY;
+ [JsonProperty("DPI (normalizes sens to 1000dpi and converts 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;
- [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 != ra::DEFAULT_TIME_MIN;
- }
-
- bool ShouldSerializemaximumTime()
- {
- return maximumTime != ra::DEFAULT_TIME_MAX;
- }
-
- DriverSettings()
+ bool ShouldSerializesetExtraInfo()
{
- Marshal::PtrToStructure(IntPtr(&default_settings), this);
+ return setExtraInfo == true;
}
-private:
- [OnDeserialized]
- void OnDeserializedMethod(StreamingContext context)
+ bool ShouldSerializeminimumTime()
{
- args.x.tableData.length = args.x.tableData.points->Length;
- args.y.tableData.length = args.y.tableData.points->Length;
-
- array<Vec2<float>>::Resize(args.x.tableData.points, ra::ARB_LUT_CAPACITY);
- array<Vec2<float>>::Resize(args.y.tableData.points, ra::ARB_LUT_CAPACITY);
+ return minimumTime != ra::DEFAULT_TIME_MIN;
}
- [OnSerializing]
- void OnSerializingMethod(StreamingContext context)
+ bool ShouldSerializemaximumTime()
{
- array<Vec2<float>>::Resize(args.x.tableData.points, args.x.tableData.length);
- array<Vec2<float>>::Resize(args.y.tableData.points, args.y.tableData.length);
+ return maximumTime != ra::DEFAULT_TIME_MAX;
}
- [OnSerialized]
- void OnSerializedMethod(StreamingContext context)
+ void Init(const ra::device_config& cfg)
{
- array<Vec2<float>>::Resize(args.x.tableData.points, ra::ARB_LUT_CAPACITY);
- array<Vec2<float>>::Resize(args.y.tableData.points, ra::ARB_LUT_CAPACITY);
+ 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 LutBase
+[StructLayout(LayoutKind::Sequential, CharSet = CharSet::Unicode)]
+public ref struct DeviceSettings
{
- [JsonConverter(Converters::StringEnumConverter::typeid)]
- enum class Mode
- {
- logarithmic, linear
- } mode;
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
+ String^ name;
- virtual void SetArgs(AccelArgs%) {}
- virtual void SetData(ra::accel_union&) {}
-};
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_NAME_LEN)]
+ String^ profile;
+ [MarshalAs(UnmanagedType::ByValTStr, SizeConst = ra::MAX_DEV_ID_LEN)]
+ String^ id;
-[JsonObject(ItemRequired = Required::Always)]
-public ref struct SpacedLut abstract : public LutBase
-{
- [JsonProperty("Whether points affect velocity (true) or sensitivity (false)")]
- bool transfer;
-
- double start;
- double stop;
- array<float>^ data;
+ DeviceConfig config;
- void SetArgsBase(AccelArgs% args)
+ DeviceSettings(ra::device_settings& args)
{
- args.spacedTableArgs.transfer = transfer;
- args.spacedTableArgs.start = start;
- args.spacedTableArgs.stop = stop;
+ Marshal::PtrToStructure(IntPtr(&args), this);
}
- void SetDataBase(ra::accel_union& accel)
- {
- if (size_t(data->LongLength) > ra::SPACED_LUT_CAPACITY) {
- throw gcnew InteropException("data is too large");
- }
- }
+ DeviceSettings() :
+ DeviceSettings(default_device_settings) {}
};
-[JsonObject(ItemRequired = Required::Always)]
-public ref struct LinearLut sealed : public SpacedLut
-{
- LinearLut()
- {
- }
-
- LinearLut(const ra::linear_lut& table)
- {
- 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);
- }
- virtual void SetArgs(AccelArgs% args) override
- {
- SetArgsBase(args);
- args.spacedTableArgs.num = data->Length;
- args.spacedTableArgs.mode = SpacedTableMode::linear;
- }
-
- virtual void SetData(ra::accel_union& accel) override
- {
- SetDataBase(accel);
- pin_ptr<float> pdata = &data[0];
- std::memcpy(&accel.lin_lut.data, pdata, sizeof(float) * data->Length);
- }
-};
-
-[JsonObject(ItemRequired = Required::Always)]
-public ref struct BinLogLut sealed : public SpacedLut
+public ref class ProfileErrors
{
- short num;
-
- BinLogLut()
- {
- }
-
- 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)));
-
- pin_ptr<float> pdata = &data[0];
- std::memcpy(pdata, &table.data, sizeof(float) * data->Length);
- }
+ List<String^>^ tmp;
+ bool single;
- virtual void SetArgs(AccelArgs% args) override
- {
- SetArgsBase(args);
- args.spacedTableArgs.num = num;
- args.spacedTableArgs.mode = SpacedTableMode::binlog;
- }
+ delegate void MsgHandler(const char*);
- virtual void SetData(ra::accel_union& accel) override
+ void Add(const char* msg)
{
- 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);
+ tmp->Add(gcnew String(msg));
}
-};
-
-public ref struct RaConvert {
- static DriverSettings^ Settings(String^ json)
+public:
+ ref struct SingleProfileErrors
{
- return NonNullable<DriverSettings^>(json);
- }
+ Profile^ prof;
+ array<String^>^ messages;
+ int lastX;
+ int lastY;
+ };
- 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);
- }
+ List<SingleProfileErrors^>^ list;
- static LutBase^ Table(String^ json)
+ ProfileErrors(List<Profile^>^ profiles)
{
- 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();
+ 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();
+ }
}
- }
-
- static String^ Table(LutBase^ lut)
- {
- auto serializerSettings = gcnew JsonSerializerSettings();
- return JsonConvert::SerializeObject(
- lut, lut->GetType(), Formatting::Indented, serializerSettings);
- };
- generic <typename T>
- static T NonNullable(String^ json)
- {
- T obj = JsonConvert::DeserializeObject<T>(json);
- if (obj == nullptr) throw gcnew JsonException("invalid JSON");
- return obj;
+ 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;
+ };
- SettingsErrors(DriverSettings^ settings)
+ List<SingleDeviceErrors^>^ list;
+
+ 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::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
{
- instance_t* const instance = new instance_t();
-
+ accel_instance_t* const instance = new accel_instance_t();
public:
+
ManagedAccel() {}
- ManagedAccel(ExtendedSettings^ settings)
+ ManagedAccel(ra::modifier_settings& settings) :
+ instance(new accel_instance_t(settings)) {}
+
+ ManagedAccel(Profile^ settings)
{
Settings = settings;
}
@@ -547,95 +436,300 @@ 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::modifier_settings NativeSettings()
+ {
+ return instance->settings;
+ }
+
+};
+
+
+[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<Profile^>^ profiles;
+
+ [NonSerialized]
+ List<ManagedAccel^>^ accels;
+
+ 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 {
- ra::write(instance->data);
+ buffer = new std::byte[sizeof(ra::io_base) + modifier_data_bytes + device_data_bytes];
}
- catch (const ra::error& e) {
+ 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;
}
- 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();
+
+ ProfileErrors^ profErrors = gcnew ProfileErrors(profiles);
+ if (!profErrors->Empty()) {
+ sb->Append(profErrors->ToString());
}
- 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);
+ 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<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;
+ }
+ }
+
+ 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));
- if (val->tables.x) {
- val->tables.x->SetData(instance->data.mod.accel.x);
+ 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 (val->tables.y) {
- val->tables.y->SetData(instance->data.mod.accel.y);
- }
+ if (prof->argsY.mode == AccelMode::lut) {
+ Array::Resize(prof->argsY.data, ra::LUT_RAW_DATA_CAPACITY);
+ }
+ else {
+ prof->argsY.data = dataQueue->Dequeue();
+ }
}
+ return jObject;
}
- static ManagedAccel^ GetActive()
+ String^ ToJSON()
{
+ return ToJObject()->ToString();
+ }
+
+ // 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");
+
+ 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 {
- auto active = gcnew ManagedAccel();
- ra::read(active->instance->data);
- active->instance->inv = ra::invokers(active->instance->data.args);
- return active;
+ bytes = ra::read();
}
- catch (const ra::error& e) {
+ catch (const std::exception& e) {
throw gcnew InteropException(e);
+ }
+
+ auto cfg = gcnew DriverConfig();
+ cfg->profiles = gcnew List<Profile^>();
+ cfg->accels = gcnew List<ManagedAccel^>();
+ cfg->devices = gcnew List<DeviceSettings^>();
+
+ 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;
}
-private:
- LutBase^ extract(ra::spaced_lut_mode mode, ra::accel_union& au)
+ static DriverConfig^ FromProfile(Profile^ prof)
{
- 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();
- }
+ auto cfg = gcnew DriverConfig();
+ cfg->profiles = gcnew List<Profile^>();
+ cfg->accels = gcnew List<ManagedAccel^>();
+ cfg->devices = gcnew List<DeviceSettings^>();
+
+ cfg->profiles->Add(prof);
+ cfg->accels->Add(gcnew ManagedAccel(prof));
+ cfg->defaultDeviceConfig.Init(default_device_settings.config);
+ return cfg;
}
-};
-public ref struct VersionHelper
-{
- literal String^ VersionString = RA_VER_STRING;
+ static DriverConfig^ GetDefault()
+ {
+ return FromProfile(gcnew Profile());
+ }
- static Version^ ValidOrThrow()
+ static void Deactivate()
{
try {
- ra::version_t v = ra::valid_version_or_throw();
- return gcnew Version(v.major, v.minor, v.patch, 0);
+ ra::reset();
}
- catch (const ra::error& e) {
+ catch (const std::exception& e) {
throw gcnew InteropException(e);
}
}
+
+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)" &amp;
@@ -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" />