Event input layer based on HID-Api
Thus runs in parallel to the existing implementation on Linux and Mac, but can (soon) replace the Mac code and will run on Windows eventually.
This commit is contained in:
parent
c14f37edc0
commit
e920dc7509
13 changed files with 1007 additions and 77 deletions
|
@ -207,6 +207,7 @@ option(ENABLE_QT "Set to ON to build the internal Qt launcher" ON)
|
|||
option(ENABLE_TRAFFIC "Set to ON to build the external traffic generator modules" ON)
|
||||
option(ENABLE_FGQCANVAS "Set to ON to build the Qt-based remote canvas application" OFF)
|
||||
option(ENABLE_DEMCONVERT "Set to ON to build the dem conversion tool (default)" ON)
|
||||
option(ENABLE_HID_INPUT "Set to ON to build HID-based input code (default)" OFF)
|
||||
|
||||
include (DetectArch)
|
||||
|
||||
|
@ -239,6 +240,8 @@ include( ConfigureMsvc3rdParty )
|
|||
if(EVENT_INPUT)
|
||||
if(APPLE)
|
||||
add_definitions(-DWITH_EVENTINPUT)
|
||||
find_library(IOKIT_FRAMEWORK IOKit)
|
||||
list(APPEND EVENT_INPUT_LIBRARIES ${IOKIT_FRAMEWORK})
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux|FreeBSD")
|
||||
if(NOT UDEV_FOUND)
|
||||
message(WARNING "UDev not found, event input is disabled!")
|
||||
|
@ -252,6 +255,10 @@ if(EVENT_INPUT)
|
|||
message(WARNING "event-based input is not supported on this platform yet")
|
||||
endif()
|
||||
|
||||
if (ENABLE_HID_INPUT)
|
||||
list(APPEND EVENT_INPUT_LIBRARIES hidapi)
|
||||
endif()
|
||||
|
||||
# Keep PLIB INPUT enabled as long as EventInput does not replace current joystick configurations.
|
||||
set(ENABLE_PLIB_JOYSTICK 1)
|
||||
else(EVENT_INPUT)
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
#cmakedefine HAVE_CRASHRPT
|
||||
|
||||
#cmakedefine ENABLE_FLITE
|
||||
#cmakedefine ENABLE_HID_INPUT
|
||||
|
||||
#cmakedefine HAVE_QT
|
||||
|
||||
|
|
|
@ -10,6 +10,11 @@ else()
|
|||
set(EVENT_INPUT_HEADERS FGLinuxEventInput.hxx)
|
||||
endif()
|
||||
|
||||
if (ENABLE_HID_INPUT)
|
||||
list(APPEND EVENT_INPUT_SOURCES FGHIDEventInput.cxx)
|
||||
list(APPEND EVENT_INPUT_HEADERS FGHIDEventInput.hxx)
|
||||
endif()
|
||||
|
||||
|
||||
set(SOURCES
|
||||
FGButton.cxx
|
||||
|
@ -65,4 +70,12 @@ if(ENABLE_JS_DEMO)
|
|||
install(TARGETS js_demo RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
if (COMMAND flightgear_test)
|
||||
set(HID_INPUT_TEST_SOURCES test_hidinput.cxx FGEventInput.cxx
|
||||
FGCommonInput.cxx FGDEviceConfigurationMap.cxx)
|
||||
|
||||
flightgear_test(hidinput "${HID_INPUT_TEST_SOURCES}")
|
||||
target_link_libraries(hidinput ${EVENT_INPUT_LIBRARIES} hidapi)
|
||||
endif()
|
||||
|
||||
flightgear_component(Input "${SOURCES}" "${HEADERS}")
|
||||
|
|
|
@ -28,8 +28,6 @@
|
|||
|
||||
#include "FGDeviceConfigurationMap.hxx"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#include <simgear/misc/sg_dir.hxx>
|
||||
#include <simgear/props/props_io.hxx>
|
||||
#include <simgear/structure/exception.hxx>
|
||||
|
@ -45,9 +43,15 @@ FGDeviceConfigurationMap::FGDeviceConfigurationMap( const string& relative_path,
|
|||
const std::string& nodeName)
|
||||
{
|
||||
// scan for over-ride configurations, loaded via joysticks.xml, etc
|
||||
BOOST_FOREACH(SGPropertyNode_ptr preloaded, nodePath->getChildren(nodeName)) {
|
||||
BOOST_FOREACH(SGPropertyNode* nameProp, preloaded->getChildren("name")) {
|
||||
overrideDict[nameProp->getStringValue()] = preloaded;
|
||||
for (auto preloaded : nodePath->getChildren(nodeName)) {
|
||||
// allow specifying a serial number in the override
|
||||
std::string serial = preloaded->getStringValue("serial-number");
|
||||
if (!serial.empty()) {
|
||||
serial = "::" + serial;
|
||||
}
|
||||
|
||||
for (auto nameProp : preloaded->getChildren("name")) {
|
||||
overrideDict[nameProp->getStringValue() + serial] = preloaded;
|
||||
} // of names iteration
|
||||
} // of defined overrides iteration
|
||||
|
||||
|
@ -62,15 +66,15 @@ FGDeviceConfigurationMap::~FGDeviceConfigurationMap()
|
|||
SGPropertyNode_ptr
|
||||
FGDeviceConfigurationMap::configurationForDeviceName(const std::string& name)
|
||||
{
|
||||
NameNodeMap::iterator j = overrideDict.find(name);
|
||||
auto j = overrideDict.find(name);
|
||||
if (j != overrideDict.end()) {
|
||||
return j->second;
|
||||
}
|
||||
|
||||
// no override, check out list of config files
|
||||
NamePathMap::iterator it = namePathMap.find(name);
|
||||
auto it = namePathMap.find(name);
|
||||
if (it == namePathMap.end()) {
|
||||
return SGPropertyNode_ptr();
|
||||
return {};
|
||||
}
|
||||
|
||||
SGPropertyNode_ptr result(new SGPropertyNode);
|
||||
|
@ -79,14 +83,15 @@ FGDeviceConfigurationMap::configurationForDeviceName(const std::string& name)
|
|||
result->setStringValue("source", it->second.utf8Str());
|
||||
} catch (sg_exception&) {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "parse failure reading:" << it->second);
|
||||
return NULL;
|
||||
return {};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FGDeviceConfigurationMap::hasConfiguration(const std::string& name) const
|
||||
{
|
||||
NameNodeMap::const_iterator j = overrideDict.find(name);
|
||||
auto j = overrideDict.find(name);
|
||||
if (j != overrideDict.end()) {
|
||||
return true;
|
||||
}
|
||||
|
@ -107,7 +112,7 @@ void FGDeviceConfigurationMap::scan_dir(const SGPath & path)
|
|||
simgear::PathList children = dir.children(simgear::Dir::TYPE_FILE |
|
||||
simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
|
||||
|
||||
BOOST_FOREACH(SGPath path, children) {
|
||||
for (SGPath path : children) {
|
||||
if (path.isDir()) {
|
||||
scan_dir(path);
|
||||
} else if (path.extension() == "xml") {
|
||||
|
@ -124,14 +129,12 @@ void FGDeviceConfigurationMap::scan_dir(const SGPath & path)
|
|||
|
||||
void FGDeviceConfigurationMap::readCachedData(const SGPath& path)
|
||||
{
|
||||
flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
|
||||
NamePathMap::iterator it;
|
||||
BOOST_FOREACH(string s, cache->readStringListProperty(path.utf8Str())) {
|
||||
auto cache = flightgear::NavDataCache::instance();
|
||||
for (string s : cache->readStringListProperty(path.utf8Str())) {
|
||||
// important - only insert if not already present. This ensures
|
||||
// user configs can override those in the base package, since they are
|
||||
// searched first.
|
||||
it = namePathMap.find(s);
|
||||
if (it == namePathMap.end()) {
|
||||
if (namePathMap.find(s) == namePathMap.end()) {
|
||||
namePathMap.insert(std::make_pair(s, path));
|
||||
}
|
||||
} // of cached names iteration
|
||||
|
@ -148,18 +151,22 @@ void FGDeviceConfigurationMap::refreshCacheForFile(const SGPath& path)
|
|||
return;
|
||||
}
|
||||
|
||||
NamePathMap::iterator it;
|
||||
std::string serial = n->getStringValue("serial-number");
|
||||
if (!serial.empty()) {
|
||||
serial = "::" + serial;
|
||||
}
|
||||
|
||||
string_list names;
|
||||
BOOST_FOREACH(SGPropertyNode* nameProp, n->getChildren("name")) {
|
||||
names.push_back(nameProp->getStringValue());
|
||||
for (auto nameProp : n->getChildren("name")) {
|
||||
const string name = nameProp->getStringValue() + serial;
|
||||
names.push_back(name);
|
||||
// same comment as readCachedData: only insert if not already present
|
||||
it = namePathMap.find(names.back());
|
||||
if (it == namePathMap.end()) {
|
||||
namePathMap.insert(std::make_pair(names.back(), path));
|
||||
if (namePathMap.find(name) == namePathMap.end()) {
|
||||
namePathMap.insert(std::make_pair(name, path));
|
||||
}
|
||||
}
|
||||
|
||||
flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
|
||||
auto cache = flightgear::NavDataCache::instance();
|
||||
cache->stampCacheFile(path);
|
||||
cache->writeStringListProperty(path.utf8Str(), names);
|
||||
}
|
||||
|
|
|
@ -102,9 +102,8 @@ FGInputEvent::FGInputEvent( FGInputDevice * aDevice, SGPropertyNode_ptr node ) :
|
|||
|
||||
read_bindings( node, bindings, KEYMOD_NONE, device->GetNasalModule() );
|
||||
|
||||
PropertyList settingNodes = node->getChildren("setting");
|
||||
for( PropertyList::iterator it = settingNodes.begin(); it != settingNodes.end(); ++it )
|
||||
settings.push_back( new FGEventSetting( *it ) );
|
||||
for (auto node : node->getChildren("setting"))
|
||||
settings.push_back( new FGEventSetting(node) );
|
||||
}
|
||||
|
||||
FGInputEvent::~FGInputEvent()
|
||||
|
@ -113,11 +112,11 @@ FGInputEvent::~FGInputEvent()
|
|||
|
||||
void FGInputEvent::update( double dt )
|
||||
{
|
||||
for( setting_list_t::iterator it = settings.begin(); it != settings.end(); ++it ) {
|
||||
if( (*it)->Test() ) {
|
||||
double value = (*it)->GetValue();
|
||||
for (auto setting : settings) {
|
||||
if( setting->Test() ) {
|
||||
const double value = setting->GetValue();
|
||||
if( value != lastSettingValue ) {
|
||||
device->Send( GetName(), (*it)->GetValue() );
|
||||
device->Send( GetName(), value );
|
||||
lastSettingValue = value;
|
||||
}
|
||||
}
|
||||
|
@ -275,6 +274,14 @@ void FGInputDevice::Configure( SGPropertyNode_ptr aDeviceNode )
|
|||
|
||||
}
|
||||
|
||||
void FGInputDevice::AddHandledEvent( FGInputEvent_ptr event )
|
||||
{
|
||||
auto it = handledEvents.find(event->GetName());
|
||||
if (it == handledEvents.end()) {
|
||||
handledEvents.insert(it, std::make_pair(event->GetName(), event));
|
||||
}
|
||||
}
|
||||
|
||||
void FGInputDevice::update( double dt )
|
||||
{
|
||||
for( map<string,FGInputEvent_ptr>::iterator it = handledEvents.begin(); it != handledEvents.end(); it++ )
|
||||
|
@ -292,9 +299,10 @@ void FGInputDevice::update( double dt )
|
|||
void FGInputDevice::HandleEvent( FGEventData & eventData )
|
||||
{
|
||||
string eventName = TranslateEventName( eventData );
|
||||
if( debugEvents )
|
||||
cout << GetName() << " has event " <<
|
||||
eventName << " modifiers=" << eventData.modifiers << " value=" << eventData.value << endl;
|
||||
if( debugEvents ) {
|
||||
SG_LOG(SG_INPUT, SG_INFO, GetName() << " has event " <<
|
||||
eventName << " modifiers=" << eventData.modifiers << " value=" << eventData.value);
|
||||
}
|
||||
|
||||
if( handledEvents.count( eventName ) > 0 ) {
|
||||
handledEvents[ eventName ]->fire( eventData );
|
||||
|
@ -306,6 +314,11 @@ void FGInputDevice::SetName( string name )
|
|||
this->name = name;
|
||||
}
|
||||
|
||||
void FGInputDevice::SetSerialNumber( std::string serial )
|
||||
{
|
||||
serialNumber = serial;
|
||||
}
|
||||
|
||||
void FGInputDevice::SendFeatureReport(unsigned int reportId, const std::string& data)
|
||||
{
|
||||
SG_LOG(SG_INPUT, SG_WARN, "SendFeatureReport not implemented");
|
||||
|
@ -335,9 +348,6 @@ void FGEventInput::shutdown()
|
|||
|
||||
void FGEventInput::init( )
|
||||
{
|
||||
SG_LOG(SG_INPUT, SG_DEBUG, "Initializing event bindings");
|
||||
// SGPropertyNode * base = fgGetNode("/input/event", true);
|
||||
|
||||
}
|
||||
|
||||
void FGEventInput::postinit ()
|
||||
|
@ -354,20 +364,40 @@ void FGEventInput::update( double dt )
|
|||
unsigned FGEventInput::AddDevice( FGInputDevice * inputDevice )
|
||||
{
|
||||
SGPropertyNode_ptr baseNode = fgGetNode( PROPERTY_ROOT, true );
|
||||
SGPropertyNode_ptr deviceNode = NULL;
|
||||
SGPropertyNode_ptr deviceNode;
|
||||
|
||||
const string deviceName = inputDevice->GetName();
|
||||
SGPropertyNode_ptr configNode;
|
||||
|
||||
if (!inputDevice->GetSerialNumber().empty()) {
|
||||
const string nameWithSerial = deviceName + "::" + inputDevice->GetSerialNumber();
|
||||
if (configMap.hasConfiguration(nameWithSerial)) {
|
||||
configNode = configMap.configurationForDeviceName(nameWithSerial);
|
||||
}
|
||||
}
|
||||
|
||||
if (configNode == nullptr) {
|
||||
if (!configMap.hasConfiguration(deviceName)) {
|
||||
SG_LOG(SG_INPUT, SG_DEBUG, "No configuration found for device " << deviceName <<
|
||||
" (with serial: " << inputDevice->GetSerialNumber() << ")");
|
||||
delete inputDevice;
|
||||
return INVALID_DEVICE_INDEX;
|
||||
}
|
||||
configNode = configMap.configurationForDeviceName(deviceName);
|
||||
}
|
||||
|
||||
// look for configuration in the device map
|
||||
if ( configMap.hasConfiguration( inputDevice->GetName() ) ) {
|
||||
// found - copy to /input/event/device[n]
|
||||
|
||||
// find a free index
|
||||
unsigned index;
|
||||
for( index = 0; index < MAX_DEVICES; index++ )
|
||||
if( (deviceNode = baseNode->getNode( "device", index, false ) ) == NULL )
|
||||
unsigned int index;
|
||||
for ( index = 0; index < MAX_DEVICES; index++ ) {
|
||||
if ( (deviceNode = baseNode->getNode( "device", index, false ) ) == nullptr )
|
||||
break;
|
||||
}
|
||||
|
||||
if (index == MAX_DEVICES) {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "To many event devices - ignoring " << inputDevice->GetName() );
|
||||
delete inputDevice;
|
||||
return INVALID_DEVICE_INDEX;
|
||||
}
|
||||
|
||||
|
@ -375,15 +405,7 @@ unsigned FGEventInput::AddDevice( FGInputDevice * inputDevice )
|
|||
deviceNode = baseNode->getNode( "device", index, true );
|
||||
|
||||
// and copy the properties from the configuration tree
|
||||
copyProperties( configMap.configurationForDeviceName(inputDevice->GetName()), deviceNode );
|
||||
|
||||
}
|
||||
|
||||
if( deviceNode == NULL ) {
|
||||
SG_LOG(SG_INPUT, SG_DEBUG, "No configuration found for device " << inputDevice->GetName() );
|
||||
delete inputDevice;
|
||||
return INVALID_DEVICE_INDEX;
|
||||
}
|
||||
copyProperties(configNode, deviceNode );
|
||||
|
||||
inputDevice->Configure( deviceNode );
|
||||
|
||||
|
@ -447,6 +469,11 @@ std::string FGReportSetting::reportBytes(const std::string& moduleName) const
|
|||
}
|
||||
|
||||
naRef module = nas->getModule(moduleName.c_str());
|
||||
if (naIsNil(module)) {
|
||||
SG_LOG(SG_IO, SG_WARN, "No such Nasal module:" << moduleName);
|
||||
return {};
|
||||
}
|
||||
|
||||
naRef func = naHash_cget(module, (char*) nasalFunction.c_str());
|
||||
if (!naIsFunc(func)) {
|
||||
return std::string();
|
||||
|
|
|
@ -217,8 +217,9 @@ typedef class SGSharedPtr<FGInputEvent> FGInputEvent_ptr;
|
|||
*/
|
||||
class FGInputDevice : public SGReferenced {
|
||||
public:
|
||||
FGInputDevice() : debugEvents(false), grab(false) {}
|
||||
FGInputDevice( std::string aName ) : name(aName), debugEvents(false), grab(false) {}
|
||||
FGInputDevice() {}
|
||||
FGInputDevice( std::string aName, std::string aSerial = {} ) :
|
||||
name(aName), serialNumber(aSerial) {}
|
||||
|
||||
virtual ~FGInputDevice();
|
||||
|
||||
|
@ -239,12 +240,12 @@ public:
|
|||
void SetName( std::string name );
|
||||
std::string & GetName() { return name; }
|
||||
|
||||
void SetSerialNumber( std::string serial );
|
||||
std::string& GetSerialNumber() { return serialNumber; }
|
||||
|
||||
void HandleEvent( FGEventData & eventData );
|
||||
|
||||
virtual void AddHandledEvent( FGInputEvent_ptr handledEvent ) {
|
||||
if( handledEvents.count( handledEvent->GetName() ) == 0 )
|
||||
handledEvents[handledEvent->GetName()] = handledEvent;
|
||||
}
|
||||
virtual void AddHandledEvent( FGInputEvent_ptr handledEvent );
|
||||
|
||||
virtual void Configure( SGPropertyNode_ptr deviceNode );
|
||||
|
||||
|
@ -256,20 +257,24 @@ public:
|
|||
|
||||
const std::string & GetNasalModule() const { return nasalModule; }
|
||||
|
||||
private:
|
||||
protected:
|
||||
// A map of events, this device handles
|
||||
std::map<std::string,FGInputEvent_ptr> handledEvents;
|
||||
|
||||
// the device has a name to be recognized
|
||||
std::string name;
|
||||
|
||||
// serial number string to disambiguate multiple instances
|
||||
// of the same device
|
||||
std::string serialNumber;
|
||||
|
||||
// print out events comming in from the device
|
||||
// if true
|
||||
bool debugEvents;
|
||||
bool debugEvents = false;
|
||||
|
||||
// grab the device exclusively, if O/S supports this
|
||||
// so events are not sent to other applications
|
||||
bool grab;
|
||||
bool grab = false;
|
||||
|
||||
SGPropertyNode_ptr deviceNode;
|
||||
std::string nasalModule;
|
||||
|
|
726
src/Input/FGHIDEventInput.cxx
Normal file
726
src/Input/FGHIDEventInput.cxx
Normal file
|
@ -0,0 +1,726 @@
|
|||
// FGHIDEventInput.cxx -- handle event driven input devices via HIDAPI
|
||||
//
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2017, James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "FGHIDEventInput.hxx"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cassert>
|
||||
|
||||
#include <hidapi/hidapi.h>
|
||||
#include <hidapi/hidparse.h>
|
||||
|
||||
#include <simgear/structure/exception.hxx>
|
||||
#include <simgear/sg_inlines.h>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/io/lowlevel.hxx>
|
||||
|
||||
|
||||
namespace HID
|
||||
{
|
||||
enum class UsagePage
|
||||
{
|
||||
Undefined = 0,
|
||||
GenericDesktop,
|
||||
Simulation,
|
||||
VR,
|
||||
Sport,
|
||||
Game,
|
||||
GenericDevice,
|
||||
Keyboard,
|
||||
LEDs,
|
||||
Button,
|
||||
Ordinal,
|
||||
Telephony,
|
||||
Consumer,
|
||||
Digitizer,
|
||||
// reserved 0x0E
|
||||
// PID 0x0f
|
||||
Unicode = 0x10,
|
||||
AlphanumericDisplay = 0x14,
|
||||
|
||||
VendorDefinedStart = 0xFF00
|
||||
};
|
||||
|
||||
enum GenericDesktopUsage
|
||||
{
|
||||
// generic desktop section
|
||||
GD_Joystick = 0x04,
|
||||
GD_GamePad = 0x05,
|
||||
GD_Keyboard = 0x06,
|
||||
GD_Keypad = 0x07,
|
||||
GD_MultiAxisController = 0x08,
|
||||
GD_X = 0x30,
|
||||
GD_Y,
|
||||
GD_Z,
|
||||
GD_Rx,
|
||||
GD_Ry,
|
||||
GD_Rz,
|
||||
GD_Slider,
|
||||
GD_Dial,
|
||||
GD_Wheel,
|
||||
GD_Hatswitch,
|
||||
GD_DpadUp = 0x90,
|
||||
GD_DpadDown,
|
||||
GD_DpadRight,
|
||||
GD_DpadLeft
|
||||
};
|
||||
|
||||
enum LEDUsage
|
||||
{
|
||||
LED_Undefined = 0,
|
||||
LED_Play = 0x36,
|
||||
LED_Pause = 0x37,
|
||||
LED_GenericIndicator = 0x4B
|
||||
};
|
||||
|
||||
enum AlphanumericUsage
|
||||
{
|
||||
AD_AlphanumericDisplay = 0x01,
|
||||
AD_BitmappedDisplay = 0x2,
|
||||
AD_DisplayControlReport = 0x24,
|
||||
AD_ClearDisplay = 0x25,
|
||||
AD_CharacterReport = 0x2B,
|
||||
AD_DisplayData = 0x2C,
|
||||
AD_DisplayStatus = 0x2D,
|
||||
AD_DisplayBrightness = 0x46,
|
||||
AD_DisplayContrast = 0x47
|
||||
};
|
||||
|
||||
enum class ReportType
|
||||
{
|
||||
In = 0x08,
|
||||
Out = 0x09,
|
||||
Feature = 0x0B
|
||||
};
|
||||
|
||||
std::string nameForUsage(uint32_t usagePage, uint32_t usage)
|
||||
{
|
||||
const auto enumUsage = static_cast<UsagePage>(usagePage);
|
||||
if (enumUsage == UsagePage::GenericDesktop) {
|
||||
switch (usage) {
|
||||
case GD_Joystick: return "joystick";
|
||||
case GD_Wheel: return "wheel";
|
||||
case GD_Dial: return "dial";
|
||||
case GD_Hatswitch: return "hat";
|
||||
case GD_Slider: return "slider";
|
||||
case GD_Rx: return "x-rotate";
|
||||
case GD_Ry: return "y-rotate";
|
||||
case GD_Rz: return "z-rotate";
|
||||
case GD_X: return "x-translate";
|
||||
case GD_Y: return "y-translate";
|
||||
case GD_Z: return "z-translate";
|
||||
default:
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID generic desktop usage:" << usage);
|
||||
}
|
||||
} else if (enumUsage == UsagePage::Simulation) {
|
||||
switch (usage) {
|
||||
default:
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID simulation usage:" << usage);
|
||||
}
|
||||
} else if (enumUsage == UsagePage::Consumer) {
|
||||
switch (usage) {
|
||||
default:
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID consumer usage:" << usage);
|
||||
}
|
||||
} else if (enumUsage == UsagePage::AlphanumericDisplay) {
|
||||
switch (usage) {
|
||||
case AD_AlphanumericDisplay: return "alphanumeric";
|
||||
case AD_CharacterReport: return "character-report";
|
||||
case AD_DisplayData: return "display-data";
|
||||
case AD_DisplayBrightness: return "display-brightness";
|
||||
|
||||
default:
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID alphanumeric usage:" << usage);
|
||||
}
|
||||
} else if (enumUsage == UsagePage::LEDs) {
|
||||
switch (usage) {
|
||||
case LED_GenericIndicator: return "led-misc";
|
||||
case LED_Pause: return "led-pause";
|
||||
default:
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID LED usage:" << usage);
|
||||
|
||||
}
|
||||
} else if (enumUsage == UsagePage::Button) {
|
||||
std::stringstream os;
|
||||
os << "button-" << usage;
|
||||
return os.str();
|
||||
} else if (enumUsage >= UsagePage::VendorDefinedStart) {
|
||||
return "vendor";
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Unhandled HID usage page:" << std::hex << usagePage
|
||||
<< " with usage " << std::hex << usage);
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
bool shouldPrefixWithAbs(uint32_t usagePage, uint32_t usage)
|
||||
{
|
||||
const auto enumUsage = static_cast<UsagePage>(usagePage);
|
||||
if (enumUsage == UsagePage::GenericDesktop) {
|
||||
switch (usage) {
|
||||
case GD_Wheel:
|
||||
case GD_Dial:
|
||||
case GD_Hatswitch:
|
||||
case GD_Slider:
|
||||
case GD_Rx:
|
||||
case GD_Ry:
|
||||
case GD_Rz:
|
||||
case GD_X:
|
||||
case GD_Y:
|
||||
case GD_Z:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // of namespace
|
||||
|
||||
class FGHIDEventInput::FGHIDEventInputPrivate
|
||||
{
|
||||
public:
|
||||
FGHIDEventInput* p = nullptr;
|
||||
|
||||
void evaluateDevice(hid_device_info* deviceInfo);
|
||||
};
|
||||
|
||||
// anonymous namespace to define our device subclass
|
||||
namespace
|
||||
{
|
||||
|
||||
class FGHIDDevice : public FGInputDevice {
|
||||
public:
|
||||
FGHIDDevice(hid_device_info* devInfo,
|
||||
FGHIDEventInput* subsys);
|
||||
|
||||
virtual ~FGHIDDevice();
|
||||
|
||||
void Open() override;
|
||||
void Close() override;
|
||||
|
||||
void update(double dt) override;
|
||||
const char *TranslateEventName(FGEventData &eventData) override;
|
||||
void Send( const char * eventName, double value ) override;
|
||||
void SendFeatureReport(unsigned int reportId, const std::string& data) override;
|
||||
|
||||
class Item
|
||||
{
|
||||
public:
|
||||
Item(const std::string& n, uint32_t offset, uint8_t size) :
|
||||
name(n),
|
||||
bitOffset(offset),
|
||||
bitSize(size)
|
||||
{}
|
||||
|
||||
std::string name;
|
||||
uint32_t bitOffset = 0; // form the start of the report
|
||||
uint8_t bitSize = 1;
|
||||
bool isRelative = false;
|
||||
bool doSignExtend = false;
|
||||
int lastValue = 0;
|
||||
// int defaultValue = 0;
|
||||
// range, units, etc not needed for now
|
||||
// hopefully this doesn't need to be a list
|
||||
FGInputEvent_ptr event;
|
||||
};
|
||||
private:
|
||||
class Report
|
||||
{
|
||||
public:
|
||||
Report(HID::ReportType ty, uint8_t n = 0) : type(ty), number(n) {}
|
||||
|
||||
HID::ReportType type;
|
||||
uint8_t number = 0;
|
||||
std::vector<Item*> items;
|
||||
|
||||
uint32_t currentBitSize() const
|
||||
{
|
||||
uint32_t size = 0;
|
||||
for (auto i : items) {
|
||||
size += i->bitSize;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
void scanCollection(hid_item* collection);
|
||||
void scanItem(hid_item* item);
|
||||
|
||||
Report* getReport(HID::ReportType ty, uint8_t number, bool doCreate = false);
|
||||
|
||||
void sendReport(Report* report) const;
|
||||
|
||||
uint8_t countWithName(const std::string& name) const;
|
||||
std::pair<Report*, Item*> itemWithName(const std::string& name) const;
|
||||
|
||||
void processInputReport(Report* report, unsigned char* data, size_t length,
|
||||
double dt, int keyModifiers);
|
||||
|
||||
int maybeSignExtend(Item* item, int inValue);
|
||||
|
||||
std::vector<Report*> _reports;
|
||||
std::string _hidPath;
|
||||
hid_device* _device = nullptr;
|
||||
bool _haveNumberedReports = false;
|
||||
|
||||
// all sets which will be send on the next update() call.
|
||||
std::set<Report*> _dirtyReports;
|
||||
};
|
||||
|
||||
class HIDEventData : public FGEventData
|
||||
{
|
||||
public:
|
||||
// item, value, dt, keyModifiers
|
||||
HIDEventData(FGHIDDevice::Item* it, int value, double dt, int keyMods) :
|
||||
FGEventData(value, dt, keyMods),
|
||||
item(it)
|
||||
{
|
||||
assert(item);
|
||||
}
|
||||
|
||||
FGHIDDevice::Item* item = nullptr;
|
||||
};
|
||||
|
||||
FGHIDDevice::FGHIDDevice(hid_device_info *devInfo, FGHIDEventInput *)
|
||||
{
|
||||
_hidPath = devInfo->path;
|
||||
|
||||
std::wstring manufactuerName = std::wstring(devInfo->manufacturer_string),
|
||||
productName = std::wstring(devInfo->product_string);
|
||||
const auto serial = devInfo->serial_number;
|
||||
|
||||
auto path = devInfo->path;
|
||||
|
||||
SetName(simgear::strutils::convertWStringToUtf8(manufactuerName) + " " +
|
||||
simgear::strutils::convertWStringToUtf8(productName));
|
||||
SetSerialNumber(simgear::strutils::convertWStringToUtf8(serial));
|
||||
}
|
||||
|
||||
FGHIDDevice::~FGHIDDevice()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FGHIDDevice::Open()
|
||||
{
|
||||
_device = hid_open_path(_hidPath.c_str());
|
||||
if (_device == 0) {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "Failed to open:" << _hidPath);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char reportDescriptor[1024];
|
||||
int descriptorSize = hid_get_descriptor(_device, reportDescriptor, 1024);
|
||||
|
||||
hid_item* rootItem = nullptr;
|
||||
hid_parse_reportdesc(reportDescriptor, descriptorSize, &rootItem);
|
||||
|
||||
scanCollection(rootItem);
|
||||
|
||||
hid_free_reportdesc(rootItem);
|
||||
|
||||
for (auto& v : handledEvents) {
|
||||
auto reportItem = itemWithName(v.first);
|
||||
if (!reportItem.second) {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "HID device has no element for event:" << v.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
FGInputEvent_ptr event = v.second;
|
||||
// SG_LOG(SG_INPUT, SG_INFO, "found item for event:" << v.first);
|
||||
reportItem.second->event = event;
|
||||
}
|
||||
}
|
||||
|
||||
void FGHIDDevice::scanCollection(hid_item* c)
|
||||
{
|
||||
for (hid_item* child = c->collection; child != nullptr; child = child->next) {
|
||||
if (child->collection) {
|
||||
scanCollection(child);
|
||||
} else {
|
||||
// leaf item
|
||||
scanItem(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto FGHIDDevice::getReport(HID::ReportType ty, uint8_t number, bool doCreate) -> Report*
|
||||
{
|
||||
if (number > 0) {
|
||||
_haveNumberedReports = true;
|
||||
}
|
||||
|
||||
for (auto report : _reports) {
|
||||
if ((report->type == ty) && (report->number == number)) {
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
if (doCreate) {
|
||||
auto r = new Report{ty, number};
|
||||
_reports.push_back(r);
|
||||
return r;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
auto FGHIDDevice::itemWithName(const std::string& name) const -> std::pair<Report*, Item*>
|
||||
{
|
||||
for (auto report : _reports) {
|
||||
for (auto item : report->items) {
|
||||
if (item->name == name) {
|
||||
return std::make_pair(report, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(static_cast<Report*>(nullptr), static_cast<Item*>(nullptr));
|
||||
}
|
||||
|
||||
uint8_t FGHIDDevice::countWithName(const std::string& name) const
|
||||
{
|
||||
uint8_t result = 0;
|
||||
size_t nameLength = name.length();
|
||||
|
||||
for (auto report : _reports) {
|
||||
for (auto item : report->items) {
|
||||
if (strncmp(name.c_str(), item->name.c_str(), nameLength) == 0) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void FGHIDDevice::scanItem(hid_item* item)
|
||||
{
|
||||
std::string name = HID::nameForUsage(item->usage >> 16, item->usage & 0xffff);
|
||||
if (hid_parse_is_relative(item)) {
|
||||
name = "rel-" + name; // prefix relative names
|
||||
} else if (HID::shouldPrefixWithAbs(item->usage >> 16, item->usage & 0xffff)) {
|
||||
name = "abs-" + name;
|
||||
}
|
||||
|
||||
const auto ty = static_cast<HID::ReportType>(item->type);
|
||||
auto existingItem = itemWithName(name);
|
||||
if (existingItem.second) {
|
||||
// type fixup
|
||||
const HID::ReportType existingItemType = existingItem.first->type;
|
||||
if (existingItemType != ty) {
|
||||
// might be an item named identically in input/output and feature reports
|
||||
// -> prefix the feature one with 'feature'
|
||||
if (ty == HID::ReportType::Feature) {
|
||||
name = "feature-" + name;
|
||||
} else if (existingItemType == HID::ReportType::Feature) {
|
||||
// rename this existing item since it's a feature
|
||||
existingItem.second->name = "feature-" + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do the count now, after we did any renaming, since we might have
|
||||
// N > 1 for the new name
|
||||
int existingCount = countWithName(name);
|
||||
if (existingCount > 0) {
|
||||
if (existingCount == 1) {
|
||||
// rename existing item 0 to have the "-0" suffix
|
||||
auto existingItem = itemWithName(name);
|
||||
existingItem.second->name += "-0";
|
||||
}
|
||||
|
||||
// define the new nae
|
||||
std::stringstream os;
|
||||
os << name << "-" << existingCount;
|
||||
name = os.str();
|
||||
}
|
||||
|
||||
auto report = getReport(ty, item->report_id, true /* create */);
|
||||
uint32_t bitOffset = report->currentBitSize();
|
||||
|
||||
SG_LOG(SG_INPUT, SG_INFO, "adding item:" << name);
|
||||
Item* itemObject = new Item{name, bitOffset, item->report_size};
|
||||
itemObject->isRelative = hid_parse_is_relative(item);
|
||||
|
||||
SG_LOG(SG_INPUT, SG_INFO, "\t logical min-max:" << item->logical_min << " / " << item->logical_max);
|
||||
|
||||
itemObject->doSignExtend = (item->logical_min < 0) || (item->logical_max < 0);
|
||||
report->items.push_back(itemObject);
|
||||
}
|
||||
|
||||
void FGHIDDevice::Close()
|
||||
{
|
||||
hid_close(_device);
|
||||
}
|
||||
|
||||
void FGHIDDevice::update(double dt)
|
||||
{
|
||||
uint8_t reportBuf[65];
|
||||
int readCount = 0;
|
||||
while (true) {
|
||||
readCount = hid_read_timeout(_device, reportBuf, sizeof(reportBuf), 0);
|
||||
|
||||
if (readCount <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
int modifiers = fgGetKeyModifiers();
|
||||
const uint8_t reportNumber = _haveNumberedReports ? reportBuf[0] : 0;
|
||||
auto inputReport = getReport(HID::ReportType::In, reportNumber, false);
|
||||
if (!inputReport) {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "FGHIDDevice: Unknown input report number");
|
||||
} else {
|
||||
uint8_t* reportBytes = _haveNumberedReports ? reportBuf + 1 : reportBuf;
|
||||
size_t reportSize = _haveNumberedReports ? readCount - 1 : readCount;
|
||||
processInputReport(inputReport, reportBytes, reportSize, dt, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
FGInputDevice::update(dt);
|
||||
|
||||
for (auto rep : _dirtyReports) {
|
||||
sendReport(rep);
|
||||
}
|
||||
|
||||
_dirtyReports.clear();
|
||||
}
|
||||
|
||||
void writeBits(uint8_t* bytes, size_t bitOffset, size_t bitSize, int value)
|
||||
{
|
||||
size_t wholeBytesToSkip = bitOffset >> 3;
|
||||
uint8_t* dataByte = bytes + wholeBytesToSkip;
|
||||
size_t offsetInByte = bitOffset & 0x7;
|
||||
size_t bitsInByte = std::min(bitSize, 8 - offsetInByte);
|
||||
uint8_t mask = 0xff >> (8 - bitsInByte);
|
||||
|
||||
*dataByte |= ((value & mask) << offsetInByte);
|
||||
|
||||
if (bitsInByte < bitSize) {
|
||||
// if we have more bits to write, recurse
|
||||
writeBits(bytes, bitOffset + bitsInByte, bitSize - bitsInByte, value >> bitsInByte);
|
||||
}
|
||||
}
|
||||
|
||||
void FGHIDDevice::sendReport(Report* report) const
|
||||
{
|
||||
uint8_t reportBytes[65];
|
||||
size_t reportLength = 0;
|
||||
memset(reportBytes, 0, sizeof(reportBytes));
|
||||
reportBytes[0] = report->number;
|
||||
|
||||
// fill in valid data
|
||||
for (auto item : report->items) {
|
||||
reportLength += item->bitSize;
|
||||
if (item->lastValue == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
writeBits(reportBytes + 1, item->bitOffset, item->bitSize, item->lastValue);
|
||||
}
|
||||
|
||||
reportLength /= 8;
|
||||
// send the data, based on th report type
|
||||
if (report->type == HID::ReportType::Feature) {
|
||||
hid_send_feature_report(_device, reportBytes, reportLength + 1);
|
||||
} else {
|
||||
assert(report->type == HID::ReportType::Out);
|
||||
hid_write(_device, reportBytes, reportLength + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int extractBits(uint8_t* bytes, size_t lengthInBytes, size_t bitOffset, size_t bitSize)
|
||||
{
|
||||
const size_t wholeBytesToSkip = bitOffset >> 3;
|
||||
const size_t offsetInByte = bitOffset & 0x7;
|
||||
|
||||
// work out how many whole bytes to copy
|
||||
const size_t bytesToCopy = std::min(sizeof(uint32_t), (offsetInByte + bitSize + 7) / 8);
|
||||
uint32_t v = 0;
|
||||
// this goes from byte alignment to word alignment safely
|
||||
memcpy((void*) &v, bytes + wholeBytesToSkip, bytesToCopy);
|
||||
|
||||
// shift down so lowest bit is aligned
|
||||
v = v >> offsetInByte;
|
||||
|
||||
// mask off any extraneous top bits
|
||||
const uint32_t mask = ~(0xffffffff << bitSize);
|
||||
v &= mask;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
int signExtend(int inValue, size_t bitSize)
|
||||
{
|
||||
const int m = 1U << (bitSize - 1);
|
||||
return (inValue ^ m) - m;
|
||||
}
|
||||
|
||||
int FGHIDDevice::maybeSignExtend(Item* item, int inValue)
|
||||
{
|
||||
return item->doSignExtend ? signExtend(inValue, item->bitSize) : inValue;
|
||||
}
|
||||
|
||||
void FGHIDDevice::processInputReport(Report* report, unsigned char* data,
|
||||
size_t length,
|
||||
double dt, int keyModifiers)
|
||||
{
|
||||
//SG_LOG(SG_INPUT, SG_INFO, "Report " << report->number);
|
||||
#if 0
|
||||
{
|
||||
std::ostringstream byteString;
|
||||
byteString << std::hex;
|
||||
for (int i=0; i<length; ++i) {
|
||||
byteString << (int) data[i] << " ";
|
||||
}
|
||||
SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
|
||||
}
|
||||
#endif
|
||||
|
||||
for (auto item : report->items) {
|
||||
int value = extractBits(data, length, item->bitOffset, item->bitSize);
|
||||
|
||||
value = maybeSignExtend(item, value);
|
||||
|
||||
// suppress events for values that aren't changing
|
||||
if (item->isRelative) {
|
||||
// supress spurious 0-valued relative events
|
||||
if (value == 0) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// supress no-change events for absolute items
|
||||
if (value == item->lastValue) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
item->lastValue = value;
|
||||
if (!item->event)
|
||||
continue;
|
||||
|
||||
SG_LOG(SG_INPUT, SG_INFO, "\titem:" << item->name << " = " << value);
|
||||
|
||||
HIDEventData event{item, value, dt, keyModifiers};
|
||||
HandleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void FGHIDDevice::SendFeatureReport(unsigned int reportId, const std::string& data)
|
||||
{
|
||||
uint8_t buf[65];
|
||||
size_t len = std::min(data.length() + 1, sizeof(buf));
|
||||
buf[0] = reportId;
|
||||
memcpy(buf + 1, data.data(), len - 1);
|
||||
hid_send_feature_report(_device, buf, len);
|
||||
}
|
||||
|
||||
const char *FGHIDDevice::TranslateEventName(FGEventData &eventData)
|
||||
{
|
||||
HIDEventData& hidEvent = static_cast<HIDEventData&>(eventData);
|
||||
return hidEvent.item->name.c_str();
|
||||
}
|
||||
|
||||
void FGHIDDevice::Send(const char *eventName, double value)
|
||||
{
|
||||
auto item = itemWithName(eventName);
|
||||
if (item.second == nullptr) {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "FGHIDDevice: unknown item name:" << eventName);
|
||||
return;
|
||||
}
|
||||
|
||||
int intValue = static_cast<int>(value);
|
||||
if (item.second->lastValue == intValue) {
|
||||
return; // not actually changing
|
||||
}
|
||||
|
||||
// update the stored value prior to sending
|
||||
item.second->lastValue = intValue;
|
||||
_dirtyReports.insert(item.first);
|
||||
}
|
||||
|
||||
} // of anonymous namespace
|
||||
|
||||
FGHIDEventInput::FGHIDEventInput() :
|
||||
FGEventInput(),
|
||||
d(new FGHIDEventInputPrivate)
|
||||
{
|
||||
d->p = this; // store back pointer to outer object on pimpl
|
||||
}
|
||||
|
||||
FGHIDEventInput::~FGHIDEventInput()
|
||||
{
|
||||
}
|
||||
|
||||
void FGHIDEventInput::init()
|
||||
{
|
||||
// have to wait until postinit since loading config files
|
||||
// requires Nasal to be running
|
||||
}
|
||||
|
||||
void FGHIDEventInput::postinit()
|
||||
{
|
||||
hid_init();
|
||||
|
||||
hid_device_info* devices = hid_enumerate(0 /* vendor ID */, 0 /* product ID */);
|
||||
|
||||
for (hid_device_info* curDev = devices; curDev != nullptr; curDev = curDev->next) {
|
||||
d->evaluateDevice(curDev);
|
||||
}
|
||||
|
||||
hid_free_enumeration(devices);
|
||||
}
|
||||
|
||||
void FGHIDEventInput::shutdown()
|
||||
{
|
||||
FGEventInput::shutdown();
|
||||
|
||||
hid_exit();
|
||||
}
|
||||
|
||||
//
|
||||
// read all elements in each input device
|
||||
//
|
||||
void FGHIDEventInput::update(double dt)
|
||||
{
|
||||
FGEventInput::update(dt);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void FGHIDEventInput::FGHIDEventInputPrivate::evaluateDevice(hid_device_info* deviceInfo)
|
||||
{
|
||||
SG_LOG(SG_INPUT, SG_DEBUG, "HID device:" << deviceInfo->product_string << " from " << deviceInfo->manufacturer_string);
|
||||
|
||||
// allocate an input device, and add to the base class to see if we have
|
||||
// a config
|
||||
p->AddDevice(new FGHIDDevice(deviceInfo, p));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
46
src/Input/FGHIDEventInput.hxx
Normal file
46
src/Input/FGHIDEventInput.hxx
Normal file
|
@ -0,0 +1,46 @@
|
|||
// FGHIDEventInput.hxx -- handle event driven input devices via HIDAPI
|
||||
//
|
||||
// Written by James Turner
|
||||
//
|
||||
// Copyright (C) 2017, James Turner <zakalawe@mac.com>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
|
||||
#ifndef __FGHIDEVENTINPUT_HXX_
|
||||
#define __FGHIDEVENTINPUT_HXX_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "FGEventInput.hxx"
|
||||
|
||||
class FGHIDEventInput : public FGEventInput {
|
||||
public:
|
||||
FGHIDEventInput();
|
||||
|
||||
virtual ~FGHIDEventInput();
|
||||
|
||||
void update(double dt) override;
|
||||
void init() override;
|
||||
void postinit();
|
||||
void shutdown() override;
|
||||
private:
|
||||
class FGHIDEventInputPrivate;
|
||||
|
||||
std::unique_ptr<FGHIDEventInputPrivate> d;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -260,8 +260,8 @@ public:
|
|||
static EventTypeByName EVENT_TYPE_BY_NAME;
|
||||
|
||||
|
||||
FGLinuxInputDevice::FGLinuxInputDevice( std::string aName, std::string aDevname ) :
|
||||
FGInputDevice(aName),
|
||||
FGLinuxInputDevice::FGLinuxInputDevice( std::string aName, std::string aDevname, std::string aSerial ) :
|
||||
FGInputDevice(aName,aSerial),
|
||||
devname( aDevname ),
|
||||
fd(-1)
|
||||
{
|
||||
|
@ -481,10 +481,10 @@ void FGLinuxEventInput::postinit()
|
|||
|
||||
dev = udev_device_get_parent( dev );
|
||||
const char * name = udev_device_get_sysattr_value(dev,"name");
|
||||
|
||||
const char * serial = udev_device_get_sysattr_value(dev, "serial");
|
||||
SG_LOG(SG_INPUT,SG_DEBUG, "name=" << (name?name:"<null>") << ", node=" << (node?node:"<null>"));
|
||||
if( name && node )
|
||||
AddDevice( new FGLinuxInputDevice(name, node) );
|
||||
AddDevice( new FGLinuxInputDevice(name, node, serial) );
|
||||
|
||||
udev_device_unref(dev);
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ void FGMacOSXEventInputPrivate::matchedDevice(IOHIDDeviceRef device)
|
|||
{
|
||||
std::string productName = getDeviceStringProperty(device, CFSTR(kIOHIDProductKey));
|
||||
std::string manufacturer = getDeviceStringProperty(device, CFSTR(kIOHIDManufacturerKey));
|
||||
std::string serial = getDeviceStringProperty(device, CFSTR(kIOHIDSerialNumberKey));
|
||||
|
||||
SG_LOG(SG_INPUT, SG_INFO, "matched device:" << productName << " from " << manufacturer);
|
||||
|
||||
|
@ -258,6 +259,8 @@ void FGMacOSXEventInputPrivate::matchedDevice(IOHIDDeviceRef device)
|
|||
|
||||
FGMacOSXInputDevice* macInputDevice = new FGMacOSXInputDevice(device, this);
|
||||
macInputDevice->SetName(manufacturer + " " + productName);
|
||||
macInputDevice->SetSerialNumber(serial);
|
||||
|
||||
p->AddDevice(macInputDevice);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,9 +22,7 @@
|
|||
//
|
||||
// $Id$
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include "input.hxx"
|
||||
|
||||
|
@ -48,6 +46,10 @@
|
|||
#define INPUTEVENT_CLASS FGLinuxEventInput
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_HID_INPUT)
|
||||
#include "FGHIDEventInput.hxx"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
@ -81,6 +83,14 @@ FGInput::FGInput ()
|
|||
set_subsystem( "input-event", new INPUTEVENT_CLASS() );
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_HID_INPUT)
|
||||
if (fgGetBool("/sim/input/enable-hid", true)) {
|
||||
set_subsystem( "input-event-hid", new FGHIDEventInput() );
|
||||
} else {
|
||||
SG_LOG(SG_INPUT, SG_WARN, "HID-based event input disabled");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FGInput::~FGInput ()
|
||||
|
|
80
src/Input/test_hidinput.cxx
Normal file
80
src/Input/test_hidinput.cxx
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Written by James Turner, started 2017.
|
||||
//
|
||||
// Copyright (C) 2017 James Turner
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation; either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but
|
||||
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "unitTestHelpers.hxx"
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
|
||||
#include "FGHIDEventInput.cxx"
|
||||
|
||||
void testValueExtract()
|
||||
{
|
||||
uint8_t testDataFromSpec[4] = {0, 0xf4, 0x1 | (0x7 << 2), 0x03};
|
||||
SG_VERIFY(extractBits(testDataFromSpec, 4, 8, 10) == 500);
|
||||
SG_VERIFY(extractBits(testDataFromSpec, 4, 18, 10) == 199);
|
||||
|
||||
uint8_t testData2[4] = {0x01 << 6 | 0x0f,
|
||||
0x17 | (1 << 6),
|
||||
0x3 | (0x11 << 2),
|
||||
0x3d | (1 << 6) };
|
||||
|
||||
SG_VERIFY(extractBits(testData2, 4, 0, 6) == 15);
|
||||
SG_VERIFY(extractBits(testData2, 4, 6, 12) == 3421);
|
||||
SG_VERIFY(extractBits(testData2, 4, 18, 12) == 3921);
|
||||
SG_VERIFY(extractBits(testData2, 4, 30, 1) == 1);
|
||||
SG_VERIFY(extractBits(testData2, 4, 31, 1) == 0);
|
||||
}
|
||||
|
||||
// void writeBits(uint8_t* bytes, size_t bitOffset, size_t bitSize, int value)
|
||||
|
||||
void testValueInsert()
|
||||
{
|
||||
uint8_t buf[8];
|
||||
memset(buf, 0, 8);
|
||||
|
||||
int a = 3421;
|
||||
int b = 3921;
|
||||
writeBits(buf, 6, 12, a);
|
||||
writeBits(buf, 18, 12, b);
|
||||
|
||||
SG_VERIFY(buf[0] == 0x40);
|
||||
SG_VERIFY(buf[1] == 0x57);
|
||||
SG_VERIFY(buf[2] == (0x03 | 0x44));
|
||||
SG_VERIFY(buf[3] == 0x3d);
|
||||
}
|
||||
|
||||
void testSignExtension()
|
||||
{
|
||||
SG_VERIFY(signExtend(0x80, 8) == -128);
|
||||
SG_VERIFY(signExtend(0xff, 8) == -1);
|
||||
SG_VERIFY(signExtend(0x7f, 8) == 127);
|
||||
|
||||
SG_VERIFY(signExtend(0x831, 12) == -1999);
|
||||
SG_VERIFY(signExtend(0x7dd, 12) == 2013);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
testValueExtract();
|
||||
testValueInsert();
|
||||
testSignExtension();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
|
@ -81,6 +81,11 @@ void postinitNasalGUI(naRef globals, naContext c)
|
|||
|
||||
}
|
||||
|
||||
int fgGetKeyModifiers()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void syncPausePopupState()
|
||||
{
|
||||
|
||||
|
|
Loading…
Reference in a new issue