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