1
0
Fork 0

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:
James Turner 2017-12-11 17:10:29 +00:00
parent c14f37edc0
commit e920dc7509
13 changed files with 1007 additions and 77 deletions

View file

@ -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)

View file

@ -50,6 +50,7 @@
#cmakedefine HAVE_CRASHRPT
#cmakedefine ENABLE_FLITE
#cmakedefine ENABLE_HID_INPUT
#cmakedefine HAVE_QT

View file

@ -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}")

View file

@ -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);
}

View file

@ -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();

View file

@ -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;

View 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));
}
///////////////////////////////////////////////////////////////////////////////////////////////

View 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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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 ()

View 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;
}

View file

@ -81,6 +81,11 @@ void postinitNasalGUI(naRef globals, naContext c)
}
int fgGetKeyModifiers()
{
return 0;
}
void syncPausePopupState()
{