From 3c72db2de84004775ccb99be5d7f73351193cb69 Mon Sep 17 00:00:00 2001 From: peri4 Date: Thu, 11 Sep 2025 21:06:30 +0300 Subject: [PATCH] add PIHIDevice --- CMakeLists.txt | 2 +- libs/main/system/pihidevice.cpp | 637 ++++++++++++++++++++++++++++++ libs/main/system/pihidevice.h | 121 ++++++ libs/main/system/pilibrary.h | 12 +- libs/main/system/pisystemmodule.h | 1 + 5 files changed, 766 insertions(+), 7 deletions(-) create mode 100644 libs/main/system/pihidevice.cpp create mode 100644 libs/main/system/pihidevice.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 249641e6..c910064f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -328,7 +328,7 @@ if(NOT PIP_FREERTOS) if(WIN32) if(${C_COMPILER} STREQUAL "cl.exe") else() - list(APPEND LIBS_MAIN ws2_32 iphlpapi psapi cfgmgr32 setupapi) + list(APPEND LIBS_MAIN ws2_32 iphlpapi psapi cfgmgr32 setupapi hid) endif() else() list(APPEND LIBS_MAIN dl) diff --git a/libs/main/system/pihidevice.cpp b/libs/main/system/pihidevice.cpp new file mode 100644 index 00000000..51d06074 --- /dev/null +++ b/libs/main/system/pihidevice.cpp @@ -0,0 +1,637 @@ +#include "pihidevice.h" + +#include "piliterals_string.h" +#include "piliterals_time.h" +#ifndef WINDOWS +# include "pidir.h" +# include "pifile.h" +# include "piiostream.h" + +# include +# include +# include +# include +# include +# include +#else +// clang-format off +# include +# include +extern "C" { +# include +} +// clang-format on +#endif + + +bool PIHIDeviceInfo::match(const PIString & str) const { + if (product.toLowerCase().contains(str.toLowerCase())) return true; + if (path.toLowerCase().contains(str.toLowerCase())) return true; + return false; +} + + +int PIHIDeviceInfo::axesAbsoluteCount() const { + int ret = 0; + for (const auto & a: axes) + if (!a.is_relative) ++ret; + return ret; +} + + +int PIHIDeviceInfo::axesRelativeCount() const { + int ret = 0; + for (const auto & a: axes) + if (a.is_relative) ++ret; + return ret; +} + + +void PIHIDeviceInfo::prepare() { + axis_by_dataindex.clear(); + button_by_dataindex.clear(); + for (const auto & i: axes) + axis_by_dataindex[i.data_index] = i; + for (const auto & i: buttons) + button_by_dataindex[i.data_index] = i; +} + + +PICout operator<<(PICout s, const PIHIDeviceInfo & v) { + s.saveAndSetControls(0); + s << "PIHIDeviceInfo(" << v.product << " (" << v.manufacturer << "), " << v.VID << ":" << v.PID + << ", " //<< "path \"" << v.path << "\", " + << v.axesAbsoluteCount() << " abs axes, " << v.axesRelativeCount() << " rel axes, " << v.buttonsCount() << " buttons)"; + s.restoreControls(); + return s; +} + + +PRIVATE_DEFINITION_START(PIHIDevice) +#ifndef WINDOWS + PIFile file; + bool is_js = false; +#else + PIByteArray buffer; + HANDLE deviceHandle = nullptr; + PHIDP_PREPARSED_DATA preparsed = nullptr; +#endif +PRIVATE_DEFINITION_END(PIHIDevice) + + +PIHIDevice::~PIHIDevice() { + close(); +} + +bool PIHIDevice::isOpened() const { +#ifndef WINDOWS + return PRIVATE->file.isOpened(); +#else + return PRIVATE->deviceHandle; +#endif +} + + +bool PIHIDevice::open(const PIHIDeviceInfo & device) { + close(); + cur_axes.clear(); + cur_buttons.clear(); + di = device; + di.prepare(); + if (device.isNull()) return false; +#ifndef WINDOWS + if (!PRIVATE->file.open(di.path, PIIODevice::ReadOnly)) { + piCout << "PIHIDevice::open" << di.path << "error:" << errorString(); + return false; + } + PRIVATE->is_js = PIFile::FileInfo(di.path).name().startsWith("js"_a); + return true; +#else + PRIVATE->deviceHandle = CreateFileA(di.path.dataAscii(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + 0, + nullptr); + if (PRIVATE->deviceHandle == INVALID_HANDLE_VALUE) { + piCoutObj << "PIHIDevice::open" << di.path << "error:" << errorString(); + PRIVATE->deviceHandle = nullptr; + return false; + } + HidD_GetPreparsedData(PRIVATE->deviceHandle, &PRIVATE->preparsed); + return true; +#endif +} + + +void PIHIDevice::close() { + stop(); +#ifndef WINDOWS + PRIVATE->file.close(); +#else + if (PRIVATE->deviceHandle) { + CloseHandle(PRIVATE->deviceHandle); + PRIVATE->deviceHandle = nullptr; + } + if (PRIVATE->preparsed) { + HidD_FreePreparsedData(PRIVATE->preparsed); + PRIVATE->preparsed = nullptr; + } +#endif +} + + +void PIHIDevice::start() { + if (!isOpened()) return; + PIThread::start(200_Hz); +#ifndef WINDOWS +#else +#endif +} + + +void PIHIDevice::stop() { + PIThread::stop(); +#ifdef WINDOWS + if (PRIVATE->deviceHandle) { + CancelIoEx(PRIVATE->deviceHandle, nullptr); + } +#endif + if (!waitForFinish(1000_ms)) terminate(); +} + + +void PIHIDevice::run() { + Event e; +#ifndef WINDOWS +# pragma pack(push, 1) + struct input_event { + struct timeval time; + ushort type; + ushort code; + uint value; + }; + struct js_event { + uint time; /* event timestamp in milliseconds */ + short value; /* value */ + uchar type; /* event type */ + uchar number; /* axis/button number */ + }; +# pragma pack(pop) + if (PRIVATE->is_js) { + js_event ie; + while (PRIVATE->file.read(&ie, sizeof(ie)) == sizeof(ie)) { + if (ie.type == 0) continue; + bool ok = false; + switch (ie.type) { + case 2: { + // piCout << ie.value; + cur_axes[ie.number] = procDeadZone(ie.value / 32767. / 2. + 0.5); + ok = true; + } break; + case 1: + // piCout << ie.code; + cur_buttons[ie.number] = ie.value; + ok = true; + break; + default: break; + } + // piCout << ok << ie.type << ie.code << ie.value; + if (!ok) continue; + } + } else { + input_event ie; + while (PRIVATE->file.read(&ie, sizeof(ie)) == sizeof(ie)) { + if (ie.type == 0) continue; + bool ok = false; + switch (ie.type) { + case 2: { // rel axis + auto vi = di.axis_by_dataindex.value(ie.code); + if (vi.isValid()) { + e.type = Event::tAxisMove; + e.axis = vi; + e.value = static_cast(ie.value); + event(e); + } + ok = true; + } break; + case 3: { // abs axis + auto vi = di.axis_by_dataindex.value(ie.code); + float fv = (ie.value - vi.min) / piMaxf(1.f, (float)(vi.max - vi.min)); + cur_axes[ie.code] = procDeadZone(fv); + ok = true; + } break; + case 1: // button + // piCout << ie.code; + cur_buttons[ie.code] = ie.value; + ok = true; + break; + default: break; + } + // piCout << ok << ie.type << ie.code << ie.value; + if (!ok) continue; + } + } +#else + PRIVATE->buffer.resize(di.input_report_size).fill(0); + DWORD readed = 0; + // piCout << "read" << PRIVATE->deviceHandle << PRIVATE->buffer.size(); + HIDP_DATA gdd[256]; + ULONG gdd_len = 256; + if (ReadFile(PRIVATE->deviceHandle, PRIVATE->buffer.data(), PRIVATE->buffer.size_s(), &readed, nullptr) != TRUE) return; + // piCout << readed << PRIVATE->buffer.size(); + if (readed != PRIVATE->buffer.size()) return; + auto gd = HidP_GetData(HidP_Input, gdd, &gdd_len, PRIVATE->preparsed, (PCHAR)PRIVATE->buffer.data(), PRIVATE->buffer.size_s()); + // piCout << "readed" << PRIVATE->buffer << gdd_len; + auto cbit = cur_buttons.makeIterator(); + while (cbit.next()) + cbit.value() = 0; + for (int i = 0; i < gdd_len; ++i) { + const auto & cd(gdd[i]); + // piCout << cd.DataIndex << cd.RawValue; + auto vi = di.axis_by_dataindex.value(cd.DataIndex); + if (vi.isValid()) { + if (vi.is_relative) { + e.type = Event::tAxisMove; + e.axis = vi; + e.value = static_cast(cd.RawValue); + event(e); + } else { + auto & axis(cur_axes[cd.DataIndex]); + float fv = (cd.RawValue - vi.min) / piMaxf(1.f, (float)(vi.max - vi.min)); + cur_axes[vi.data_index] = procDeadZone(fv); + } + // piCout << "axis" << vi.data_index << "->" << cur_axes[vi.data_index]; + continue; + } + auto bi = di.button_by_dataindex.value(cd.DataIndex); + if (bi.isValid()) { + cur_buttons[bi.data_index] = 1; + // piCout << "button" << bi.data_index << "-> 1"; + continue; + } + } +#endif + + auto ait = cur_axes.makeIterator(); + e.type = Event::tAxisMove; + while (ait.next()) { + if (ait.value() != prev_axes.value(ait.key())) { + e.axis = di.axis_by_dataindex.value(ait.key()); + e.value = ait.value(); + event(e); + } + } + prev_axes = cur_axes; + + auto bit = cur_buttons.makeIterator(); + e.type = Event::tButton; + while (bit.next()) { + if (bit.value() != prev_buttons.value(bit.key())) { + e.button = di.button_by_dataindex.value(bit.key()); + e.value = bit.value(); + event(e); + } + } + prev_buttons = cur_buttons; +} + + +double PIHIDevice::procDeadZone(double in) { + double cv = (in - 0.5) * 2.; + if (piAbsd(cv) < dead_zone) return 0.; + if (cv < 0) + return (cv + dead_zone) / (1. - dead_zone); + else + return (cv - dead_zone) / (1. - dead_zone); + return cv; +} + + +PIVector PIHIDevice::allDevices(bool try_open) { + PIVector ret; + +#ifndef WINDOWS + + auto readFile = [](const PIString & path) { + auto ba = PIFile::readAll(path, true); + PIString ret; + for (const auto & b: ba) { + if (!PIChar(b).isAscii()) break; + ret += PIChar(b); + } + return ret.trim(); + }; + auto isDir = [](const PIFile::FileInfo & fi) { + return fi.isDir() && !fi.flags[PIFile::FileInfo::Dot] && !fi.flags[PIFile::FileInfo::DotDot]; + }; + auto checkBit = [](const ullong & flags, ullong bit, const PIString & name) { return (flags & (1ULL << bit)) > 0; }; + + PIDir hid_dir("/sys/bus/hid/devices"_a); + auto hid_devs = hid_dir.entries(); + for (auto hd: hid_devs) { + // piCout << d.path; + if (!isDir(hd)) continue; + PIDir dir_input(hd.path + "/input"_a); + auto hid_inputs = dir_input.entries(); + for (auto hd_i: hid_inputs) { + if (!isDir(hd_i)) continue; + // now in /sys/bus/hid/devices//input/input + // piCout << hd_i.path; + PIHIDeviceInfo dev; + dev.product = readFile(hd_i.path + "/name"_a); + // piCout << readFile(hd_i.path + "/name"_a); + dev.VID = readFile(hd_i.path + "/id/vendor"_a); + dev.PID = readFile(hd_i.path + "/id/product"_a); + dev.version = readFile(hd_i.path + "/id/version"_a); + dev.manufacturer = readFile(hd_i.path + "/id/manufacturer"_a); + + // piCout << dev.product; + dev.input_report_size = 24; + PIDir dir_e(hd_i.path); + PIStringList devs; + auto dl_e = dir_e.entries(); + for (auto d_e: dl_e) { + if (!d_e.isDir() || d_e.flags[PIFile::FileInfo::Dot] || d_e.flags[PIFile::FileInfo::DotDot]) continue; + devs << d_e.name(); + } + /*bool dev_found = false; + for (const auto & d: devs) { + if (d.startsWith("js"_a)) { + dev.path = "/dev/input/"_a + d; + dev_found = true; + break; + } + } + if (!dev_found) {*/ + // search for event dir + for (const auto & d: devs) { + if (d.startsWith("event"_a)) { + dev.path = "/dev/input/"_a + d; + break; + } + } + + if (dev.path.isEmpty()) continue; + + if (try_open) { + PIFile test_f(dev.path, PIIODevice::ReadOnly); + if (test_f.isClosed()) continue; + } + + ullong ev = readFile(hd_i.path + "/capabilities/ev"_a).toULLong(16); + + auto readAxes = [readFile, checkBit, &hd_i, &dev](const PIString & file, bool is_relative) { + PIVector ret; + ullong bits = readFile(hd_i.path + file).toULLong(16); + // piCout<< PICoutManipulators::Bin << abs; + if (bits > 0) { + int fd = ::open(dev.path.dataAscii(), O_RDONLY); + if (fd < 0) { + // piCout << "Warning: can`t open" << dev.path << errorString(); + } + PIHIDeviceInfo::AxisInfo ai; + ai.is_relative = is_relative; + for (int bit = 0; bit < 64; ++bit) { + if (checkBit(bits, bit, PIString::fromNumber(bit))) { + ai.data_index = bit; + if (fd >= 0) { + struct input_absinfo abs_info; + if (ioctl(fd, EVIOCGABS(bit), &abs_info) != -1) { + ai.min = abs_info.minimum; + ai.max = abs_info.maximum; + // piCout << "axis" << bit << abs_info.minimum << abs_info.maximum << abs_info.flat << abs_info.fuzz; + } else { + ai.min = 0; + ai.max = 1024; + } + } + ret << ai; + } + } + if (fd >= 0) ::close(fd); + } + return ret; + }; + + dev.axes << readAxes("/capabilities/abs"_a, false) << readAxes("/capabilities/rel"_a, true); + for (int i = 0; i < dev.axes.size_s(); ++i) + dev.axes[i].index = i; + + PIString key_str = readFile(hd_i.path + "/capabilities/key"_a); + PIVector key_words; + while (key_str.isNotEmpty()) { + PIString w = key_str.takeWord().trimmed(); + if (w.isEmpty()) break; + key_words.prepend(w.toULong(16)); + } + PIBitArray key_bits((const uchar *)key_words.data(), key_words.size_s() * sizeof(ulong)); + + PIHIDeviceInfo::ButtonInfo bi; + for (uint b = 0; b < key_bits.bitSize(); ++b) { + if (!key_bits[b]) continue; + bi.index = dev.buttons.size_s(); + bi.code = b; + bi.data_index = b; + dev.buttons << bi; + } + + if (dev.manufacturer.isEmpty()) { + for (const auto * hwp: {"/usr/share/hwdata/usb.ids", "/var/lib/usbutils/usb.ids"}) { + PIFile hwf(hwp, PIIODevice::ReadOnly); + if (hwf.isClosed()) continue; + // piCout << "search" << dev.VID << "in" << hwp; + PIString line; + PIIOTextStream ts(&hwf); + while (!hwf.isEnd()) { + line = ts.readLine(); + if (line.startsWith(dev.VID)) { + line.takeWord(); + line.trim(); + dev.manufacturer = line; + break; + } + } + break; + } + } + + if (dev.isNotNull() && (dev.buttonsCount() > 0 || dev.axesCount() > 0)) ret << dev; + } + } + +#else + + GUID guid; + HidD_GetHidGuid(&guid); + HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&guid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (deviceInfoSet == INVALID_HANDLE_VALUE) { + piCout << "SetupDiGetClassDevs error:" << errorString(); + return ret; + } + + SP_DEVICE_INTERFACE_DATA deviceInterfaceData; + deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + for (DWORD i = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, nullptr, &guid, i, &deviceInterfaceData); ++i) { + PIHIDeviceInfo dev; + + DWORD requiredSize = 0; + SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, nullptr, 0, &requiredSize, nullptr); + PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = + reinterpret_cast(new BYTE[requiredSize]); + PIScopeExitCall exit_call([&deviceInterfaceDetailData]() { delete[] reinterpret_cast(deviceInterfaceDetailData); }); + deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, + &deviceInterfaceData, + deviceInterfaceDetailData, + requiredSize, + nullptr, + nullptr)) { + piCout << "SetupDiGetDeviceInterfaceDetail error:" << errorString(); + continue; + } + + if (try_open) { + auto test_f = CreateFileA(deviceInterfaceDetailData->DevicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + 0, + nullptr); + if (test_f == INVALID_HANDLE_VALUE) continue; + CloseHandle(test_f); + } + + HANDLE deviceHandle = + CreateFileA(deviceInterfaceDetailData->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (deviceHandle == INVALID_HANDLE_VALUE) { + piCout << "CreateFileA error:" << errorString(); + continue; + } + + // piCout << i << deviceHandle; + + PHIDP_PREPARSED_DATA preparsed = nullptr; + if (HidD_GetPreparsedData(deviceHandle, &preparsed) == FALSE) { + piCout << "HidD_GetPreparsedData error:" << errorString(); + continue; + } + // auto pp = PIByteArray(preparsed, 64); + // piCout << piChangedEndian(pp.dataAs(7)) << piChangedEndian(pp.dataAs(8)) << + // piChangedEndian(pp.dataAs(9)); + // piCout << pp; + + dev.path = deviceInterfaceDetailData->DevicePath; + + HIDD_ATTRIBUTES attr; + PIByteArray str_buff(1024); + HidD_GetAttributes(deviceHandle, &attr); + dev.VID.setNumber(attr.VendorID, 16).expandLeftTo(4, '0').toLowerCase(); + dev.PID.setNumber(attr.ProductID, 16).expandLeftTo(4, '0').toLowerCase(); + dev.version.setNumber(attr.VersionNumber); + + HIDP_CAPS caps; + HidP_GetCaps(preparsed, &caps); + dev.input_report_size = caps.InputReportByteLength; + + str_buff.fill(0); + HidD_GetManufacturerString(deviceHandle, str_buff.data(), str_buff.size_s()); + dev.manufacturer = PIString(reinterpret_cast(str_buff.data())); + + str_buff.fill(0); + HidD_GetProductString(deviceHandle, str_buff.data(), str_buff.size_s()); + dev.product = PIString(reinterpret_cast(str_buff.data())); + + str_buff.fill(0); + HidD_GetSerialNumberString(deviceHandle, str_buff.data(), str_buff.size_s()); + dev.serial = PIString(reinterpret_cast(str_buff.data())); + + PIVector axes_abs, axes_rel; + HIDP_VALUE_CAPS value_caps[1024]; + USHORT _cnt = caps.NumberInputValueCaps; + memset(value_caps, 0, sizeof(value_caps[0]) * 1024); + HidP_GetValueCaps(HidP_Input, value_caps, &_cnt, preparsed); + for (int i = 0; i < caps.NumberInputValueCaps; ++i) { + const auto & vc(value_caps[i]); + PIHIDeviceInfo::AxisInfo vi; + vi.bits = vc.BitSize; + vi.min = vc.LogicalMin; + vi.max = vc.LogicalMax; + vi.is_relative = vc.IsAbsolute == 0; + if (vi.max == vi.min) ++vi.max; + if (vc.IsRange == 1) { + int count = vc.Range.UsageMax - vc.Range.UsageMin + 1; + int cur_index = vc.Range.DataIndexMin; + for (int v = 0; v < count; ++v) { + vi.data_index = cur_index; + if (vi.is_relative) + axes_rel << vi; + else + axes_abs << vi; + ++cur_index; + } + } else { + vi.data_index = vc.NotRange.DataIndex; + if (vi.is_relative) + axes_rel << vi; + else + axes_abs << vi; + } + // piCout << vc.LinkCollection << vc.LinkUsage << vc.LinkUsagePage; + } + dev.axes << axes_abs << axes_rel; + for (int i = 0; i < dev.axes.size_s(); ++i) + dev.axes[i].index = i; + + HIDP_BUTTON_CAPS button_caps[1024]; + _cnt = caps.NumberInputButtonCaps; + memset(button_caps, 0, sizeof(button_caps[0]) * 1024); + HidP_GetButtonCaps(HidP_Input, button_caps, &_cnt, preparsed); + for (int i = 0; i < _cnt; ++i) { + const auto & bc(button_caps[i]); + PIHIDeviceInfo::ButtonInfo bi; + // dev.values.append(PIHIDeviceInfo::ValueInfo{value_caps[i].BitSize, value_caps[i].LogicalMin, value_caps[i].LogicalMax}); + if (bc.IsRange == 1) { + int count = bc.Range.UsageMax - bc.Range.UsageMin + 1; + int cur_index = bc.Range.DataIndexMin; + for (int b = 0; b < count; ++b) { + bi.index = dev.buttons.size_s(); + bi.data_index = cur_index; + dev.buttons << bi; + ++cur_index; + // piCout << b << (start_bit / 8) << (start_bit % 8); + } + } else { + bi.index = dev.buttons.size_s(); + bi.data_index = bc.NotRange.DataIndex; + dev.buttons << bi; + // piCout << (bi.bit / 8) << (bi.bit % 8); + } + // piCout << bc.IsRange << bc.Range.UsageMin << bc.Range.UsageMax + // << bc.Range.DataIndexMin << bc.Range.DataIndexMax; + } + + HidD_FreePreparsedData(preparsed); + CloseHandle(deviceHandle); + + if (dev.buttonsCount() > 0 || dev.axesCount() > 0) ret << dev; + } + + SetupDiDestroyDeviceInfoList(deviceInfoSet); + +#endif + + return ret; +} + + +PIHIDeviceInfo PIHIDevice::findDevice(const PIString & name) { + if (name.isEmpty()) return PIHIDeviceInfo(); + auto devices = PIHIDevice::allDevices(); + for (auto d: devices) { + if (d.match(name)) return d; + } + return PIHIDeviceInfo(); +} diff --git a/libs/main/system/pihidevice.h b/libs/main/system/pihidevice.h new file mode 100644 index 00000000..7f6dc9c9 --- /dev/null +++ b/libs/main/system/pihidevice.h @@ -0,0 +1,121 @@ +/*! \file pihidevice.h + * \ingroup System + * \~\brief + * \~english HID device + * \~russian HID устройство + */ +/* + PIP - Platform Independent Primitives + HID device + Ivan Pelipenko peri4ko@yandex.ru + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +#ifndef PIHIDEVICE_H +#define PIHIDEVICE_H + +#include "pithread.h" + + +struct PIP_EXPORT PIHIDeviceInfo { + friend class PIHIDevice; + struct PIP_EXPORT ValueInfoBase { + bool isValid() const { return index >= 0; } + int index = -1; + int data_index = -1; + }; + struct PIP_EXPORT AxisInfo: public ValueInfoBase { + int bits = 0; + int min = 0; + int max = 1; + bool is_relative = false; + }; + struct PIP_EXPORT ButtonInfo: public ValueInfoBase { + int code = 0; + }; + PIString path; + PIString manufacturer; + PIString product; + PIString serial; + PIString version; + PIString VID; + PIString PID; + PIVector axes; + PIVector buttons; + bool isNull() const { return path.isEmpty(); } + bool isNotNull() const { return !isNull(); } + bool match(const PIString & str) const; + int axesCount() const { return axes.size_s(); } + int axesAbsoluteCount() const; + int axesRelativeCount() const; + int buttonsCount() const { return buttons.size_s(); } + +private: + void prepare(); + PIMap axis_by_dataindex; + PIMap button_by_dataindex; + int input_report_size = 0; + int output_report_size = 0; + int feature_report_size = 0; +}; + +PIP_EXPORT PICout operator<<(PICout s, const PIHIDeviceInfo & v); + + +class PIP_EXPORT PIHIDevice: public PIThread { + PIOBJECT_SUBCLASS(PIHIDevice, PIThread) + +public: + ~PIHIDevice(); + + struct PIP_EXPORT Event { + enum Type { + tNone, + tButton, + tAxisMove, + }; + Type type = tNone; + PIHIDeviceInfo::AxisInfo axis; + PIHIDeviceInfo::ButtonInfo button; + float value = 0.; + }; + + bool isOpened() const; + bool open(const PIHIDeviceInfo & device); + void close(); + void start(); + void stop(); + + void setDeadZone(float v) { dead_zone = v; } + float deadZone() const { return dead_zone; } + + EVENT1(event, PIHIDevice::Event, e); + + static PIVector allDevices(bool try_open = true); + static PIHIDeviceInfo findDevice(const PIString & name); + +private: + void run() override; + double procDeadZone(double in); + + PRIVATE_DECLARATION(PIP_EXPORT) + PIHIDeviceInfo di; + PIMap prev_axes, cur_axes; + PIMap prev_buttons, cur_buttons; + float dead_zone = 0.f; +}; + + +#endif diff --git a/libs/main/system/pilibrary.h b/libs/main/system/pilibrary.h index c73fb2c5..9f5b901a 100644 --- a/libs/main/system/pilibrary.h +++ b/libs/main/system/pilibrary.h @@ -22,13 +22,13 @@ You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ -# + #ifndef PILIBRARY_H -# define PILIBRARY_H +#define PILIBRARY_H -# ifndef MICRO_PIP +#ifndef MICRO_PIP -# include "pistring.h" +# include "pistring.h" //! \ingroup System //! \~\brief @@ -82,5 +82,5 @@ private: PIString libpath, liberror; }; -# endif // MICRO_PIP -#endif // PILIBRARY_H +#endif // MICRO_PIP +#endif // PILIBRARY_H diff --git a/libs/main/system/pisystemmodule.h b/libs/main/system/pisystemmodule.h index 7b4ff139..311abda5 100644 --- a/libs/main/system/pisystemmodule.h +++ b/libs/main/system/pisystemmodule.h @@ -53,6 +53,7 @@ #ifndef PISYSTEMMODULE_H #define PISYSTEMMODULE_H +#include "pihidevice.h" #include "pilibrary.h" #include "piplugin.h" #include "piprocess.h"