FlightGear next
FGHIDEventInput.cxx
Go to the documentation of this file.
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 <Main/fg_props.hxx>
31#include <hidapi/hidapi.h>
32#include <hidapi/hidparse.h>
33
34#include <simgear/structure/exception.hxx>
35#include <simgear/sg_inlines.h>
36#include <simgear/misc/strutils.hxx>
37#include <simgear/io/lowlevel.hxx>
38
39const char* hexTable = "0123456789ABCDEF";
40
41namespace HID
42{
43 enum class UsagePage
44 {
45 Undefined = 0x00,
47 Simulation = 0x02,
48 VR = 0x03,
49 Sport =0x04,
50 Game =0x05,
52 Keyboard = 0x07,
53 LEDs = 0x08,
54 Button = 0x09,
55 Ordinal = 0x0A,
56 Telephony = 0x0B,
57 Consumer = 0x0C,
58 Digitizer = 0x0D,
59 // reserved 0x0E
60 PID = 0x0F,
61 Unicode = 0x10,
67 Arcade = 0x91,
69 };
70
72 {
91 SC_Aileron = 0xB0,
101 SC_Rudder = 0xBA,
112 SC_Brake = 0xC5,
113 SC_Clutch = 0xC6,
124
125 };
126
127
129 {
130 // generic desktop section
133 GD_Mouse = 0x02,
138 GD_Keypad = 0x07,
179 GD_X = 0x30,
180 GD_Y = 0x31,
181 GD_Z = 0x32,
182 GD_Rx = 0x33,
183 GD_Ry = 0x34,
184 GD_Rz = 0x35,
185 GD_Slider = 0x36,
186 GD_Dial = 0x37,
187 GD_Wheel = 0x38,
192 GD_Start = 0x3D,
193 GD_Select = 0x3E,
195 GD_Vx = 0x40,
196 GD_Vy = 0x41,
197 GD_Vz = 0x42,
198 GD_Vbrx = 0x43,
199 GD_Vbry = 0x44,
200 GD_Vbrz = 0x45,
201 GD_Vno = 0x46,
204 GD_Qx = 0x49,
205 GD_Qy = 0x4A,
206 GD_Qz = 0x4B,
207 GD_Qw = 0x4C,
224 GD_DpadUp = 0x90,
255 GD_RPM= 0xC1,
276 };
277
279 {
285 LED_Kana = 0x05,
286 LED_Power = 0x06,
287 LED_Shift = 0x07,
289 LED_Mute = 0x09,
300 LED_CAV = 0x14,
301 LED_CLV = 0x15,
304 LED_Ring = 0x18,
312 LED_Hold = 0x20,
324 LED_Busy = 0x2C,
325 LED_Ready = 0x2D,
331 LED_Stop = 0x33,
334 LED_Play = 0x36,
335 LED_Pause = 0x37,
337 LED_Error = 0x39,
358 };
359
376
393
394 enum class ReportType
395 {
397 In = 0x08,
398 Out = 0x09,
399 Feature = 0x0B
400 };
401
402 std::string nameForUsage(uint32_t usagePage, uint32_t usage)
403 {
404 const auto enumUsage = static_cast<UsagePage>(usagePage);
405 if (enumUsage == UsagePage::Undefined) {
406 std::stringstream os;
407 os << "undefined-" << usage;
408 return os.str();
409 }
410
411 if (enumUsage == UsagePage::GenericDesktop) {
412 switch (usage) {
413 case GD_Undefined: return "undefined";
414 case GD_Pointer: return "pointer";
415 case GD_Mouse: return "mouse";
416 case GD_Reserved03: return "reserved03";
417 case GD_GamePad: return "gamepad";
418 case GD_Keyboard: return "keyboard";
419 case GD_Keypad: return "keypad";
420 case GD_Joystick: return "joystick";
421 case GD_Wheel: return "wheel";
422 case GD_Dial: return "dial";
423 case GD_Hatswitch: return "hat";
424 case GD_Slider: return "slider";
425 case GD_Rx: return "x-rotate";
426 case GD_Ry: return "y-rotate";
427 case GD_Rz: return "z-rotate";
428 case GD_X: return "x-translate";
429 case GD_Y: return "y-translate";
430 case GD_Z: return "z-translate";
431 case GD_WaterCoolingDevice: return "watercoolingdevice";
432 case GD_MultiAxisController: return "multiaxiscontroller";
433 case GD_TabletPCSysCtrls: return "tabletpcsysctrls";
434 case GD_CountedBuffer: return "countedbuffer";
435 case GD_ByteCount: return "bytecount";
436 case GD_MotionWakeUp: return "motionwakeup";
437 case GD_Start: return "start";
438 case GD_Select: return "select";
439 case GD_Vx: return "x-vector";
440 case GD_Vy: return "y-vector";
441 case GD_Vz: return "z-vector";
442 case GD_Vbrx: return "relative-x-vector";
443 case GD_Vbry: return "relative-y-vector";
444 case GD_Vbrz: return "relative-z-vector";
445 case GD_Vno: return "non-oriented-vector";
446 case GD_DpadUp: return "direction-pad-up";
447 case GD_DpadDown: return "direction-pad-down";
448 case GD_DpadRight: return "direction-pad-right";
449 case GD_DpadLeft: return "direction-pad-left";
450 case GD_ComputerChassisDevice: return "computerchassisdevice";
451 case GD_WirelessRadioControls: return "wirelessradiocontrols";
452 case GD_PortableDeviceControl: return "portabledevicecontrol";
453 case GD_SystemMultiAxisController: return "systemmultiaxiscontroller";
454 case GD_SpatialController: return "spatialcontroller";
455 case GD_AssistiveControl: return "assistivecontrol";
456 case GD_DeviceDock: return "devicedock";
457 case GD_DockableDevice: return "dockabledevice";
458 case GD_CallStateManagementControl: return "callstatemanagementcontrol";
459 case GD_FeatureNotification: return "featurenotification";
460 case GD_ResolutionMultiplier: return "resolutionmultiplier";
461 case GD_Qx: return "qx";
462 case GD_Qy: return "qy";
463 case GD_Qz: return "qz";
464 case GD_Qw: return "qw";
465 case GD_SystemControl: return "systemcontrol";
466 case GD_SystemPowerDown: return "systempowerdown";
467 case GD_SystemSleep: return "systemsleep";
468 case GD_SystemWakeUp: return "systemwakeup";
469 case GD_SystemContextMenu: return "systemcontextmenu";
470 case GD_SystemMainMenu: return "systemmainmenu";
471 case GD_SystemAppMenu: return "systemappmenu";
472 case GD_SystemMenuHelp: return "systemmenuhelp";
473 case GD_SystemMenuExit: return "systemmenuexit";
474 case GD_SystemMenuSelect: return "systemmenuselect";
475 case GD_SystemMenuRight: return "systemmenuright";
476 case GD_SystemMenuLeft: return "systemmenuleft";
477 case GD_SystemMenuUp: return "systemmenuup";
478 case GD_SystemMenuDown: return "systemmenudown";
479 case GD_SystemColdRestart: return "systemcoldrestart";
480 case GD_SystemWarmRestart: return "systemwarmrestart";
481 case GD_IndexTrigger: return "indextrigger";
482 case GD_PalmTrigger: return "palmtrigger";
483 case GD_Thumbstick: return "thumbstick";
484 case GD_SystemFunctionShift: return "systemfunctionshift";
485 case GD_SystemFunctionShiftLock: return "systemfunctinshiftlock";
486 case GD_SystemFunctionShiftLockIndicator: return "systemfunctionshiftlockindicator";
487 case GD_SystemDismissNotification: return "systemdismissnotification";
488 case GD_SystemDoNotDisturb: return "systemdonotdisturb";
489 case GD_SystemDock: return "systemdock";
490 case GD_SystemUndock: return "systemundock";
491 case GD_SystemSetup: return "systemsetup";
492 case GD_SystemBreak: return "systembreak";
493 case GD_SystemDebuggerBreak: return "systemdebuggerbreak";
494 case GD_ApplicationBreak: return "applicationbreak";
495 case GD_ApplicationDebuggerBreak: return "applicationdebuggerbreak";
496 case GD_SystemSpeakerMute: return "systemspeakermute";
497 case GD_SystemHibernate: return "systemhibernate";
498 case GD_SystemMicrophoneMute: return "systemmicrophonemute";
499 case GD_SystemDisplayInvert: return "systemdisplayinvert";
500 case GD_SystemDisplayInternal: return "systemdisplayinternal";
501 case GD_SystemDisplayExternal: return "systemdisplayexternal";
502 case GD_SystemDisplayBoth: return "systemdisplayboth";
503 case GD_SystemDisplayDual: return "systemdisplaydual";
504 case GD_SystemDisplayToggleIntExtMode: return "systemdisplaytoggleintextmode";
505 case GD_SystemDisplaySwapPrimarySecondary: return "systemdisplayswapprimarysecondary";
506 case GD_SystemDisplayToggleLCDAutoscale: return "systemdisplaytogglelcdautoscale";
507 case GD_SensorZone: return "SENSORZONE";
508 case GD_RPM: return "rpm";
509 case GD_CoolantLevel: return "coolantlevel";
510 case GD_CoolantCriticalLevel: return "coolantcriticallevel";
511 case GD_CoolantPump: return "coolant";
512 case GD_ChassisEnclosure: return "chassisenclosure";
513 case GD_WirelessRadioButton: return "wirelessradiobutton";
514 case GD_WirelessRadioLED: return "wirelessradioled";
515 case GD_WirelessRadioSliderSwitch: return "wirelessradiosliderswitch";
516 case GD_SystemDisplayRotationLockButton: return "systemdisplayrotationlockbutton";
517 case GD_SystemDisplayRotationLockSliderSwitch: return "systemdisplayrotationlocksliderswitch";
518 case GD_ControlEnable: return "controlenable";
519 case GD_DockableDeviceUniqueID: return "dockabledeviceuniqueid";
520 case GD_DockableDeviceVendorID: return "dockabledevicevendorid";
521 case GD_DockableDevicePrimaryUsagePage: return "dockabledeviceprimaryusagepage";
522 case GD_DockableDevicePrimaryUsageID: return "dockabledeviceprimaryusageid";
523 case GD_DockableDeviceDockingState: return "dockabledevicedockingstate";
524 case GD_DockableDeviceDisplayOcclusion: return "dockabledevicedisplayocclusion";
525 case GD_DockableDeviceObjectType: return "dockabledeviceobjecttype";
526 case GD_CallActiveLED: return "callactiveled";
527 case GD_CallMuteToggle: return "callmutetoggle";
528 case GD_CallMuteLED: return "callmuteled";
529 case GD_Reserved14: return "reserved14";
530 case GD_Reserved15: return "reserved15";
531 case GD_Reserved16: return "reserved16";
532 case GD_Reserved17: return "reserved17";
533 case GD_Reserved18: return "reserved18";
534 case GD_Reserved19: return "reserved19";
535 case GD_Reserved1A: return "reserved1a";
536 case GD_Reserved1B: return "reserved1b";
537 case GD_Reserved1C: return "reserved1c";
538 case GD_Reserved1D: return "reserved1d";
539 case GD_Reserved1E: return "reserved1e";
540 case GD_Reserved1F: return "reserved1f";
541 case GD_Reserved20: return "reserved20";
542 case GD_Reserved21: return "reserved21";
543 case GD_Reserved22: return "reserved22";
544 case GD_Reserved23: return "reserved23";
545 case GD_Reserved24: return "reserved24";
546 case GD_Reserved25: return "reserved25";
547 case GD_Reserved26: return "reserved26";
548 case GD_Reserved27: return "reserved27";
549 case GD_Reserved28: return "reserved28";
550 case GD_Reserved29: return "reserved29";
551 case GD_Reserved2A: return "reserved2a";
552 case GD_Reserved2B: return "reserved2b";
553 case GD_Reserved2C: return "reserved2c";
554 case GD_Reserved2D: return "reserved2d";
555 case GD_Reserved2E: return "reserved2e";
556 case GD_Reserved2F: return "reserved2f";
557 case GD_Reserved3F: return "reserved3f";
558
559 default:
560 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID generic desktop usage:" << usage);
561 }
562 } else if (enumUsage == UsagePage::Simulation) {
563 switch (usage) {
564 case SC_FlightSimulationDevice: return "flightsimulationdevice";
565 case SC_AutomobileSimulationDevice: return "AutomobileSimulationDevice";
566 case SC_TankSimulationDevice: return "tanksimulationdevice";
567 case SC_SpaceShipSimulationDevice: return "spaceshipsimulationdevice";
568 case SC_SubmarineSimulationDevice: return "submarinesimulationdevice";
569 case SC_SailingSimulationDevice: return "sailingsimulationdevice";
570 case SC_MotorcycleSimulationDevice: return "motorcyclesimulationdevice";
571 case SC_SportsSimulationDevice: return "sportssimulationdevice";
572 case SC_AirplaneSimulationDevice: return "airplanesimulationdevice";
573 case SC_HelicopterSimulationDevice: return "helicoptersimulationdevice";
574 case SC_MagicCarpetSimulationDevice: return "magiccarpetsimulationdevice";
575 case SC_BycicleSimulationDevice: return "byciclesimulationdevice";
576 case SC_FlightControlStick: return "flightcontrolstick";
577 case SC_FlightStick: return "flightstick";
578 case SC_CyclicControl: return "cycliccontrol";
579 case SC_CyclicTrim: return "cyclictrim";
580 case SC_FlightYoke: return "flightyoke";
581 case SC_TrackControl: return "trackcontrol";
582 case SC_Aileron: return "aileron";
583 case SC_AileronTrim: return "ailerontrim";
584 case SC_AntiTorqueControl: return "antitorquecontrol";
585 case SC_AutopilotEnable: return "autopilotenable";
586 case SC_ChaffRelease: return "chaffrelease";
587 case SC_CollectiveControl: return "collectivecontrol";
588 case SC_DiveBrake: return "divebrake";
589 case SC_ElectronicCountermeasures: return "electroniccountermeasures";
590 case SC_Elevator: return "elevator";
591 case SC_ElevatorTrim: return "elevatortrim";
592 case SC_Rudder: return "rudder";
593 case SC_Throttle: return "throttle";
594 case SC_FlightCommunications: return "flightcommunications";
595 case SC_FlareRelease: return "flarerelease";
596 case SC_LandingGear: return "landinggear";
597 case SC_ToeBrake: return "toebrake";
598 case SC_Trigger: return "trigger";
599 case SC_WeaponsArm: return "weaponsarm";
600 case SC_WeaponsSelect: return "weaponsselect";
601 case SC_WingFlaps: return "wingsflap";
602 case SC_Accelerator: return "accelerator";
603 case SC_Brake: return "brake";
604 case SC_Clutch: return "clutch";
605 case SC_Shifter: return "shifter";
606 case SC_Steering: return "steering";
607 case SC_TurretDirection: return "turretdirection";
608 case SC_BarrelElevation: return "barrelelevation";
609 case SC_DivePlane: return "diveplane";
610 case SC_Ballast: return "balast";
611 case SC_BicycleCrank: return "bicyclehandle";
612 case SC_HandleBars: return "handlebars";
613 case SC_FrontBrake: return "frontbrake";
614 case SC_RearBrake: return "rearbrake";
615 default:
616 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID simulation usage:" << usage);
617 }
618 } else if (enumUsage == UsagePage::Consumer) {
619 switch (usage) {
620 default:
621 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID consumer usage:" << usage);
622 }
623 } else if (enumUsage == UsagePage::AlphanumericDisplay) {
624 switch (usage) {
625 case AD_AlphanumericDisplay: return "alphanumeric";
626 case AD_CharacterReport: return "character-report";
627 case AD_DisplayData: return "display-data";
628 case AD_DisplayBrightness: return "display-brightness";
629 case AD_7SegmentDirectMap: return "seven-segment-direct";
630 case AD_14SegmentDirectMap: return "fourteen-segment-direct";
631
632 default:
633 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID alphanumeric usage:" << usage);
634 }
635 } else if (enumUsage == UsagePage::AlphanumericDisplay) {
636 switch (usage) {
637 case VR_Undefined: return "undefined-vr";
638 case VR_Belt: return "belt-vr";
639 case VR_BodySuit: return "bodysuit-vr";
640 case VR_Flexor: return "flexor-vr";
641 case VR_Glove: return "glove-vr";
642 case VR_HeadTracker: return "headtracker-vr";
643 case VR_HeadMountedDisplay: return "headmounteddisplay-vr";
644 case VR_HandTracker: return "handtracker-vr";
645 case VR_Oculometer: return "oculometer-vr";
646 case VR_Vest: return "vest-vr";
647 case VR_AnimatronicDevice: return "animatronicdevice-vr";
648 case VR_StereoEnable: return "stereoenable-vr";
649 case VR_DisplayEnable: return "displayenable-vr";
650 default:
651 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID VR usage:" << usage);
652 }
653 } else if (enumUsage == UsagePage::LEDs) {
654 switch (usage) {
655 case LED_Undefined: return "undefined-led";
656 case LED_NumLock: return "numlock-led";
657 case LED_CapsLock: return "capslock-led";
658 case LED_ScrollLock: return "scrolllock-led";
659 case LED_Compose: return "compose-led";
660 case LED_Kana: return "kana-led";
661 case LED_Power: return "power-led";
662 case LED_Shift: return "shift-led";
663 case LED_DoNotDisturb: return "donotdisturb-led";
664 case LED_Mute: return "mute-led";
665 case LED_ToneEnable: return "toneenable-led";
666 case LED_HighCutFilter: return "highcutfilter-led";
667 case LED_LowCutFilter: return "lowcutfilter-led";
668 case LED_EqualizerEnable: return "equalizerenable-led";
669 case LED_SoundFieldOn: return "soundfieldon-led";
670 case LED_SurroundOn: return "surroundon-led";
671 case LED_Repeat: return "repeat-led";
672 case LED_Stereo: return "stereo-led";
673 case LED_SampligRateDetect: return "samplingratedetect-led";
674 case LED_Spinning: return "spinning-led";
675 case LED_CAV: return "cav-led";
676 case LED_CLV: return "clv-led";
677 case LED_RecordingFormatDetect: return "recordingformatdetect-led";
678 case LED_OffHook: return "offhook-led";
679 case LED_Ring: return "ring-led";
680 case LED_MessageWaiting: return "messagewaiting-led";
681 case LED_DataMode: return "datamode-led";
682 case LED_BatteryOperation: return "batteryoperation-led";
683 case LED_BatteryOk: return "batteryok-led";
684 case LED_BatteryLow: return "batterylow-led";
685 case LED_Speaker: return "speaker-led";
686 case LED_HeadSet: return "headset-led";
687 case LED_Hold: return "hold-led";
688 case LED_Microphone: return "microphone-led";
689 case LED_Coverage: return "coverage-led";
690 case LED_NightMode: return "nightmode-led";
691 case LED_SendCalls: return "sendcalls-led";
692 case LED_CallPickup: return "callpickup-led";
693 case LED_Conference: return "conference-led";
694 case LED_StandBy: return "standby-led";
695 case LED_CameraOn: return "cameraon-led";
696 case LED_CameraOff: return "cameraoff-led";
697 case LED_OnLine: return "online-led";
698 case LED_OffLine: return "offline-led";
699 case LED_Busy: return "busy-led";
700 case LED_Ready: return "ready-led";
701 case LED_PaperOut: return "paperout-led";
702 case LED_PaperJam: return "paperjam-led";
703 case LED_Remote: return "remote-led";
704 case LED_Forward: return "forward-led";
705 case LED_Reverse: return "reverse-led";
706 case LED_Stop: return "stop=led";
707 case LED_Rewind: return "rewind-led";
708 case LED_FastForward: return "fastforward-led";
709 case LED_Play: return "play-led";
710 case LED_Pause: return "pause-led";
711 case LED_Record: return "record-led";
712 case LED_Error: return "error-led";
713 case LED_UsageSelectedIndicator: return "usageselectedindicator-led";
714 case LED_UsageInUseIndicator: return "usageinuseindicator-led";
715 case LED_UsageMultiModeIndicator: return "usagemultimodeindicator-led";
716 case LED_IndicatorOn: return "indicatoron-led";
717 case LED_IndicatorFlash: return "idicatorflash-led";
718 case LED_IndicatorSlowBlink: return "indicatorslowblink-led";
719 case LED_IndicatorFastBlink: return "indicatorfastblink-led";
720 case LED_IndicatorOff: return "indicatoroff-led";
721 case LED_FlashOnTime: return "flashontime-led";
722 case LED_SlowBlinkOnTime: return "slowblinkontime-led";
723 case LED_SlowBlinkOffTime: return "slowblinkofftime-led";
724 case LED_FastBlinkOnTime: return "fastblinkontime-led";
725 case LED_FastBlinkOfftime: return "fastblinkofftime-led";
726 case LED_UsageIndicatorColor: return "usageindicatorcolor-led";
727 case LED_IndicatorRed: return "usageindicatorred-led";
728 case LED_IndicatorGreen: return "usageindicatorgreen-led";
729 case LED_IndicatorAmber: return "usageindicatoramber-led";
730 case LED_GenericIndicator: return "usagegenericindicator-led";
731 case LED_SystemSuspend: return "usagesystemsuspend-led";
732 case LED_ExternalPowerConnected: return "externalpowerconnected-led";
733 default:
734 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID LED usage:" << usage);
735
736 }
737 } else if (enumUsage == UsagePage::Button) {
738 std::stringstream os;
739 os << "button-" << usage;
740 return os.str();
741 } else if (enumUsage >= UsagePage::VendorDefinedStart) {
742 return "vendor";
743 } else {
744 SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID usage page:" << std::hex << usagePage
745 << " with usage " << std::hex << usage);
746 }
747
748 return "unknown";
749 }
750
751 bool shouldPrefixWithAbs(uint32_t usagePage, uint32_t usage)
752 {
753 const auto enumUsage = static_cast<UsagePage>(usagePage);
754 if (enumUsage == UsagePage::GenericDesktop) {
755 switch (usage) {
756 case GD_Wheel:
757 case GD_Dial:
758 case GD_Hatswitch:
759 case GD_Slider:
760 case GD_Rx:
761 case GD_Ry:
762 case GD_Rz:
763 case GD_X:
764 case GD_Y:
765 case GD_Z:
766 return true;
767 default:
768 break;
769 }
770 }
771
772 return false;
773 }
774
775 ReportType reportTypeFromString(const std::string& s)
776 {
777 if (s == "input") return ReportType::In;
778 if (s == "output") return ReportType::Out;
779 if (s == "feature") return ReportType::Feature;
780 return ReportType::Invalid;
781 }
782} // of namespace
783
785{
786public:
787 FGHIDEventInput* p = nullptr;
788
789 void evaluateDevice(hid_device_info* deviceInfo);
790};
791
792// anonymous namespace to define our device subclass
793namespace
794{
795
796class FGHIDDevice : public FGInputDevice {
797public:
798 FGHIDDevice(hid_device_info* devInfo,
799 FGHIDEventInput* subsys);
800
801 virtual ~FGHIDDevice();
802
803 bool Open() override;
804 void Close() override;
805 void Configure(SGPropertyNode_ptr node) override;
806
807 void update(double dt) override;
808 const char *TranslateEventName(FGEventData &eventData) override;
809 void Send( const char * eventName, double value ) override;
810 void SendFeatureReport(unsigned int reportId, const std::string& data) override;
811
812 class Item
813 {
814 public:
815 Item(const std::string& n, uint32_t offset, uint8_t size) :
816 name(n),
817 bitOffset(offset),
818 bitSize(size)
819 {}
820
821 std::string name;
822 uint32_t bitOffset = 0; // form the start of the report
823 uint8_t bitSize = 1;
824 bool isRelative = false;
825 bool doSignExtend = false;
826 int lastValue = 0;
827 // int defaultValue = 0;
828 // range, units, etc not needed for now
829 // hopefully this doesn't need to be a list
830 FGInputEvent_ptr event;
831 };
832private:
833 class Report
834 {
835 public:
836 Report(HID::ReportType ty, uint8_t n = 0) : type(ty), number(n) {}
837
838 HID::ReportType type;
839 uint8_t number = 0;
840 std::vector<Item*> items;
841
842 uint32_t currentBitSize() const
843 {
844 uint32_t size = 0;
845 for (auto i : items) {
846 size += i->bitSize;
847 }
848 return size;
849 }
850 };
851
852 bool parseUSBHIDDescriptor();
853 void parseCollection(hid_item* collection);
854 void parseItem(hid_item* item);
855
856 Report* getReport(HID::ReportType ty, uint8_t number, bool doCreate = false);
857
858 void sendReport(Report* report) const;
859
860 uint8_t countWithName(const std::string& name) const;
861 std::pair<Report*, Item*> itemWithName(const std::string& name) const;
862
863 void processInputReport(Report* report, unsigned char* data, size_t length,
864 double dt, int keyModifiers);
865
866 int maybeSignExtend(Item* item, int inValue);
867
868 void defineReport(SGPropertyNode_ptr reportNode);
869
870 std::vector<Report*> _reports;
871 std::string _hidPath;
872 hid_device* _device = nullptr;
873 bool _haveNumberedReports = false;
874 bool _debugRaw = false;
875
879 bool _haveLocalDescriptor = false;
880
882 std::vector<uint8_t>_rawXMLDescriptor;
883
884 // all sets which will be send on the next update() call.
885 std::set<Report*> _dirtyReports;
886};
887
888class HIDEventData : public FGEventData
889{
890public:
891 // item, value, dt, keyModifiers
892 HIDEventData(FGHIDDevice::Item* it, int value, double dt, int keyMods) :
893 FGEventData(value, dt, keyMods),
894 item(it)
895 {
896 assert(item);
897 }
898
899 FGHIDDevice::Item* item = nullptr;
900};
901
902FGHIDDevice::FGHIDDevice(hid_device_info *devInfo, FGHIDEventInput *)
903{
904 class_id = "FGHIDDevice";
905 _hidPath = devInfo->path;
906
907 std::wstring manufacturerName, productName;
908 productName = devInfo->product_string ? std::wstring(devInfo->product_string)
909 : L"unknown HID device";
910
911 if (devInfo->manufacturer_string) {
912 manufacturerName = std::wstring(devInfo->manufacturer_string);
913 SetName(simgear::strutils::convertWStringToUtf8(manufacturerName) + " " +
914 simgear::strutils::convertWStringToUtf8(productName));
915 } else {
916 SetName(simgear::strutils::convertWStringToUtf8(productName));
917 }
918
919 const auto serial = devInfo->serial_number;
920 std::string path(devInfo->path);
921 // most devices return an empty serial number, unfortunately
922 if ((serial != nullptr) && std::wcslen(serial) > 0) {
923 SetSerialNumber(simgear::strutils::convertWStringToUtf8(serial));
924 }
925
926 SG_LOG(SG_INPUT, SG_DEBUG, "HID device:" << GetName() << " at path " << _hidPath);
927}
928
929FGHIDDevice::~FGHIDDevice()
930{
931 if (_device) {
932 hid_close(_device);
933 }
934}
935
936void FGHIDDevice::Configure(SGPropertyNode_ptr node)
937{
938 // base class first
940
941 if (node->hasChild("hid-descriptor")) {
942 _haveLocalDescriptor = true;
943 if (debugEvents) {
944 SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using local HID descriptor");
945 }
946
947 for (auto report : node->getChild("hid-descriptor")->getChildren("report")) {
948 defineReport(report);
949 }
950 }
951
952 if (node->hasChild("hid-raw-descriptor")) {
953 _rawXMLDescriptor = simgear::strutils::decodeHex(node->getStringValue("hid-raw-descriptor"));
954 if (debugEvents) {
955 SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using XML-defined raw HID descriptor");
956 }
957 }
958
959 if (node->getBoolValue("hid-debug-raw")) {
960 _debugRaw = true;
961 }
962}
963
964bool FGHIDDevice::Open()
965{
966 SG_LOG(SG_INPUT, SG_INFO, "HID open " << GetUniqueName());
967 _device = hid_open_path(_hidPath.c_str());
968 if (_device == nullptr) {
969 SG_LOG(SG_INPUT, SG_WARN, GetUniqueName() << ": HID: Failed to open:" << _hidPath);
970 SG_LOG(SG_INPUT, SG_WARN, "\tnote on Linux you may need to adjust permissions of the device using UDev rules.");
971 return false;
972 }
973
974#if !defined(SG_WINDOWS)
975 if (_rawXMLDescriptor.empty()) {
976 _rawXMLDescriptor.resize(2048);
977 int descriptorSize = hid_get_descriptor(_device, _rawXMLDescriptor.data(), _rawXMLDescriptor.size());
978 if (descriptorSize <= 0) {
979 SG_LOG(SG_INPUT, SG_WARN, "HID: " << GetUniqueName() << " failed to read HID descriptor");
980 return false;
981 }
982
983 _rawXMLDescriptor.resize(descriptorSize);
984 }
985#endif
986
987 if (!_haveLocalDescriptor) {
988 bool ok = parseUSBHIDDescriptor();
989 if (!ok)
990 return false;
991 }
992
993 for (auto& v : handledEvents) {
994 auto reportItem = itemWithName(v.first);
995 if (!reportItem.second) {
996 SG_LOG(SG_INPUT, SG_WARN, "HID device:" << GetUniqueName() << " has no element for event:" << v.first);
997 continue;
998 }
999
1000 FGInputEvent_ptr event = v.second;
1001 if (debugEvents) {
1002 SG_LOG(SG_INPUT, SG_INFO, "\tfound item for event:" << v.first);
1003 }
1004
1005 reportItem.second->event = event;
1006 }
1007
1008 return true;
1009}
1010
1011bool FGHIDDevice::parseUSBHIDDescriptor()
1012{
1013#if defined(SG_WINDOWS)
1014 if (_rawXMLDescriptor.empty()) {
1015 SG_LOG(SG_INPUT, SG_ALERT, GetUniqueName() << ": on Windows, there is no way to extract the UDB-HID report descriptor. "
1016 << "\nPlease supply the report descriptor in the device XML configuration.");
1017 SG_LOG(SG_INPUT, SG_ALERT, "See this page:<> for information on extracting the report descriptor on Windows");
1018 return false;
1019 }
1020#endif
1021
1022 if (_debugRaw) {
1023 SG_LOG(SG_INPUT, SG_INFO, "\nHID: descriptor for:" << GetUniqueName());
1024 {
1025 std::ostringstream byteString;
1026
1027 for (size_t i = 0; i < _rawXMLDescriptor.size(); ++i) {
1028 byteString << hexTable[_rawXMLDescriptor[i] >> 4];
1029 byteString << hexTable[_rawXMLDescriptor[i] & 0x0f];
1030 byteString << " ";
1031 }
1032 SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
1033 }
1034 }
1035
1036 hid_item* rootItem = nullptr;
1037 hid_parse_reportdesc(_rawXMLDescriptor.data(), _rawXMLDescriptor.size(), &rootItem);
1038 if (debugEvents) {
1039 SG_LOG(SG_INPUT, SG_INFO, "\nHID: scan for:" << GetUniqueName());
1040 }
1041
1042 parseCollection(rootItem);
1043
1044 hid_free_reportdesc(rootItem);
1045 return true;
1046}
1047
1048void FGHIDDevice::parseCollection(hid_item* c)
1049{
1050 for (hid_item* child = c->collection; child != nullptr; child = child->next) {
1051 if (child->collection) {
1052 parseCollection(child);
1053 } else {
1054 // leaf item
1055 parseItem(child);
1056 }
1057 }
1058}
1059
1060auto FGHIDDevice::getReport(HID::ReportType ty, uint8_t number, bool doCreate) -> Report*
1061{
1062 if (number > 0) {
1063 _haveNumberedReports = true;
1064 }
1065
1066 for (auto report : _reports) {
1067 if ((report->type == ty) && (report->number == number)) {
1068 return report;
1069 }
1070 }
1071
1072 if (doCreate) {
1073 auto r = new Report{ty, number};
1074 _reports.push_back(r);
1075 return r;
1076 } else {
1077 return nullptr;
1078 }
1079}
1080
1081auto FGHIDDevice::itemWithName(const std::string& name) const -> std::pair<Report*, Item*>
1082{
1083 for (auto report : _reports) {
1084 for (auto item : report->items) {
1085 if (item->name == name) {
1086 return std::make_pair(report, item);
1087 }
1088 }
1089 }
1090
1091 return std::make_pair(static_cast<Report*>(nullptr), static_cast<Item*>(nullptr));
1092}
1093
1094uint8_t FGHIDDevice::countWithName(const std::string& name) const
1095{
1096 uint8_t result = 0;
1097 size_t nameLength = name.length();
1098
1099 for (auto report : _reports) {
1100 for (auto item : report->items) {
1101 if (strncmp(name.c_str(), item->name.c_str(), nameLength) == 0) {
1102 result++;
1103 }
1104 }
1105 }
1106
1107 return result;
1108}
1109
1110void FGHIDDevice::parseItem(hid_item* item)
1111{
1112 std::string name = HID::nameForUsage(item->usage >> 16, item->usage & 0xffff);
1113 if (hid_parse_is_relative(item)) {
1114 name = "rel-" + name; // prefix relative names
1115 } else if (HID::shouldPrefixWithAbs(item->usage >> 16, item->usage & 0xffff)) {
1116 name = "abs-" + name;
1117 }
1118
1119 const auto ty = static_cast<HID::ReportType>(item->type);
1120 auto existingItem = itemWithName(name);
1121 if (existingItem.second) {
1122 // type fixup
1123 const HID::ReportType existingItemType = existingItem.first->type;
1124 if (existingItemType != ty) {
1125 // might be an item named identically in input/output and feature reports
1126 // -> prefix the feature one with 'feature'
1127 if (ty == HID::ReportType::Feature) {
1128 name = "feature-" + name;
1129 } else if (existingItemType == HID::ReportType::Feature) {
1130 // rename this existing item since it's a feature
1131 existingItem.second->name = "feature-" + name;
1132 }
1133 }
1134 }
1135
1136 // do the count now, after we did any renaming, since we might have
1137 // N > 1 for the new name
1138 int existingCount = countWithName(name);
1139 if (existingCount > 0) {
1140 if (existingCount == 1) {
1141 // rename existing item 0 to have the "-0" suffix
1142 auto existingItem = itemWithName(name);
1143 existingItem.second->name += "-0";
1144 }
1145
1146 // define the new nae
1147 std::stringstream os;
1148 os << name << "-" << existingCount;
1149 name = os.str();
1150 }
1151
1152 auto report = getReport(ty, item->report_id, true /* create */);
1153 uint32_t bitOffset = report->currentBitSize();
1154
1155 if (debugEvents) {
1156 SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << ": add:" << name << ", bits: " << bitOffset << ":" << (int) item->report_size
1157 << ", report=" << (int) item->report_id);
1158 }
1159
1160 Item* itemObject = new Item{name, bitOffset, item->report_size};
1161 itemObject->isRelative = hid_parse_is_relative(item);
1162 itemObject->doSignExtend = (item->logical_min < 0) || (item->logical_max < 0);
1163 report->items.push_back(itemObject);
1164}
1165
1166void FGHIDDevice::Close()
1167{
1168 if (_device) {
1169 hid_close(_device);
1170 _device = nullptr;
1171 }
1172}
1173
1174void FGHIDDevice::update(double dt)
1175{
1176 if (!_device) {
1177 return;
1178 }
1179
1180 uint8_t reportBuf[65];
1181 int readCount = 0;
1182 while (true) {
1183 readCount = hid_read_timeout(_device, reportBuf, sizeof(reportBuf), 0);
1184
1185 if (readCount <= 0) {
1186 break;
1187 }
1188
1189 int modifiers = fgGetKeyModifiers();
1190 const uint8_t reportNumber = _haveNumberedReports ? reportBuf[0] : 0;
1191 auto inputReport = getReport(HID::ReportType::In, reportNumber, false);
1192 if (!inputReport) {
1193 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: Unknown input report number:" <<
1194 static_cast<int>(reportNumber));
1195 } else {
1196 uint8_t* reportBytes = _haveNumberedReports ? reportBuf + 1 : reportBuf;
1197 size_t reportSize = _haveNumberedReports ? readCount - 1 : readCount;
1198 processInputReport(inputReport, reportBytes, reportSize, dt, modifiers);
1199 }
1200 }
1201
1203
1204 for (auto rep : _dirtyReports) {
1205 sendReport(rep);
1206 }
1207
1208 _dirtyReports.clear();
1209}
1210
1211void FGHIDDevice::sendReport(Report* report) const
1212{
1213 if (!_device) {
1214 return;
1215 }
1216
1217 uint8_t reportBytes[65];
1218 size_t reportLength = 0;
1219 memset(reportBytes, 0, sizeof(reportBytes));
1220 reportBytes[0] = report->number;
1221
1222// fill in valid data
1223 for (auto item : report->items) {
1224 reportLength += item->bitSize;
1225 if (item->lastValue == 0) {
1226 continue;
1227 }
1228
1229 writeBits(reportBytes + 1, item->bitOffset, item->bitSize, item->lastValue);
1230 }
1231
1232 reportLength /= 8;
1233
1234 if (_debugRaw) {
1235 std::ostringstream byteString;
1236 for (size_t i=0; i<reportLength; ++i) {
1237 byteString << hexTable[reportBytes[i] >> 4];
1238 byteString << hexTable[reportBytes[i] & 0x0f];
1239 byteString << " ";
1240 }
1241 SG_LOG(SG_INPUT, SG_INFO, "sending bytes: " << byteString.str());
1242 }
1243
1244
1245// send the data, based on the report type
1246 if (report->type == HID::ReportType::Feature) {
1247 hid_send_feature_report(_device, reportBytes, reportLength + 1);
1248 } else {
1249 assert(report->type == HID::ReportType::Out);
1250 hid_write(_device, reportBytes, reportLength + 1);
1251 }
1252}
1253
1254int FGHIDDevice::maybeSignExtend(Item* item, int inValue)
1255{
1256 return item->doSignExtend ? signExtend(inValue, item->bitSize) : inValue;
1257}
1258
1259void FGHIDDevice::processInputReport(Report* report, unsigned char* data,
1260 size_t length,
1261 double dt, int keyModifiers)
1262{
1263 if (_debugRaw) {
1264 SG_LOG(SG_INPUT, SG_INFO, GetName() << " FGHIDDeivce received input report:" << (int) report->number << ", len=" << length);
1265 {
1266 std::ostringstream byteString;
1267 for (size_t i=0; i<length; ++i) {
1268 byteString << hexTable[data[i] >> 4];
1269 byteString << hexTable[data[i] & 0x0f];
1270 byteString << " ";
1271 }
1272 SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
1273 }
1274 }
1275
1276 for (auto item : report->items) {
1277 int value = extractBits(data, length, item->bitOffset, item->bitSize);
1278
1279 value = maybeSignExtend(item, value);
1280
1281 // suppress events for values that aren't changing
1282 if (item->isRelative) {
1283 // supress spurious 0-valued relative events
1284 if (value == 0) {
1285 continue;
1286 }
1287 } else {
1288 // supress no-change events for absolute items
1289 if (value == item->lastValue) {
1290 continue;
1291 }
1292 }
1293
1294 item->lastValue = value;
1295 if (!item->event)
1296 continue;
1297
1298 if (_debugRaw) {
1299 SG_LOG(SG_INPUT, SG_INFO, "\titem:" << item->name << " = " << value);
1300 }
1301
1302 HIDEventData event{item, value, dt, keyModifiers};
1303 HandleEvent(event);
1304 }
1305}
1306
1307void FGHIDDevice::SendFeatureReport(unsigned int reportId, const std::string& data)
1308{
1309 if (!_device) {
1310 return;
1311 }
1312
1313 if (_debugRaw) {
1314 SG_LOG(SG_INPUT, SG_INFO, GetName() << ": FGHIDDevice: Sending feature report:" << (int) reportId << ", len=" << data.size());
1315 {
1316 std::ostringstream byteString;
1317
1318 for (unsigned int i=0; i<data.size(); ++i) {
1319 byteString << hexTable[data[i] >> 4];
1320 byteString << hexTable[data[i] & 0x0f];
1321 byteString << " ";
1322 }
1323 SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
1324 }
1325 }
1326
1327 uint8_t buf[65];
1328 size_t len = std::min(data.length() + 1, sizeof(buf));
1329 buf[0] = reportId;
1330 memcpy(buf + 1, data.data(), len - 1);
1331 size_t r = hid_send_feature_report(_device, buf, len);
1332 if (r < 0) {
1333 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: Sending feature report failed, error-string is:\n"
1334 << simgear::strutils::error_string(errno));
1335 }
1336}
1337
1338const char *FGHIDDevice::TranslateEventName(FGEventData &eventData)
1339{
1340 HIDEventData& hidEvent = static_cast<HIDEventData&>(eventData);
1341 return hidEvent.item->name.c_str();
1342}
1343
1344void FGHIDDevice::Send(const char *eventName, double value)
1345{
1346 auto item = itemWithName(eventName);
1347 if (item.second == nullptr) {
1348 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice:unknown item name:" << eventName);
1349 return;
1350 }
1351
1352 int intValue = static_cast<int>(value);
1353 if (item.second->lastValue == intValue) {
1354 return; // not actually changing
1355 }
1356
1357 lastEventName->setStringValue(eventName);
1358 lastEventValue->setDoubleValue(value);
1359
1360 // update the stored value prior to sending
1361 item.second->lastValue = intValue;
1362 _dirtyReports.insert(item.first);
1363}
1364
1365void FGHIDDevice::defineReport(SGPropertyNode_ptr reportNode)
1366{
1367 const int nChildren = reportNode->nChildren();
1368 uint32_t bitCount = 0;
1369 const auto rty = HID::reportTypeFromString(reportNode->getStringValue("type"));
1370 if (rty == HID::ReportType::Invalid) {
1371 SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: invalid report type:" <<
1372 reportNode->getStringValue("type"));
1373 return;
1374 }
1375
1376 const auto id = reportNode->getIntValue("id");
1377 if (id > 0) {
1378 _haveNumberedReports = true;
1379 }
1380
1381 auto report = new Report(rty, id);
1382 _reports.push_back(report);
1383
1384 for (int c=0; c < nChildren; ++c) {
1385 const auto nd = reportNode->getChild(c);
1386 const int size = nd->getIntValue("size", 1); // default to a single bit
1387 if (nd->getNameString() == "unused-bits") {
1388 bitCount += size;
1389 continue;
1390 }
1391
1392 if (nd->getNameString() == "type" || nd->getNameString() == "id") {
1393 continue; // already handled above
1394 }
1395
1396 // allow repeating items
1397 uint8_t count = nd->getIntValue("count", 1);
1398 std::string name = nd->getNameString();
1399 const auto lastHypen = name.rfind("-");
1400 std::string baseName = name.substr(0, lastHypen + 1);
1401 int baseIndex = std::stoi(name.substr(lastHypen + 1));
1402
1403 const bool isRelative = (name.find("rel-") == 0);
1404 const bool isSigned = nd->getBoolValue("is-signed", false);
1405
1406 for (uint8_t i=0; i < count; ++i) {
1407 std::ostringstream oss;
1408 oss << baseName << (baseIndex + i);
1409 Item* itemObject = new Item{oss.str(), bitCount, static_cast<uint8_t>(size)};
1410 itemObject->isRelative = isRelative;
1411 itemObject->doSignExtend = isSigned;
1412 report->items.push_back(itemObject);
1413 bitCount += size;
1414 }
1415 }
1416}
1417
1418
1419} // of anonymous namespace
1420
1421
1422int extractBits(uint8_t* bytes, size_t lengthInBytes, size_t bitOffset, size_t bitSize)
1423{
1424 const size_t wholeBytesToSkip = bitOffset >> 3;
1425 const size_t offsetInByte = bitOffset & 0x7;
1426
1427 // work out how many whole bytes to copy
1428 const size_t bytesToCopy = std::min(sizeof(uint32_t), (offsetInByte + bitSize + 7) / 8);
1429 uint32_t v = 0;
1430 // this goes from byte alignment to word alignment safely
1431 memcpy((void*) &v, bytes + wholeBytesToSkip, bytesToCopy);
1432
1433 // shift down so lowest bit is aligned
1434 v = v >> offsetInByte;
1435
1436 // mask off any extraneous top bits
1437 const uint32_t mask = ~(0xffffffff << bitSize);
1438 v &= mask;
1439
1440 return v;
1441}
1442
1443int signExtend(int inValue, size_t bitSize)
1444{
1445 const int m = 1U << (bitSize - 1);
1446 return (inValue ^ m) - m;
1447}
1448
1449void writeBits(uint8_t* bytes, size_t bitOffset, size_t bitSize, int value)
1450{
1451 size_t wholeBytesToSkip = bitOffset >> 3;
1452 uint8_t* dataByte = bytes + wholeBytesToSkip;
1453 size_t offsetInByte = bitOffset & 0x7;
1454 size_t bitsInByte = std::min(bitSize, 8 - offsetInByte);
1455 uint8_t mask = 0xff >> (8 - bitsInByte);
1456
1457 *dataByte |= ((value & mask) << offsetInByte);
1458
1459 if (bitsInByte < bitSize) {
1460 // if we have more bits to write, recurse
1461 writeBits(bytes, bitOffset + bitsInByte, bitSize - bitsInByte, value >> bitsInByte);
1462 }
1463}
1464
1465FGHIDEventInput::FGHIDEventInput() : FGEventInput("Input/HID", "/input/hid"),
1467{
1468 d->p = this; // store back pointer to outer object on pimpl
1469}
1470
1474
1476{
1477 SG_LOG(SG_INPUT, SG_INFO, "Re-Initializing HID input bindings");
1481}
1482
1484{
1485 SG_LOG(SG_INPUT, SG_INFO, "HID event input starting up");
1486
1487 hid_init();
1488
1489 hid_device_info* devices = hid_enumerate(0 /* vendor ID */, 0 /* product ID */);
1490
1491 for (hid_device_info* curDev = devices; curDev != nullptr; curDev = curDev->next) {
1492 d->evaluateDevice(curDev);
1493 }
1494
1495 hid_free_enumeration(devices);
1496}
1497
1499{
1500 SG_LOG(SG_INPUT, SG_INFO, "HID event input shutting down");
1502
1503 hid_exit();
1504}
1505
1506//
1507// read all elements in each input device
1508//
1510{
1512}
1513
1514
1515// Register the subsystem.
1516SGSubsystemMgr::Registrant<FGHIDEventInput> registrantFGHIDEventInput;
1517
1519
1521{
1522 // allocate an input device, and add to the base class to see if we have
1523 // a config
1524 p->AddDevice(new FGHIDDevice(deviceInfo, p));
1525}
1526
class SGSharedPtr< FGInputEvent > FGInputEvent_ptr
const char * hexTable
int extractBits(uint8_t *bytes, size_t lengthInBytes, size_t bitOffset, size_t bitSize)
int signExtend(int inValue, size_t bitSize)
void writeBits(uint8_t *bytes, size_t bitOffset, size_t bitSize, int value)
SGSubsystemMgr::Registrant< FGHIDEventInput > registrantFGHIDEventInput
#define i(x)
void update(double dt) override
void shutdown() override
void init() override
void evaluateDevice(hid_device_info *deviceInfo)
void reinit() override
void shutdown() override
void postinit() override
void update(double dt) override
virtual void Configure(SGPropertyNode_ptr deviceNode)
virtual void update(double dt)
const char * name
int fgGetKeyModifiers()
AlphanumericDisplayUsage
@ AD_BitmappedDisplay
@ AD_CharacterReport
@ AD_DisplayContrast
@ AD_DisplayControlReport
@ AD_DisplayBrightness
@ AD_AlphanumericDisplay
@ AD_14SegmentDirectMap
@ AD_7SegmentDirectMap
@ GD_SystemWarmRestart
@ GD_DockableDeviceObjectType
@ GD_AssistiveControl
@ GD_ComputerChassisDevice
@ GD_WirelessRadioButton
@ GD_WirelessRadioControls
@ GD_SystemDismissNotification
@ GD_SystemMenuLeft
@ GD_SystemSpeakerMute
@ GD_SystemDisplayDual
@ GD_DockableDeviceDockingState
@ GD_ResolutionMultiplier
@ GD_SystemColdRestart
@ GD_DockableDeviceVendorID
@ GD_SystemMicrophoneMute
@ GD_SystemDisplayRotationLockSliderSwitch
@ GD_CallStateManagementControl
@ GD_TabletPCSysCtrls
@ GD_SystemDisplayInternal
@ GD_PortableDeviceControl
@ GD_SystemMenuHelp
@ GD_SystemPowerDown
@ GD_SystemDebuggerBreak
@ GD_SystemDisplayExternal
@ GD_SystemMenuDown
@ GD_SystemDisplayRotationLockButton
@ GD_SystemDisplaySwapPrimarySecondary
@ GD_SpatialController
@ GD_DockableDevicePrimaryUsageID
@ GD_DockableDeviceUniqueID
@ GD_SystemFunctionShiftLockIndicator
@ GD_SystemContextMenu
@ GD_DockableDevicePrimaryUsagePage
@ GD_SystemDoNotDisturb
@ GD_WirelessRadioLED
@ GD_SystemDisplayInvert
@ GD_DockableDevice
@ GD_SystemFunctionShift
@ GD_SystemMenuSelect
@ GD_CoolantCriticalLevel
@ GD_SystemMainMenu
@ GD_SystemHibernate
@ GD_WirelessRadioSliderSwitch
@ GD_SystemDisplayBoth
@ GD_FeatureNotification
@ GD_WaterCoolingDevice
@ GD_ApplicationDebuggerBreak
@ GD_MultiAxisController
@ GD_SystemMenuExit
@ GD_SystemMultiAxisController
@ GD_ChassisEnclosure
@ GD_SystemFunctionShiftLock
@ GD_DockableDeviceDisplayOcclusion
@ GD_CallMuteToggle
@ GD_ApplicationBreak
@ GD_SystemMenuRight
@ GD_SystemDisplayToggleLCDAutoscale
@ GD_SystemDisplayToggleIntExtMode
@ SC_FlightCommunications
@ SC_AntiTorqueControl
@ SC_FlightSimulationDevice
@ SC_ElectronicCountermeasures
@ SC_SubmarineSimulationDevice
@ SC_TurretDirection
@ SC_SpaceShipSimulationDevice
@ SC_ChaffRelease
@ SC_SportsSimulationDevice
@ SC_AirplaneSimulationDevice
@ SC_TankSimulationDevice
@ SC_TrackControl
@ SC_AutomobileSimulationDevice
@ SC_BycicleSimulationDevice
@ SC_MagicCarpetSimulationDevice
@ SC_FlightControlStick
@ SC_MotorcycleSimulationDevice
@ SC_CollectiveControl
@ SC_BarrelElevation
@ SC_HelicopterSimulationDevice
@ SC_CyclicControl
@ SC_AutopilotEnable
@ SC_SailingSimulationDevice
@ VR_HeadMountedDisplay
@ VR_AnimatronicDevice
std::string nameForUsage(uint32_t usagePage, uint32_t usage)
bool shouldPrefixWithAbs(uint32_t usagePage, uint32_t usage)
@ LED_FastBlinkOnTime
@ LED_HighCutFilter
@ LED_IndicatorAmber
@ LED_BatteryOperation
@ LED_IndicatorSlowBlink
@ LED_UsageMultiModeIndicator
@ LED_SampligRateDetect
@ LED_EqualizerEnable
@ LED_UsageIndicatorColor
@ LED_SystemSuspend
@ LED_GenericIndicator
@ LED_UsageInUseIndicator
@ LED_FastBlinkOfftime
@ LED_ExternalPowerConnected
@ LED_UsageSelectedIndicator
@ LED_IndicatorGreen
@ LED_SlowBlinkOnTime
@ LED_MessageWaiting
@ LED_RecordingFormatDetect
@ LED_SlowBlinkOffTime
@ LED_IndicatorFlash
@ LED_IndicatorFastBlink
ReportType reportTypeFromString(const std::string &s)
void report(Airplane *a)
int usage()