1 // FGHIDEventInput.cxx -- handle event driven input devices via HIDAPI
2 //
3 // Written by James Turner
4 //
5 // Copyright (C) 2017, James Turner <zakalawe@mac.com>
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 //
21
22 #include "config.h"
23
24 #include "FGHIDEventInput.hxx"
25
26 #include <cstdlib>
27 #include <cassert>
28 #include <algorithm>
29
30 #include <hidapi/hidapi.h>
31 #include <hidapi/hidparse.h>
32
33 #include <simgear/structure/exception.hxx>
34 #include <simgear/sg_inlines.h>
35 #include <simgear/misc/strutils.hxx>
36 #include <simgear/io/lowlevel.hxx>
37
38 const char* hexTable = "0123456789ABCDEF";
39
40 namespace HID
41 {
42 enum class UsagePage
43 {
44 Undefined = 0,
45 GenericDesktop,
46 Simulation,
47 VR,
48 Sport,
49 Game,
50 GenericDevice,
51 Keyboard,
52 LEDs,
53 Button,
54 Ordinal,
55 Telephony,
56 Consumer,
57 Digitizer,
58 // reserved 0x0E
59 // PID 0x0f
60 Unicode = 0x10,
61 AlphanumericDisplay = 0x14,
62
63 VendorDefinedStart = 0xFF00
64 };
65
66 enum GenericDesktopUsage
67 {
68 // generic desktop section
69 GD_Joystick = 0x04,
70 GD_GamePad = 0x05,
71 GD_Keyboard = 0x06,
72 GD_Keypad = 0x07,
73 GD_MultiAxisController = 0x08,
74 GD_X = 0x30,
75 GD_Y,
76 GD_Z,
77 GD_Rx,
78 GD_Ry,
79 GD_Rz,
80 GD_Slider,
81 GD_Dial,
82 GD_Wheel,
83 GD_Hatswitch,
84 GD_DpadUp = 0x90,
85 GD_DpadDown,
86 GD_DpadRight,
87 GD_DpadLeft
88 };
89
90 enum LEDUsage
91 {
92 LED_Undefined = 0,
93 LED_Play = 0x36,
94 LED_Pause = 0x37,
95 LED_GenericIndicator = 0x4B
96 };
97
98 enum AlphanumericUsage
99 {
100 AD_AlphanumericDisplay = 0x01,
101 AD_BitmappedDisplay = 0x2,
102 AD_DisplayControlReport = 0x24,
103 AD_ClearDisplay = 0x25,
104 AD_CharacterReport = 0x2B,
105 AD_DisplayData = 0x2C,
106 AD_DisplayStatus = 0x2D,
107 AD_Rows = 0x35,
108 AD_Columns = 0x36,
109 AD_7SegmentDirectMap = 0x43,
110 AD_14SegmentDirectMap = 0x45,
111 AD_DisplayBrightness = 0x46,
112 AD_DisplayContrast = 0x47
113 };
114
115 enum class ReportType
116 {
117 Invalid = 0,
118 In = 0x08,
119 Out = 0x09,
120 Feature = 0x0B
121 };
122
nameForUsage(uint32_t usagePage,uint32_t usage)123 std::string nameForUsage(uint32_t usagePage, uint32_t usage)
124 {
125 const auto enumUsage = static_cast<UsagePage>(usagePage);
126 if (enumUsage == UsagePage::Undefined) {
127 std::stringstream os;
128 os << "undefined-" << usage;
129 return os.str();
130 }
131
132 if (enumUsage == UsagePage::GenericDesktop) {
133 switch (usage) {
134 case GD_Joystick: return "joystick";
135 case GD_Wheel: return "wheel";
136 case GD_Dial: return "dial";
137 case GD_Hatswitch: return "hat";
138 case GD_Slider: return "slider";
139 case GD_Rx: return "x-rotate";
140 case GD_Ry: return "y-rotate";
141 case GD_Rz: return "z-rotate";
142 case GD_X: return "x-translate";
143 case GD_Y: return "y-translate";
144 case GD_Z: return "z-translate";
145 default:
146 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID generic desktop usage:" << usage);
147 }
148 } else if (enumUsage == UsagePage::Simulation) {
149 switch (usage) {
150 default:
151 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID simulation usage:" << usage);
152 }
153 } else if (enumUsage == UsagePage::Consumer) {
154 switch (usage) {
155 default:
156 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID consumer usage:" << usage);
157 }
158 } else if (enumUsage == UsagePage::AlphanumericDisplay) {
159 switch (usage) {
160 case AD_AlphanumericDisplay: return "alphanumeric";
161 case AD_CharacterReport: return "character-report";
162 case AD_DisplayData: return "display-data";
163 case AD_DisplayBrightness: return "display-brightness";
164 case AD_7SegmentDirectMap: return "seven-segment-direct";
165 case AD_14SegmentDirectMap: return "fourteen-segment-direct";
166
167 default:
168 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID alphanumeric usage:" << usage);
169 }
170 } else if (enumUsage == UsagePage::LEDs) {
171 switch (usage) {
172 case LED_GenericIndicator: return "led-misc";
173 case LED_Pause: return "led-pause";
174 default:
175 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID LED usage:" << usage);
176
177 }
178 } else if (enumUsage == UsagePage::Button) {
179 std::stringstream os;
180 os << "button-" << usage;
181 return os.str();
182 } else if (enumUsage >= UsagePage::VendorDefinedStart) {
183 return "vendor";
184 } else {
185 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID usage page:" << std::hex << usagePage
186 << " with usage " << std::hex << usage);
187 }
188
189 return "unknown";
190 }
191
shouldPrefixWithAbs(uint32_t usagePage,uint32_t usage)192 bool shouldPrefixWithAbs(uint32_t usagePage, uint32_t usage)
193 {
194 const auto enumUsage = static_cast<UsagePage>(usagePage);
195 if (enumUsage == UsagePage::GenericDesktop) {
196 switch (usage) {
197 case GD_Wheel:
198 case GD_Dial:
199 case GD_Hatswitch:
200 case GD_Slider:
201 case GD_Rx:
202 case GD_Ry:
203 case GD_Rz:
204 case GD_X:
205 case GD_Y:
206 case GD_Z:
207 return true;
208 default:
209 break;
210 }
211 }
212
213 return false;
214 }
215
reportTypeFromString(const std::string & s)216 ReportType reportTypeFromString(const std::string& s)
217 {
218 if (s == "input") return ReportType::In;
219 if (s == "output") return ReportType::Out;
220 if (s == "feature") return ReportType::Feature;
221 return ReportType::Invalid;
222 }
223 } // of namespace
224
225 class FGHIDEventInput::FGHIDEventInputPrivate
226 {
227 public:
228 FGHIDEventInput* p = nullptr;
229
230 void evaluateDevice(hid_device_info* deviceInfo);
231 };
232
233 // anonymous namespace to define our device subclass
234 namespace
235 {
236
237 class FGHIDDevice : public FGInputDevice {
238 public:
239 FGHIDDevice(hid_device_info* devInfo,
240 FGHIDEventInput* subsys);
241
242 virtual ~FGHIDDevice();
243
244 bool Open() override;
245 void Close() override;
246 void Configure(SGPropertyNode_ptr node) override;
247
248 void update(double dt) override;
249 const char *TranslateEventName(FGEventData &eventData) override;
250 void Send( const char * eventName, double value ) override;
251 void SendFeatureReport(unsigned int reportId, const std::string& data) override;
252
253 class Item
254 {
255 public:
Item(const std::string & n,uint32_t offset,uint8_t size)256 Item(const std::string& n, uint32_t offset, uint8_t size) :
257 name(n),
258 bitOffset(offset),
259 bitSize(size)
260 {}
261
262 std::string name;
263 uint32_t bitOffset = 0; // form the start of the report
264 uint8_t bitSize = 1;
265 bool isRelative = false;
266 bool doSignExtend = false;
267 int lastValue = 0;
268 // int defaultValue = 0;
269 // range, units, etc not needed for now
270 // hopefully this doesn't need to be a list
271 FGInputEvent_ptr event;
272 };
273 private:
274 class Report
275 {
276 public:
Report(HID::ReportType ty,uint8_t n=0)277 Report(HID::ReportType ty, uint8_t n = 0) : type(ty), number(n) {}
278
279 HID::ReportType type;
280 uint8_t number = 0;
281 std::vector<Item*> items;
282
currentBitSize() const283 uint32_t currentBitSize() const
284 {
285 uint32_t size = 0;
286 for (auto i : items) {
287 size += i->bitSize;
288 }
289 return size;
290 }
291 };
292
293 bool parseUSBHIDDescriptor();
294 void parseCollection(hid_item* collection);
295 void parseItem(hid_item* item);
296
297 Report* getReport(HID::ReportType ty, uint8_t number, bool doCreate = false);
298
299 void sendReport(Report* report) const;
300
301 uint8_t countWithName(const std::string& name) const;
302 std::pair<Report*, Item*> itemWithName(const std::string& name) const;
303
304 void processInputReport(Report* report, unsigned char* data, size_t length,
305 double dt, int keyModifiers);
306
307 int maybeSignExtend(Item* item, int inValue);
308
309 void defineReport(SGPropertyNode_ptr reportNode);
310
311 std::vector<Report*> _reports;
312 std::string _hidPath;
313 hid_device* _device = nullptr;
314 bool _haveNumberedReports = false;
315 bool _debugRaw = false;
316
317 /// set if we parsed the device description our XML
318 /// instead of from the USB data. Useful on Windows where the data
319 /// is inaccessible, or devices with broken descriptors
320 bool _haveLocalDescriptor = false;
321
322 /// allow specifying the descriptor as hex bytes in XML
323 std::vector<uint8_t>_rawXMLDescriptor;
324
325 // all sets which will be send on the next update() call.
326 std::set<Report*> _dirtyReports;
327 };
328
329 class HIDEventData : public FGEventData
330 {
331 public:
332 // item, value, dt, keyModifiers
HIDEventData(FGHIDDevice::Item * it,int value,double dt,int keyMods)333 HIDEventData(FGHIDDevice::Item* it, int value, double dt, int keyMods) :
334 FGEventData(value, dt, keyMods),
335 item(it)
336 {
337 assert(item);
338 }
339
340 FGHIDDevice::Item* item = nullptr;
341 };
342
FGHIDDevice(hid_device_info * devInfo,FGHIDEventInput *)343 FGHIDDevice::FGHIDDevice(hid_device_info *devInfo, FGHIDEventInput *)
344 {
345 _hidPath = devInfo->path;
346
347 std::wstring manufacturerName, productName;
348 productName = devInfo->product_string ? std::wstring(devInfo->product_string)
349 : L"unknown HID device";
350
351 if (devInfo->manufacturer_string) {
352 manufacturerName = std::wstring(devInfo->manufacturer_string);
353 SetName(simgear::strutils::convertWStringToUtf8(manufacturerName) + " " +
354 simgear::strutils::convertWStringToUtf8(productName));
355 } else {
356 SetName(simgear::strutils::convertWStringToUtf8(productName));
357 }
358
359 const auto serial = devInfo->serial_number;
360 std::string path(devInfo->path);
361 // most devices return an empty serial number, unfortunately
362 if ((serial != nullptr) && std::wcslen(serial) > 0) {
363 SetSerialNumber(simgear::strutils::convertWStringToUtf8(serial));
364 }
365
366 SG_LOG(SG_INPUT, SG_DEBUG, "HID device:" << GetName() << " at path " << _hidPath);
367 }
368
~FGHIDDevice()369 FGHIDDevice::~FGHIDDevice()
370 {
371 if (_device) {
372 hid_close(_device);
373 }
374 }
375
Configure(SGPropertyNode_ptr node)376 void FGHIDDevice::Configure(SGPropertyNode_ptr node)
377 {
378 // base class first
379 FGInputDevice::Configure(node);
380
381 if (node->hasChild("hid-descriptor")) {
382 _haveLocalDescriptor = true;
383 if (debugEvents) {
384 SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using local HID descriptor");
385 }
386
387 for (auto report : node->getChild("hid-descriptor")->getChildren("report")) {
388 defineReport(report);
389 }
390 }
391
392 if (node->hasChild("hid-raw-descriptor")) {
393 _rawXMLDescriptor = simgear::strutils::decodeHex(node->getStringValue("hid-raw-descriptor"));
394 if (debugEvents) {
395 SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using XML-defined raw HID descriptor");
396 }
397 }
398
399 if (node->getBoolValue("hid-debug-raw")) {
400 _debugRaw = true;
401 }
402 }
403
Open()404 bool FGHIDDevice::Open()
405 {
406 _device = hid_open_path(_hidPath.c_str());
407 if (_device == nullptr) {
408 SG_LOG(SG_INPUT, SG_WARN, GetUniqueName() << ": HID: Failed to open:" << _hidPath);
409 SG_LOG(SG_INPUT, SG_WARN, "\tnote on Linux you may need to adjust permissions of the device using UDev rules.");
410 return false;
411 }
412
413 #if !defined(SG_WINDOWS)
414 if (_rawXMLDescriptor.empty()) {
415 _rawXMLDescriptor.resize(2048);
416 int descriptorSize = hid_get_descriptor(_device, _rawXMLDescriptor.data(), _rawXMLDescriptor.size());
417 if (descriptorSize <= 0) {
418 SG_LOG(SG_INPUT, SG_WARN, "HID: " << GetUniqueName() << " failed to read HID descriptor");
419 return false;
420 }
421
422 _rawXMLDescriptor.resize(descriptorSize);
423 }
424 #endif
425
426 if (!_haveLocalDescriptor) {
427 bool ok = parseUSBHIDDescriptor();
428 if (!ok)
429 return false;
430 }
431
432 for (auto& v : handledEvents) {
433 auto reportItem = itemWithName(v.first);
434 if (!reportItem.second) {
435 SG_LOG(SG_INPUT, SG_WARN, "HID device:" << GetUniqueName() << " has no element for event:" << v.first);
436 continue;
437 }
438
439 FGInputEvent_ptr event = v.second;
440 if (debugEvents) {
441 SG_LOG(SG_INPUT, SG_INFO, "\tfound item for event:" << v.first);
442 }
443
444 reportItem.second->event = event;
445 }
446
447 return true;
448 }
449
parseUSBHIDDescriptor()450 bool FGHIDDevice::parseUSBHIDDescriptor()
451 {
452 #if defined(SG_WINDOWS)
453 if (_rawXMLDescriptor.empty()) {
454 SG_LOG(SG_INPUT, SG_ALERT, GetUniqueName() << ": on Windows, there is no way to extract the UDB-HID report descriptor. "
455 << "\nPlease supply the report descriptor in the device XML configuration.");
456 SG_LOG(SG_INPUT, SG_ALERT, "See this page:<> for information on extracting the report descriptor on Windows");
457 return false;
458 }
459 #endif
460
461 if (_debugRaw) {
462 SG_LOG(SG_INPUT, SG_INFO, "\nHID: descriptor for:" << GetUniqueName());
463 {
464 std::ostringstream byteString;
465
466 for (auto i=0; i<_rawXMLDescriptor.size(); ++i) {
467 byteString << hexTable[_rawXMLDescriptor[i] >> 4];
468 byteString << hexTable[_rawXMLDescriptor[i] & 0x0f];
469 byteString << " ";
470 }
471 SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
472 }
473 }
474
475 hid_item* rootItem = nullptr;
476 hid_parse_reportdesc(_rawXMLDescriptor.data(), _rawXMLDescriptor.size(), &rootItem);
477 if (debugEvents) {
478 SG_LOG(SG_INPUT, SG_INFO, "\nHID: scan for:" << GetUniqueName());
479 }
480
481 parseCollection(rootItem);
482
483 hid_free_reportdesc(rootItem);
484 return true;
485 }
486
parseCollection(hid_item * c)487 void FGHIDDevice::parseCollection(hid_item* c)
488 {
489 for (hid_item* child = c->collection; child != nullptr; child = child->next) {
490 if (child->collection) {
491 parseCollection(child);
492 } else {
493 // leaf item
494 parseItem(child);
495 }
496 }
497 }
498
getReport(HID::ReportType ty,uint8_t number,bool doCreate)499 auto FGHIDDevice::getReport(HID::ReportType ty, uint8_t number, bool doCreate) -> Report*
500 {
501 if (number > 0) {
502 _haveNumberedReports = true;
503 }
504
505 for (auto report : _reports) {
506 if ((report->type == ty) && (report->number == number)) {
507 return report;
508 }
509 }
510
511 if (doCreate) {
512 auto r = new Report{ty, number};
513 _reports.push_back(r);
514 return r;
515 } else {
516 return nullptr;
517 }
518 }
519
itemWithName(const std::string & name) const520 auto FGHIDDevice::itemWithName(const std::string& name) const -> std::pair<Report*, Item*>
521 {
522 for (auto report : _reports) {
523 for (auto item : report->items) {
524 if (item->name == name) {
525 return std::make_pair(report, item);
526 }
527 }
528 }
529
530 return std::make_pair(static_cast<Report*>(nullptr), static_cast<Item*>(nullptr));
531 }
532
countWithName(const std::string & name) const533 uint8_t FGHIDDevice::countWithName(const std::string& name) const
534 {
535 uint8_t result = 0;
536 size_t nameLength = name.length();
537
538 for (auto report : _reports) {
539 for (auto item : report->items) {
540 if (strncmp(name.c_str(), item->name.c_str(), nameLength) == 0) {
541 result++;
542 }
543 }
544 }
545
546 return result;
547 }
548
parseItem(hid_item * item)549 void FGHIDDevice::parseItem(hid_item* item)
550 {
551 std::string name = HID::nameForUsage(item->usage >> 16, item->usage & 0xffff);
552 if (hid_parse_is_relative(item)) {
553 name = "rel-" + name; // prefix relative names
554 } else if (HID::shouldPrefixWithAbs(item->usage >> 16, item->usage & 0xffff)) {
555 name = "abs-" + name;
556 }
557
558 const auto ty = static_cast<HID::ReportType>(item->type);
559 auto existingItem = itemWithName(name);
560 if (existingItem.second) {
561 // type fixup
562 const HID::ReportType existingItemType = existingItem.first->type;
563 if (existingItemType != ty) {
564 // might be an item named identically in input/output and feature reports
565 // -> prefix the feature one with 'feature'
566 if (ty == HID::ReportType::Feature) {
567 name = "feature-" + name;
568 } else if (existingItemType == HID::ReportType::Feature) {
569 // rename this existing item since it's a feature
570 existingItem.second->name = "feature-" + name;
571 }
572 }
573 }
574
575 // do the count now, after we did any renaming, since we might have
576 // N > 1 for the new name
577 int existingCount = countWithName(name);
578 if (existingCount > 0) {
579 if (existingCount == 1) {
580 // rename existing item 0 to have the "-0" suffix
581 auto existingItem = itemWithName(name);
582 existingItem.second->name += "-0";
583 }
584
585 // define the new nae
586 std::stringstream os;
587 os << name << "-" << existingCount;
588 name = os.str();
589 }
590
591 auto report = getReport(ty, item->report_id, true /* create */);
592 uint32_t bitOffset = report->currentBitSize();
593
594 if (debugEvents) {
595 SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << ": add:" << name << ", bits: " << bitOffset << ":" << (int) item->report_size
596 << ", report=" << (int) item->report_id);
597 }
598
599 Item* itemObject = new Item{name, bitOffset, item->report_size};
600 itemObject->isRelative = hid_parse_is_relative(item);
601 itemObject->doSignExtend = (item->logical_min < 0) || (item->logical_max < 0);
602 report->items.push_back(itemObject);
603 }
604
Close()605 void FGHIDDevice::Close()
606 {
607 if (_device) {
608 hid_close(_device);
609 _device = nullptr;
610 }
611 }
612
update(double dt)613 void FGHIDDevice::update(double dt)
614 {
615 if (!_device) {
616 return;
617 }
618
619 uint8_t reportBuf[65];
620 int readCount = 0;
621 while (true) {
622 readCount = hid_read_timeout(_device, reportBuf, sizeof(reportBuf), 0);
623
624 if (readCount <= 0) {
625 break;
626 }
627
628 int modifiers = fgGetKeyModifiers();
629 const uint8_t reportNumber = _haveNumberedReports ? reportBuf[0] : 0;
630 auto inputReport = getReport(HID::ReportType::In, reportNumber, false);
631 if (!inputReport) {
632 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: Unknown input report number:" <<
633 static_cast<int>(reportNumber));
634 } else {
635 uint8_t* reportBytes = _haveNumberedReports ? reportBuf + 1 : reportBuf;
636 size_t reportSize = _haveNumberedReports ? readCount - 1 : readCount;
637 processInputReport(inputReport, reportBytes, reportSize, dt, modifiers);
638 }
639 }
640
641 FGInputDevice::update(dt);
642
643 for (auto rep : _dirtyReports) {
644 sendReport(rep);
645 }
646
647 _dirtyReports.clear();
648 }
649
sendReport(Report * report) const650 void FGHIDDevice::sendReport(Report* report) const
651 {
652 if (!_device) {
653 return;
654 }
655
656 uint8_t reportBytes[65];
657 size_t reportLength = 0;
658 memset(reportBytes, 0, sizeof(reportBytes));
659 reportBytes[0] = report->number;
660
661 // fill in valid data
662 for (auto item : report->items) {
663 reportLength += item->bitSize;
664 if (item->lastValue == 0) {
665 continue;
666 }
667
668 writeBits(reportBytes + 1, item->bitOffset, item->bitSize, item->lastValue);
669 }
670
671 reportLength /= 8;
672
673 if (_debugRaw) {
674 std::ostringstream byteString;
675 for (size_t i=0; i<reportLength; ++i) {
676 byteString << hexTable[reportBytes[i] >> 4];
677 byteString << hexTable[reportBytes[i] & 0x0f];
678 byteString << " ";
679 }
680 SG_LOG(SG_INPUT, SG_INFO, "sending bytes: " << byteString.str());
681 }
682
683
684 // send the data, based on the report type
685 if (report->type == HID::ReportType::Feature) {
686 hid_send_feature_report(_device, reportBytes, reportLength + 1);
687 } else {
688 assert(report->type == HID::ReportType::Out);
689 hid_write(_device, reportBytes, reportLength + 1);
690 }
691 }
692
maybeSignExtend(Item * item,int inValue)693 int FGHIDDevice::maybeSignExtend(Item* item, int inValue)
694 {
695 return item->doSignExtend ? signExtend(inValue, item->bitSize) : inValue;
696 }
697
processInputReport(Report * report,unsigned char * data,size_t length,double dt,int keyModifiers)698 void FGHIDDevice::processInputReport(Report* report, unsigned char* data,
699 size_t length,
700 double dt, int keyModifiers)
701 {
702 if (_debugRaw) {
703 SG_LOG(SG_INPUT, SG_INFO, GetName() << " FGHIDDeivce received input report:" << (int) report->number << ", len=" << length);
704 {
705 std::ostringstream byteString;
706 for (size_t i=0; i<length; ++i) {
707 byteString << hexTable[data[i] >> 4];
708 byteString << hexTable[data[i] & 0x0f];
709 byteString << " ";
710 }
711 SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
712 }
713 }
714
715 for (auto item : report->items) {
716 int value = extractBits(data, length, item->bitOffset, item->bitSize);
717
718 value = maybeSignExtend(item, value);
719
720 // suppress events for values that aren't changing
721 if (item->isRelative) {
722 // supress spurious 0-valued relative events
723 if (value == 0) {
724 continue;
725 }
726 } else {
727 // supress no-change events for absolute items
728 if (value == item->lastValue) {
729 continue;
730 }
731 }
732
733 item->lastValue = value;
734 if (!item->event)
735 continue;
736
737 if (_debugRaw) {
738 SG_LOG(SG_INPUT, SG_INFO, "\titem:" << item->name << " = " << value);
739 }
740
741 HIDEventData event{item, value, dt, keyModifiers};
742 HandleEvent(event);
743 }
744 }
745
SendFeatureReport(unsigned int reportId,const std::string & data)746 void FGHIDDevice::SendFeatureReport(unsigned int reportId, const std::string& data)
747 {
748 if (!_device) {
749 return;
750 }
751
752 if (_debugRaw) {
753 SG_LOG(SG_INPUT, SG_INFO, GetName() << ": FGHIDDevice: Sending feature report:" << (int) reportId << ", len=" << data.size());
754 {
755 std::ostringstream byteString;
756
757 for (unsigned int i=0; i<data.size(); ++i) {
758 byteString << hexTable[data[i] >> 4];
759 byteString << hexTable[data[i] & 0x0f];
760 byteString << " ";
761 }
762 SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
763 }
764 }
765
766 uint8_t buf[65];
767 size_t len = std::min(data.length() + 1, sizeof(buf));
768 buf[0] = reportId;
769 memcpy(buf + 1, data.data(), len - 1);
770 size_t r = hid_send_feature_report(_device, buf, len);
771 if (r < 0) {
772 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: Sending feature report failed, error-string is:\n"
773 << simgear::strutils::error_string(errno));
774 }
775 }
776
TranslateEventName(FGEventData & eventData)777 const char *FGHIDDevice::TranslateEventName(FGEventData &eventData)
778 {
779 HIDEventData& hidEvent = static_cast<HIDEventData&>(eventData);
780 return hidEvent.item->name.c_str();
781 }
782
Send(const char * eventName,double value)783 void FGHIDDevice::Send(const char *eventName, double value)
784 {
785 auto item = itemWithName(eventName);
786 if (item.second == nullptr) {
787 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice:unknown item name:" << eventName);
788 return;
789 }
790
791 int intValue = static_cast<int>(value);
792 if (item.second->lastValue == intValue) {
793 return; // not actually changing
794 }
795
796 // update the stored value prior to sending
797 item.second->lastValue = intValue;
798 _dirtyReports.insert(item.first);
799 }
800
defineReport(SGPropertyNode_ptr reportNode)801 void FGHIDDevice::defineReport(SGPropertyNode_ptr reportNode)
802 {
803 const int nChildren = reportNode->nChildren();
804 uint32_t bitCount = 0;
805 const auto rty = HID::reportTypeFromString(reportNode->getStringValue("type"));
806 if (rty == HID::ReportType::Invalid) {
807 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: invalid report type:" <<
808 reportNode->getStringValue("type"));
809 return;
810 }
811
812 const auto id = reportNode->getIntValue("id");
813 if (id > 0) {
814 _haveNumberedReports = true;
815 }
816
817 auto report = new Report(rty, id);
818 _reports.push_back(report);
819
820 for (int c=0; c < nChildren; ++c) {
821 const auto nd = reportNode->getChild(c);
822 const int size = nd->getIntValue("size", 1); // default to a single bit
823 if (!strcmp(nd->getName(), "unused-bits")) {
824 bitCount += size;
825 continue;
826 }
827
828 if (!strcmp(nd->getName(), "type") || !strcmp(nd->getName(), "id")) {
829 continue; // already handled above
830 }
831
832 // allow repeating items
833 uint8_t count = nd->getIntValue("count", 1);
834 std::string name = nd->getNameString();
835 const auto lastHypen = name.rfind("-");
836 std::string baseName = name.substr(0, lastHypen + 1);
837 int baseIndex = std::stoi(name.substr(lastHypen + 1));
838
839 const bool isRelative = (name.find("rel-") == 0);
840 const bool isSigned = nd->getBoolValue("is-signed", false);
841
842 for (uint8_t i=0; i < count; ++i) {
843 std::ostringstream oss;
844 oss << baseName << (baseIndex + i);
845 Item* itemObject = new Item{oss.str(), bitCount, static_cast<uint8_t>(size)};
846 itemObject->isRelative = isRelative;
847 itemObject->doSignExtend = isSigned;
848 report->items.push_back(itemObject);
849 bitCount += size;
850 }
851 }
852 }
853
854
855 } // of anonymous namespace
856
857
extractBits(uint8_t * bytes,size_t lengthInBytes,size_t bitOffset,size_t bitSize)858 int extractBits(uint8_t* bytes, size_t lengthInBytes, size_t bitOffset, size_t bitSize)
859 {
860 const size_t wholeBytesToSkip = bitOffset >> 3;
861 const size_t offsetInByte = bitOffset & 0x7;
862
863 // work out how many whole bytes to copy
864 const size_t bytesToCopy = std::min(sizeof(uint32_t), (offsetInByte + bitSize + 7) / 8);
865 uint32_t v = 0;
866 // this goes from byte alignment to word alignment safely
867 memcpy((void*) &v, bytes + wholeBytesToSkip, bytesToCopy);
868
869 // shift down so lowest bit is aligned
870 v = v >> offsetInByte;
871
872 // mask off any extraneous top bits
873 const uint32_t mask = ~(0xffffffff << bitSize);
874 v &= mask;
875
876 return v;
877 }
878
signExtend(int inValue,size_t bitSize)879 int signExtend(int inValue, size_t bitSize)
880 {
881 const int m = 1U << (bitSize - 1);
882 return (inValue ^ m) - m;
883 }
884
writeBits(uint8_t * bytes,size_t bitOffset,size_t bitSize,int value)885 void writeBits(uint8_t* bytes, size_t bitOffset, size_t bitSize, int value)
886 {
887 size_t wholeBytesToSkip = bitOffset >> 3;
888 uint8_t* dataByte = bytes + wholeBytesToSkip;
889 size_t offsetInByte = bitOffset & 0x7;
890 size_t bitsInByte = std::min(bitSize, 8 - offsetInByte);
891 uint8_t mask = 0xff >> (8 - bitsInByte);
892
893 *dataByte |= ((value & mask) << offsetInByte);
894
895 if (bitsInByte < bitSize) {
896 // if we have more bits to write, recurse
897 writeBits(bytes, bitOffset + bitsInByte, bitSize - bitsInByte, value >> bitsInByte);
898 }
899 }
900
FGHIDEventInput()901 FGHIDEventInput::FGHIDEventInput() :
902 FGEventInput(),
903 d(new FGHIDEventInputPrivate)
904 {
905 d->p = this; // store back pointer to outer object on pimpl
906 }
907
~FGHIDEventInput()908 FGHIDEventInput::~FGHIDEventInput()
909 {
910 }
911
init()912 void FGHIDEventInput::init()
913 {
914 FGEventInput::init();
915 // have to wait until postinit since loading config files
916 // requires Nasal to be running
917 }
918
reinit()919 void FGHIDEventInput::reinit()
920 {
921 SG_LOG(SG_INPUT, SG_INFO, "Re-Initializing HID input bindings");
922 FGHIDEventInput::shutdown();
923 FGHIDEventInput::init();
924 FGHIDEventInput::postinit();
925 }
926
postinit()927 void FGHIDEventInput::postinit()
928 {
929 SG_LOG(SG_INPUT, SG_INFO, "HID event input starting up");
930
931 hid_init();
932
933 hid_device_info* devices = hid_enumerate(0 /* vendor ID */, 0 /* product ID */);
934
935 for (hid_device_info* curDev = devices; curDev != nullptr; curDev = curDev->next) {
936 d->evaluateDevice(curDev);
937 }
938
939 hid_free_enumeration(devices);
940 }
941
shutdown()942 void FGHIDEventInput::shutdown()
943 {
944 SG_LOG(SG_INPUT, SG_INFO, "HID event input shutting down");
945 FGEventInput::shutdown();
946
947 hid_exit();
948 }
949
950 //
951 // read all elements in each input device
952 //
update(double dt)953 void FGHIDEventInput::update(double dt)
954 {
955 FGEventInput::update(dt);
956 }
957
958
959 // Register the subsystem.
960 SGSubsystemMgr::Registrant<FGHIDEventInput> registrantFGHIDEventInput;
961
962 ///////////////////////////////////////////////////////////////////////////////////////////////
963
evaluateDevice(hid_device_info * deviceInfo)964 void FGHIDEventInput::FGHIDEventInputPrivate::evaluateDevice(hid_device_info* deviceInfo)
965 {
966 // allocate an input device, and add to the base class to see if we have
967 // a config
968 p->AddDevice(new FGHIDDevice(deviceInfo, p));
969 }
970
971 ///////////////////////////////////////////////////////////////////////////////////////////////
972