Compare commits
4 Commits
53faaeb396
...
2806086558
| Author | SHA1 | Date | |
|---|---|---|---|
| 2806086558 | |||
| ce962bfb40 | |||
| dcdd7db33d | |||
| 3c72db2de8 |
@@ -6,7 +6,7 @@ endif()
|
||||
project(PIP)
|
||||
set(PIP_MAJOR 5)
|
||||
set(PIP_MINOR 2)
|
||||
set(PIP_REVISION 0)
|
||||
set(PIP_REVISION 1)
|
||||
set(PIP_SUFFIX )
|
||||
set(PIP_COMPANY SHS)
|
||||
set(PIP_DOMAIN org.SHS)
|
||||
@@ -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)
|
||||
|
||||
643
libs/main/system/pihidevice.cpp
Normal file
643
libs/main/system/pihidevice.cpp
Normal file
@@ -0,0 +1,643 @@
|
||||
#include "pihidevice.h"
|
||||
|
||||
#include "piliterals_string.h"
|
||||
#include "piliterals_time.h"
|
||||
#ifndef WINDOWS
|
||||
# include "pidir.h"
|
||||
# include "pifile.h"
|
||||
# include "piiostream.h"
|
||||
|
||||
# include <fcntl.h>
|
||||
# include <linux/input-event-codes.h>
|
||||
# include <linux/input.h>
|
||||
# include <sys/ioctl.h>
|
||||
# include <sys/time.h>
|
||||
# include <unistd.h>
|
||||
#else
|
||||
// clang-format off
|
||||
# include <windows.h>
|
||||
# include <setupapi.h>
|
||||
extern "C" {
|
||||
# include <hidsdi.h>
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
bool PIHIDevice::open() {
|
||||
return open(di);
|
||||
}
|
||||
|
||||
|
||||
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<int>(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());
|
||||
NO_UNUSED(gd);
|
||||
// piCout << "readed" << PRIVATE->buffer << gdd_len;
|
||||
auto cbit = cur_buttons.makeIterator();
|
||||
while (cbit.next())
|
||||
cbit.value() = 0;
|
||||
for (ULONG 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<LONG>(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<PIHIDeviceInfo> PIHIDevice::allDevices(bool try_open) {
|
||||
PIVector<PIHIDeviceInfo> ret;
|
||||
|
||||
#ifndef WINDOWS
|
||||
|
||||
auto readFile = [](const PIString & path) {
|
||||
auto ba = PIFile::readAll(path);
|
||||
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/<dev>/input/input<N>
|
||||
// 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<N> 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<PIHIDeviceInfo::AxisInfo> 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<ulong> 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<PSP_DEVICE_INTERFACE_DETAIL_DATA>(new BYTE[requiredSize]);
|
||||
PIScopeExitCall exit_call([&deviceInterfaceDetailData]() { delete[] reinterpret_cast<BYTE *>(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<ushort>(7)) << piChangedEndian(pp.dataAs<ushort>(8)) <<
|
||||
// piChangedEndian(pp.dataAs<ushort>(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<wchar_t *>(str_buff.data()));
|
||||
|
||||
str_buff.fill(0);
|
||||
HidD_GetProductString(deviceHandle, str_buff.data(), str_buff.size_s());
|
||||
dev.product = PIString(reinterpret_cast<wchar_t *>(str_buff.data()));
|
||||
|
||||
str_buff.fill(0);
|
||||
HidD_GetSerialNumberString(deviceHandle, str_buff.data(), str_buff.size_s());
|
||||
dev.serial = PIString(reinterpret_cast<wchar_t *>(str_buff.data()));
|
||||
|
||||
PIVector<PIHIDeviceInfo::AxisInfo> 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();
|
||||
}
|
||||
122
libs/main/system/pihidevice.h
Normal file
122
libs/main/system/pihidevice.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/*! \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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<AxisInfo> axes;
|
||||
PIVector<ButtonInfo> 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<int, AxisInfo> axis_by_dataindex;
|
||||
PIMap<int, ButtonInfo> 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);
|
||||
bool open();
|
||||
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<PIHIDeviceInfo> 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<int, float> prev_axes, cur_axes;
|
||||
PIMap<int, int> prev_buttons, cur_buttons;
|
||||
float dead_zone = 0.f;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@@ -22,7 +22,7 @@
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#
|
||||
|
||||
#ifndef PILIBRARY_H
|
||||
#define PILIBRARY_H
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#ifndef PISYSTEMMODULE_H
|
||||
#define PISYSTEMMODULE_H
|
||||
|
||||
#include "pihidevice.h"
|
||||
#include "pilibrary.h"
|
||||
#include "piplugin.h"
|
||||
#include "piprocess.h"
|
||||
|
||||
@@ -196,7 +196,7 @@ public:
|
||||
if (isNull() && s.isNull()) return true;
|
||||
if (isNull() xor s.isNull()) return false;
|
||||
if (size() != s.size()) return false;
|
||||
return strcmp(str, s.str) == 0;
|
||||
return strncmp(str, s.str, size()) == 0;
|
||||
}
|
||||
|
||||
//! \~english Compare operator.
|
||||
@@ -209,7 +209,7 @@ public:
|
||||
if (isNull() && s.isNull()) return false;
|
||||
if (isNull() && !s.isNull()) return true;
|
||||
if (!isNull() && s.isNull()) return false;
|
||||
if (size() == s.size()) return strcmp(str, s.str) < 0;
|
||||
if (size() == s.size()) return strncmp(str, s.str, size()) < 0;
|
||||
return size() < s.size();
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ public:
|
||||
if (isNull() && s.isNull()) return false;
|
||||
if (isNull() && !s.isNull()) return false;
|
||||
if (!isNull() && s.isNull()) return true;
|
||||
if (size() == s.size()) return strcmp(str, s.str) > 0;
|
||||
if (size() == s.size()) return strncmp(str, s.str, size()) > 0;
|
||||
return size() > s.size();
|
||||
}
|
||||
|
||||
|
||||
@@ -493,8 +493,8 @@ public:
|
||||
inline const uchar * data(size_t index = 0) const { return d.data(index); }
|
||||
|
||||
template<typename T>
|
||||
inline T dataAs(size_t index = 0) {
|
||||
return *(T *)d.data(index);
|
||||
inline T dataAs(size_t index = 0) const {
|
||||
return *(const T *)d.data(index);
|
||||
}
|
||||
|
||||
//! \~english Clear array, remove all elements.
|
||||
|
||||
@@ -1754,16 +1754,24 @@ PIVariantTypes::IODevice PIVariant::toIODevice() const {
|
||||
//!
|
||||
PIPointd PIVariant::toPoint() const {
|
||||
PIByteArray ba(_content);
|
||||
if (_type == PIVariant::pivPoint) {
|
||||
PIPointd r;
|
||||
ba >> r;
|
||||
return r;
|
||||
}
|
||||
if (_type == PIVariant::pivString) {
|
||||
PIString r;
|
||||
ba >> r;
|
||||
PIStringList l = r.split(';');
|
||||
if (l.size() >= 2) return PIPointd(l[0].toDouble(), l[1].toDouble());
|
||||
}
|
||||
if (_type == PIVariant::pivPoint) {
|
||||
PIPointd r;
|
||||
if (_type == PIVariant::pivMathVector) {
|
||||
PIMathVectord r;
|
||||
ba >> r;
|
||||
return r;
|
||||
PIPointd ret;
|
||||
if (r.size() > 0) ret.x = r[0];
|
||||
if (r.size() > 1) ret.y = r[1];
|
||||
return ret;
|
||||
}
|
||||
return PIPointd();
|
||||
}
|
||||
@@ -1880,6 +1888,14 @@ PIMathVectord PIVariant::toMathVector() const {
|
||||
ba >> r;
|
||||
return r;
|
||||
}
|
||||
if (_type == PIVariant::pivPoint) {
|
||||
PIPointd r;
|
||||
ba >> r;
|
||||
PIMathVectord ret(2);
|
||||
ret[0] = r.x;
|
||||
ret[1] = r.y;
|
||||
return ret;
|
||||
}
|
||||
return PIMathVectord();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user