first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

84
src/Input/CMakeLists.txt Normal file
View File

@@ -0,0 +1,84 @@
include(FlightGearComponent)
IF(APPLE)
# no Mac implemention, use HID
elseif(WIN32)
# no Win32 specific implementation, at least for now
else()
set(EVENT_INPUT_SOURCES FGLinuxEventInput.cxx)
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
FGCommonInput.cxx
FGDeviceConfigurationMap.cxx
FGEventInput.cxx
FGKeyboardInput.cxx
FGMouseInput.cxx
input.cxx
)
set(HEADERS
FGButton.hxx
FGCommonInput.hxx
FGDeviceConfigurationMap.hxx
FGEventInput.hxx
FGKeyboardInput.hxx
FGMouseInput.hxx
input.hxx
)
if (ENABLE_PLIB_JOYSTICK)
list(APPEND SOURCES FGJoystickInput.cxx)
list(APPEND HEADERS FGJoystickInput.hxx)
endif()
if(EVENT_INPUT)
list(APPEND SOURCES ${EVENT_INPUT_SOURCES})
list(APPEND HEADERS ${EVENT_INPUT_HEADERS})
include_directories(${UDEV_INCLUDE_DIR})
endif()
if(ENABLE_FGJS AND ENABLE_PLIB_JOYSTICK)
set(FGJS_SOURCES
fgjs.cxx
jsinput.cxx
jssuper.cxx
)
add_executable(fgjs ${FGJS_SOURCES})
target_link_libraries(fgjs
SimGearCore
PLIBJoystick
)
install(TARGETS fgjs RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
if(ENABLE_JS_DEMO AND ENABLE_PLIB_JOYSTICK)
add_executable(js_demo js_demo.cxx)
target_link_libraries(js_demo
SimGearCore
PLIBJoystick
)
install(TARGETS js_demo RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
if (ENABLE_HID_INPUT)
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()
endif()
flightgear_component(Input "${SOURCES}" "${HEADERS}")

83
src/Input/FGButton.cxx Normal file
View File

@@ -0,0 +1,83 @@
// FGButton.cxx -- a simple button/key wrapper class
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "FGButton.hxx"
FGButton::FGButton ()
: is_repeatable(false),
interval_sec(0),
delay_sec(0),
release_delay_sec(0),
last_dt(0),
last_state(0)
{
}
FGButton::~FGButton ()
{
// bindings is a list of SGSharedPtr<SGBindings>
// no cleanup required
}
void FGButton::init(const SGPropertyNode* node, const std::string& name,
const std::string& module)
{
if (node == 0) {
SG_LOG(SG_INPUT, SG_DEBUG, "No bindings for button " << name);
} else {
is_repeatable = node->getBoolValue("repeatable", is_repeatable);
// Get the bindings for the button
read_bindings( node, bindings, KEYMOD_NONE, module );
}
}
void FGButton::update( int modifiers, bool pressed, int x, int y)
{
if (pressed) {
// The press event may be repeated.
if (!last_state || is_repeatable) {
SG_LOG( SG_INPUT, SG_DEBUG, "Button has been pressed" );
for (unsigned int k = 0; k < bindings[modifiers].size(); k++) {
bindings[modifiers][k]->fire(x, y);
}
}
} else {
// The release event is never repeated.
if (last_state) {
SG_LOG( SG_INPUT, SG_DEBUG, "Button has been released" );
for (unsigned int k = 0; k < bindings[modifiers|KEYMOD_RELEASED].size(); k++)
bindings[modifiers|KEYMOD_RELEASED][k]->fire(x, y);
}
}
last_state = pressed;
}

45
src/Input/FGButton.hxx Normal file
View File

@@ -0,0 +1,45 @@
// FGButton.hxx -- a simple button/key wrapper class
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifndef FGBUTTON_H
#define FGBUTTON_H
#include "FGCommonInput.hxx"
#include <Main/fg_os.hxx> // for KEYMOD_MAX
class FGButton : public FGCommonInput {
public:
FGButton();
virtual ~FGButton();
void init(const SGPropertyNode* node, const std::string& name,
const std::string& module);
void update( int modifiers, bool pressed, int x = -1, int y = -1);
bool is_repeatable;
float interval_sec, delay_sec, release_delay_sec;
float last_dt;
int last_state;
binding_list_t bindings[KEYMOD_MAX];
};
#endif

View File

@@ -0,0 +1,76 @@
// FGCommonInput.cxx -- common functions for all Input subsystems
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "FGCommonInput.hxx"
#include <Main/globals.hxx>
#include <Main/fg_os.hxx>
using simgear::PropertyList;
using std::string;
void FGCommonInput::read_bindings (const SGPropertyNode * node, binding_list_t * binding_list, int modifiers, const string & module )
{
PropertyList bindings = node->getChildren("binding");
static string nasal = "nasal";
for (unsigned int i = 0; i < bindings.size(); i++) {
std::string cmd = bindings[i]->getStringValue("command");
if (nasal.compare(cmd) == 0 && !module.empty())
bindings[i]->setStringValue("module", module.c_str());
binding_list[modifiers].push_back(new SGBinding(bindings[i], globals->get_props()));
}
// Read nested bindings for modifiers
if (node->getChild("mod-up") != 0)
read_bindings(node->getChild("mod-up"), binding_list,
modifiers|KEYMOD_RELEASED, module);
if (node->getChild("mod-shift") != 0)
read_bindings(node->getChild("mod-shift"), binding_list,
modifiers|KEYMOD_SHIFT, module);
if (node->getChild("mod-ctrl") != 0)
read_bindings(node->getChild("mod-ctrl"), binding_list,
modifiers|KEYMOD_CTRL, module);
if (node->getChild("mod-alt") != 0)
read_bindings(node->getChild("mod-alt"), binding_list,
modifiers|KEYMOD_ALT, module);
if (node->getChild("mod-meta") != 0)
read_bindings(node->getChild("mod-meta"), binding_list,
modifiers|KEYMOD_META, module);
if (node->getChild("mod-super") != 0)
read_bindings(node->getChild("mod-super"), binding_list,
modifiers|KEYMOD_SUPER, module);
if (node->getChild("mod-hyper") != 0)
read_bindings(node->getChild("mod-hyper"), binding_list,
modifiers|KEYMOD_HYPER, module);
}

View File

@@ -0,0 +1,52 @@
// FGCommonInput.hxx -- common functions for all Input subsystems
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifndef FGCOMMONINPUT_H
#define FGCOMMONINPUT_H
#include <vector>
#include <simgear/structure/SGBinding.hxx>
#include <simgear/compiler.h>
#if defined( SG_WINDOWS )
#define TGT_PLATFORM "windows"
#elif defined ( SG_MAC )
#define TGT_PLATFORM "mac"
#else
#define TGT_PLATFORM "unix"
#endif
class FGCommonInput {
public:
typedef std::vector<SGSharedPtr<SGBinding> > binding_list_t;
/*
read all "binding" nodes directly under the specified base node and fill the
vector of SGBinding supplied in binding_list. Reads all the mod-xxx bindings and
add the corresponding SGBindings.
*/
static void read_bindings (const SGPropertyNode * base, binding_list_t * binding_list, int modifiers, const std::string & module );
};
#endif

View File

@@ -0,0 +1,194 @@
// FGDeviceConfigurationMap.cxx -- a map to access xml device configuration
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "FGDeviceConfigurationMap.hxx"
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <simgear/misc/sg_dir.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/structure/exception.hxx>
#include <Main/globals.hxx>
#include <Main/sentryIntegration.hxx>
#include <Navaids/NavDataCache.hxx>
using simgear::PropertyList;
using std::string;
FGDeviceConfigurationMap::FGDeviceConfigurationMap()
{
}
FGDeviceConfigurationMap::FGDeviceConfigurationMap( const string& relative_path,
SGPropertyNode* nodePath,
const std::string& nodeName)
{
// scan for over-ride configurations, loaded via joysticks.xml, etc
for (auto preloaded : nodePath->getChildren(nodeName)) {
std::string suffix = computeSuffix(preloaded);
for (auto nameProp : preloaded->getChildren("name")) {
overrideDict[nameProp->getStringValue() + suffix] = preloaded;
} // of names iteration
} // of defined overrides iteration
scan_dir( SGPath(globals->get_fg_home(), relative_path));
scan_dir( SGPath(globals->get_fg_root(), relative_path));
}
std::string FGDeviceConfigurationMap::computeSuffix(SGPropertyNode_ptr node)
{
if (node->hasChild("serial-number")) {
return std::string("::") + node->getStringValue("serial-number");
}
// allow specifying a device number / index in the override
if (node->hasChild("device-number")) {
std::ostringstream os;
os << "_" << node->getIntValue("device-number");
return os.str();
}
return{};
}
FGDeviceConfigurationMap::~FGDeviceConfigurationMap()
{
}
SGPropertyNode_ptr
FGDeviceConfigurationMap::configurationForDeviceName(const std::string& name)
{
auto j = overrideDict.find(name);
if (j != overrideDict.end()) {
return j->second;
}
// no override, check out list of config files
auto it = namePathMap.find(name);
if (it == namePathMap.end()) {
return {};
}
SGPropertyNode_ptr result(new SGPropertyNode);
try {
readProperties(it->second, result);
result->setStringValue("source", it->second.utf8Str());
} catch (sg_exception& e) {
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::InputDeviceConfig,
"Failed to parse device configuration:" + e.getFormattedMessage(),
it->second);
return {};
}
return result;
}
bool FGDeviceConfigurationMap::hasConfiguration(const std::string& name) const
{
auto j = overrideDict.find(name);
if (j != overrideDict.end()) {
return true;
}
return namePathMap.find(name) != namePathMap.end();
}
void FGDeviceConfigurationMap::scan_dir(const SGPath & path)
{
SG_LOG(SG_INPUT, SG_DEBUG, "Scanning " << path << " for input devices");
if (!path.exists())
return;
flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
flightgear::NavDataCache::Transaction txn(cache);
simgear::Dir dir(path);
simgear::PathList children = dir.children(simgear::Dir::TYPE_FILE |
simgear::Dir::TYPE_DIR | simgear::Dir::NO_DOT_OR_DOTDOT);
for (SGPath path : children) {
if (path.isDir()) {
scan_dir(path);
} else if (path.extension() == "xml") {
if (cache->isCachedFileModified(path)) {
refreshCacheForFile(path);
} else {
readCachedData(path);
} // of cached file stamp is valid
} // of child is a file with '.xml' extension
} // of directory children iteration
txn.commit();
}
void FGDeviceConfigurationMap::readCachedData(const SGPath& path)
{
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.
if (namePathMap.find(s) == namePathMap.end()) {
namePathMap.insert(std::make_pair(s, path));
}
} // of cached names iteration
}
void FGDeviceConfigurationMap::refreshCacheForFile(const SGPath& path)
{
simgear::ErrorReportContext ectx("input-device", path.utf8Str());
SG_LOG(SG_INPUT, SG_DEBUG, "Reading device file " << path);
SGPropertyNode_ptr n(new SGPropertyNode);
try {
readProperties(path, n);
} catch (sg_exception& e) {
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::InputDeviceConfig,
"Failed to load input device configuration:" + e.getFormattedMessage(),
path);
return;
}
std::string suffix = computeSuffix(n);
string_list names;
for (auto nameProp : n->getChildren("name")) {
const string name = nameProp->getStringValue() + suffix;
names.push_back(name);
// same comment as readCachedData: only insert if not already present
if (namePathMap.find(name) == namePathMap.end()) {
namePathMap.insert(std::make_pair(name, path));
}
}
auto cache = flightgear::NavDataCache::instance();
if (!cache->isReadOnly()) {
cache->stampCacheFile(path);
cache->writeStringListProperty(path.utf8Str(), names);
}
}

View File

@@ -0,0 +1,69 @@
// FGDeviceConfigurationMap.hxx -- a map to access xml device configuration
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifndef _FGDEVICECONFIGURATIONMAP_HXX
#define _FGDEVICECONFIGURATIONMAP_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include <simgear/props/props.hxx>
#include <simgear/misc/sg_path.hxx>
#include <map>
class FGDeviceConfigurationMap
{
public:
FGDeviceConfigurationMap();
FGDeviceConfigurationMap ( const std::string& relative_path,
SGPropertyNode* nodePath,
const std::string& nodeName);
virtual ~FGDeviceConfigurationMap();
SGPropertyNode_ptr configurationForDeviceName(const std::string& name);
bool hasConfiguration(const std::string& name) const;
private:
void scan_dir(const SGPath & path);
void readCachedData(const SGPath& path);
void refreshCacheForFile(const SGPath& path);
std::string computeSuffix(SGPropertyNode_ptr node);
typedef std::map<std::string, SGPropertyNode_ptr> NameNodeMap;
// dictionary of over-ridden configurations, where the config data
// was explicitly loaded and shoudl be picked over a file search
NameNodeMap overrideDict;
typedef std::map<std::string, SGPath> NamePathMap;
// mapping from joystick name to XML configuration file path
NamePathMap namePathMap;
};
#endif

593
src/Input/FGEventInput.cxx Normal file
View File

@@ -0,0 +1,593 @@
// FGEventInput.cxx -- handle event driven input devices
//
// Written by Torsten Dreyer, started July 2009.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
//
// 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.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <cstring>
#include "FGEventInput.hxx"
#include <Main/fg_props.hxx>
#include <simgear/io/sg_file.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/math/SGMath.hxx>
#include <simgear/math/interpolater.hxx>
#include <Scripting/NasalSys.hxx>
using simgear::PropertyList;
using std::cout;
using std::endl;
using std::map;
using std::string;
FGEventSetting::FGEventSetting( SGPropertyNode_ptr base ) :
value(0.0)
{
SGPropertyNode_ptr n;
if( (n = base->getNode( "value" )) != NULL ) {
valueNode = NULL;
value = n->getDoubleValue();
} else {
n = base->getNode( "property" );
if( n == NULL ) {
SG_LOG( SG_INPUT, SG_WARN, "Neither <value> nor <property> defined for event setting." );
} else {
valueNode = fgGetNode( n->getStringValue(), true );
}
}
if( (n = base->getChild("condition")) != NULL )
condition = sgReadCondition(base, n);
else
SG_LOG( SG_INPUT, SG_ALERT, "No condition for event setting." );
}
double FGEventSetting::GetValue()
{
return valueNode == NULL ? value : valueNode->getDoubleValue();
}
bool FGEventSetting::Test()
{
return condition == NULL ? true : condition->test();
}
static inline bool StartsWith( string & s, const char * cp )
{
return s.find( cp ) == 0;
}
FGInputEvent * FGInputEvent::NewObject( FGInputDevice * device, SGPropertyNode_ptr node )
{
string name = node->getStringValue( "name", "" );
if( StartsWith( name, "button-" ) )
return new FGButtonEvent( device, node );
if( StartsWith( name, "rel-" ) )
return new FGRelAxisEvent( device, node );
if( StartsWith( name, "abs-" ) )
return new FGAbsAxisEvent( device, node );
return new FGInputEvent( device, node );
}
FGInputEvent::FGInputEvent( FGInputDevice * aDevice, SGPropertyNode_ptr node ) :
device( aDevice ),
lastDt(0.0),
lastSettingValue(std::numeric_limits<float>::quiet_NaN())
{
name = node->getStringValue( "name", "" );
desc = node->getStringValue( "desc", "" );
intervalSec = node->getDoubleValue("interval-sec",0.0);
read_bindings( node, bindings, KEYMOD_NONE, device->GetNasalModule() );
for (auto child : node->getChildren("setting"))
settings.push_back( new FGEventSetting(child) );
}
FGInputEvent::~FGInputEvent()
{
}
void FGInputEvent::update( double dt )
{
for (auto setting : settings) {
if( setting->Test() ) {
const double value = setting->GetValue();
if( value != lastSettingValue ) {
device->Send( GetName(), value );
lastSettingValue = value;
}
}
}
}
void FGInputEvent::fire( FGEventData & eventData )
{
lastDt += eventData.dt;
if( lastDt >= intervalSec ) {
for( binding_list_t::iterator it = bindings[eventData.modifiers].begin(); it != bindings[eventData.modifiers].end(); ++it )
fire( *it, eventData );
lastDt -= intervalSec;
}
}
void FGInputEvent::fire( SGBinding * binding, FGEventData & eventData )
{
binding->fire();
}
FGAxisEvent::FGAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node ) :
FGInputEvent( device, node )
{
tolerance = node->getDoubleValue("tolerance", 0.002);
minRange = node->getDoubleValue("min-range", 0.0 );
maxRange = node->getDoubleValue("max-range", 0.0 );
center = node->getDoubleValue("center", 0.0);
deadband = node->getDoubleValue("dead-band", 0.0);
lowThreshold = node->getDoubleValue("low-threshold", -0.9);
highThreshold = node->getDoubleValue("high-threshold", 0.9);
lastValue = 9999999;
// interpolation of values
if (node->hasChild("interpolater")) {
interpolater.reset(new SGInterpTable{node->getChild("interpolater")});
mirrorInterpolater = node->getBoolValue("interpolater/mirrored", false);
}
}
FGAxisEvent::~FGAxisEvent()
{
}
void FGAxisEvent::fire( FGEventData & eventData )
{
if (fabs( eventData.value - lastValue) < tolerance)
return;
lastValue = eventData.value;
// We need a copy of the FGEventData struct to set the new value and to avoid side effects
FGEventData ed = eventData;
if( minRange != maxRange )
ed.value = 2.0*(eventData.value-minRange)/(maxRange-minRange)-1.0;
if( fabs(ed.value) < deadband )
ed.value = 0.0;
if (interpolater) {
if ((ed.value < 0.0) && mirrorInterpolater) {
// mirror the positive interpolation for negative values
ed.value = -interpolater->interpolate(fabs(ed.value));
} else {
ed.value = interpolater->interpolate(ed.value);
}
}
FGInputEvent::fire( ed );
}
void FGAbsAxisEvent::fire( SGBinding * binding, FGEventData & eventData )
{
// sets the "setting" node
binding->fire( eventData.value );
}
FGRelAxisEvent::FGRelAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node ) :
FGAxisEvent( device, node )
{
// relative axes can't use tolerance
tolerance = 0.0;
}
void FGRelAxisEvent::fire( SGBinding * binding, FGEventData & eventData )
{
// sets the "offset" node
binding->fire( eventData.value, 1.0 );
}
FGButtonEvent::FGButtonEvent( FGInputDevice * device, SGPropertyNode_ptr node ) :
FGInputEvent( device, node ),
repeatable(false),
lastState(false)
{
repeatable = node->getBoolValue("repeatable", repeatable);
}
void FGButtonEvent::fire( FGEventData & eventData )
{
bool pressed = eventData.value > 0.0;
if (pressed) {
// The press event may be repeated.
if (!lastState || repeatable) {
SG_LOG( SG_INPUT, SG_DEBUG, "Button has been pressed" );
FGInputEvent::fire( eventData );
}
} else {
// The release event is never repeated.
if (lastState) {
SG_LOG( SG_INPUT, SG_DEBUG, "Button has been released" );
eventData.modifiers|=KEYMOD_RELEASED;
FGInputEvent::fire( eventData );
}
}
lastState = pressed;
}
void FGButtonEvent::update( double dt )
{
if (repeatable && lastState) {
// interval / dt handling is done by base ::fire method
FGEventData ed{1.0, dt, 0 /* modifiers */};
FGInputEvent::fire( ed );
}
}
FGInputDevice::~FGInputDevice()
{
FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
if (nas && deviceNode ) {
SGPropertyNode_ptr nasal = deviceNode->getNode("nasal");
if( nasal ) {
SGPropertyNode_ptr nasalClose = nasal->getNode("close");
if (nasalClose) {
const string s = nasalClose->getStringValue();
nas->createModule(nasalModule.c_str(), nasalModule.c_str(), s.c_str(), s.length(), deviceNode );
}
}
nas->deleteModule(nasalModule.c_str());
}
}
void FGInputDevice::Configure( SGPropertyNode_ptr aDeviceNode )
{
deviceNode = aDeviceNode;
// use _uniqueName here so each loaded device gets its own Nasal module
nasalModule = string("__event:") + _uniqueName;
for (auto ev : deviceNode->getChildren( "event" )) {
AddHandledEvent( FGInputEvent::NewObject( this, ev) );
}
debugEvents = deviceNode->getBoolValue("debug-events", debugEvents );
grab = deviceNode->getBoolValue("grab", grab );
PropertyList reportNodes = deviceNode->getChildren("report");
for( PropertyList::iterator it = reportNodes.begin(); it != reportNodes.end(); ++it ) {
FGReportSetting_ptr r = new FGReportSetting(*it);
reportSettings.push_back(r);
}
// TODO:
// add nodes for the last event:
// last-event/name [string]
// last-event/value [double]
SGPropertyNode_ptr nasal = deviceNode->getNode("nasal");
if (nasal) {
SGPropertyNode_ptr open = nasal->getNode("open");
if (open) {
const string s = open->getStringValue();
FGNasalSys *nas = (FGNasalSys *)globals->get_subsystem("nasal");
if (nas)
nas->createModule(nasalModule.c_str(), nasalModule.c_str(), s.c_str(), s.length(), deviceNode );
}
}
}
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++ )
(*it).second->update( dt );
report_setting_list_t::const_iterator it;
for (it = reportSettings.begin(); it != reportSettings.end(); ++it) {
if ((*it)->Test()) {
std::string reportData = (*it)->reportBytes(nasalModule);
SendFeatureReport((*it)->getReportId(), reportData);
}
}
}
void FGInputDevice::HandleEvent( FGEventData & eventData )
{
string eventName = TranslateEventName( eventData );
if( debugEvents ) {
SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " has event " <<
eventName << " modifiers=" << eventData.modifiers << " value=" << eventData.value);
}
if( handledEvents.count( eventName ) > 0 ) {
handledEvents[ eventName ]->fire( eventData );
}
}
void FGInputDevice::SetName( string name )
{
this->name = name;
}
void FGInputDevice::SetUniqueName(const std::string &name)
{
_uniqueName = 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");
}
const char * FGEventInput::PROPERTY_ROOT = "/input/event";
FGEventInput::FGEventInput()
{
}
FGEventInput::~FGEventInput()
{
}
void FGEventInput::shutdown()
{
for (auto it : input_devices) {
it.second->Close();
delete it.second;
}
input_devices.clear();
}
void FGEventInput::init( )
{
configMap = FGDeviceConfigurationMap( "Input/Event",
fgGetNode(PROPERTY_ROOT, true),
"device-named");
}
void FGEventInput::postinit ()
{
}
void FGEventInput::update( double dt )
{
for (auto it : input_devices) {
it.second->update(dt);
}
}
std::string FGEventInput::computeDeviceIndexName(FGInputDevice* dev) const
{
int count = 0;
const auto devName = dev->GetName();
for (auto it : input_devices) {
if (it.second->GetName() == devName) {
++count;
}
}
std::ostringstream os;
os << devName << "_" << count;
return os.str();
}
unsigned FGEventInput::AddDevice( FGInputDevice * inputDevice )
{
SGPropertyNode_ptr baseNode = fgGetNode( PROPERTY_ROOT, true );
SGPropertyNode_ptr deviceNode;
const string deviceName = inputDevice->GetName();
SGPropertyNode_ptr configNode;
// if we have a serial number set, try using that to select a specfic configuration
if (!inputDevice->GetSerialNumber().empty()) {
const string nameWithSerial = deviceName + "::" + inputDevice->GetSerialNumber();
if (configMap.hasConfiguration(nameWithSerial)) {
configNode = configMap.configurationForDeviceName(nameWithSerial);
SG_LOG(SG_INPUT, SG_INFO, "using instance-specific configuration for device "
<< nameWithSerial << " : " << configNode->getStringValue("source"));
inputDevice->SetUniqueName(nameWithSerial);
}
}
// try instanced (counted) name
const auto nameWithIndex = computeDeviceIndexName(inputDevice);
if (configNode == nullptr) {
if (configMap.hasConfiguration(nameWithIndex)) {
configNode = configMap.configurationForDeviceName(nameWithIndex);
SG_LOG(SG_INPUT, SG_INFO, "using instance-specific configuration for device "
<< nameWithIndex << " : " << configNode->getStringValue("source"));
}
}
// otherwise try the unmodifed name for the device
if (configNode == nullptr) {
if (!configMap.hasConfiguration(deviceName)) {
SG_LOG(SG_INPUT, SG_DEBUG, "No configuration found for device " << deviceName);
delete inputDevice;
return INVALID_DEVICE_INDEX;
}
configNode = configMap.configurationForDeviceName(deviceName);
}
// if we didn't generate a name based on the serial number,
// use the name with the index suffix _0, _1, etc
if (inputDevice->GetUniqueName().empty()) {
inputDevice->SetUniqueName(nameWithIndex);
}
// found - copy to /input/event/device[n]
// find a free index
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->GetUniqueName() );
delete inputDevice;
return INVALID_DEVICE_INDEX;
}
// create this node
deviceNode = baseNode->getNode( "device", index, true );
// and copy the properties from the configuration tree
copyProperties(configNode, deviceNode );
inputDevice->Configure( deviceNode );
bool ok = inputDevice->Open();
if (!ok) {
// TODO repot a better error here, to the user
SG_LOG(SG_INPUT, SG_ALERT, "can't open InputDevice " << inputDevice->GetUniqueName());
delete inputDevice;
return INVALID_DEVICE_INDEX;
}
input_devices[deviceNode->getIndex()] = inputDevice;
SG_LOG(SG_INPUT, SG_DEBUG, "using InputDevice " << inputDevice->GetUniqueName());
return deviceNode->getIndex();
}
void FGEventInput::RemoveDevice( unsigned index )
{
// not fully implemented yet
SGPropertyNode_ptr baseNode = fgGetNode( PROPERTY_ROOT, true );
SGPropertyNode_ptr deviceNode = NULL;
FGInputDevice *inputDevice = input_devices[index];
if (inputDevice) {
inputDevice->Close();
input_devices.erase(index);
delete inputDevice;
}
deviceNode = baseNode->removeChild("device", index);
}
FGReportSetting::FGReportSetting( SGPropertyNode_ptr base )
{
reportId = base->getIntValue("report-id");
nasalFunction = base->getStringValue("nasal-function");
PropertyList watchNodes = base->getChildren( "watch" );
for (PropertyList::iterator it = watchNodes.begin(); it != watchNodes.end(); ++it ) {
std::string path = (*it)->getStringValue();
SGPropertyNode_ptr n = globals->get_props()->getNode(path, true);
n->addChangeListener(this);
}
dirty = true;
}
bool FGReportSetting::Test()
{
bool d = dirty;
dirty = false;
return d;
}
std::string FGReportSetting::reportBytes(const std::string& moduleName) const
{
FGNasalSys *nas = globals->get_subsystem<FGNasalSys>();
if (!nas) {
return {};
}
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();
}
naRef result = nas->call(func, 0, 0, naNil());
if (naIsString(result)) {
size_t len = naStr_len(result);
char* bytes = naStr_data(result);
return std::string(bytes, len);
}
if (naIsVector(result)) {
int len = naVec_size(result);
std::string s;
for (int b=0; b < len; ++b) {
int num = naNumValue(naVec_get(result, b)).num;
s.push_back(static_cast<char>(num));
}
// can't access FGInputDevice here to check debugEvents flag
#if 0
std::ostringstream byteString;
static const char* hexTable = "0123456789ABCDEF";
for (int i=0; i<s.size(); ++i) {
uint8_t uc = static_cast<uint8_t>(s[i]);
byteString << hexTable[uc >> 4];
byteString << hexTable[uc & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "report bytes: (" << s.size() << ") " << byteString.str());
#endif
return s;
}
SG_LOG(SG_INPUT, SG_DEV_WARN, "bad return data from report setting");
return {};
}
void FGReportSetting::valueChanged(SGPropertyNode * node)
{
dirty = true;
}

350
src/Input/FGEventInput.hxx Normal file
View File

@@ -0,0 +1,350 @@
// FGEventInput.hxx -- handle event driven input devices
//
// Written by Torsten Dreyer, started July 2009
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
//
// 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.
//
// $Id$
#ifndef __FGEVENTINPUT_HXX
#define __FGEVENTINPUT_HXX
#include "FGCommonInput.hxx"
#include <vector>
#include <memory>
#include "FGButton.hxx"
#include "FGDeviceConfigurationMap.hxx"
#include <simgear/structure/subsystem_mgr.hxx>
// forward decls
class SGInterpTable;
/*
* A base structure for event data.
* To be extended for O/S specific implementation data
*/
struct FGEventData {
FGEventData( double aValue, double aDt, int aModifiers ) : modifiers(aModifiers), value(aValue), dt(aDt) {}
int modifiers;
double value;
double dt;
};
class FGEventSetting : public SGReferenced
{
public:
FGEventSetting( SGPropertyNode_ptr base );
bool Test();
/*
* access for the value property
*/
double GetValue();
protected:
double value;
SGPropertyNode_ptr valueNode;
SGSharedPtr<const SGCondition> condition;
};
typedef SGSharedPtr<FGEventSetting> FGEventSetting_ptr;
typedef std::vector<FGEventSetting_ptr> setting_list_t;
class FGReportSetting : public SGReferenced,
public SGPropertyChangeListener
{
public:
FGReportSetting( SGPropertyNode_ptr base );
unsigned int getReportId() const
{
return reportId;
}
std::string getNasalFunctionName() const
{
return nasalFunction;
}
bool Test();
std::string reportBytes(const std::string& moduleName) const;
virtual void valueChanged(SGPropertyNode * node);
protected:
unsigned int reportId;
std::string nasalFunction;
bool dirty;
};
typedef SGSharedPtr<FGReportSetting> FGReportSetting_ptr;
typedef std::vector<FGReportSetting_ptr> report_setting_list_t;
/*
* A wrapper class for a configured event.
*
* <event>
* <desc>Change the view pitch</desc>
* <name>rel-x-rotate</name>
* <binding>
* <command>property-adjust</command>
* <property>sim/current-view/pitch-offset-deg</property>
* <factor type="double">0.01</factor>
* <min type="double">-90.0</min>
* <max type="double">90.0</max>
* <wrap type="bool">false</wrap>
* </binding>
* <mod-xyz>
* <binding>
* ...
* </binding>
* </mod-xyz>
* </event>
*/
class FGInputDevice;
class FGInputEvent : public SGReferenced,
FGCommonInput
{
public:
/*
* Constructor for the class. The arg node shall point
* to the property corresponding to the <event> node
*/
FGInputEvent( FGInputDevice * device, SGPropertyNode_ptr node );
virtual ~FGInputEvent();
/*
* dispatch the event value through all bindings
*/
virtual void fire( FGEventData & eventData );
/*
* access for the name property
*/
std::string GetName() const { return name; }
/*
* access for the description property
*/
std::string GetDescription() const { return desc; }
virtual void update( double dt );
static FGInputEvent * NewObject( FGInputDevice * device, SGPropertyNode_ptr node );
protected:
virtual void fire( SGBinding * binding, FGEventData & eventData );
/* A more or less meaningfull description of the event */
std::string desc;
/* One of the predefined names of the event */
std::string name;
/* A list of SGBinding objects */
binding_list_t bindings[KEYMOD_MAX];
/* A list of FGEventSetting objects */
setting_list_t settings;
/* A pointer to the associated device */
FGInputDevice * device;
double lastDt;
double intervalSec;
double lastSettingValue;
};
class FGButtonEvent : public FGInputEvent
{
public:
FGButtonEvent( FGInputDevice * device, SGPropertyNode_ptr node );
virtual void fire( FGEventData & eventData );
void update( double dt ) override;
protected:
bool repeatable;
bool lastState;
};
class FGAxisEvent : public FGInputEvent
{
public:
FGAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node );
~FGAxisEvent();
void SetMaxRange( double value ) { maxRange = value; }
void SetMinRange( double value ) { minRange = value; }
void SetRange( double min, double max ) { minRange = min; maxRange = max; }
protected:
virtual void fire( FGEventData & eventData );
double tolerance;
double minRange;
double maxRange;
double center;
double deadband;
double lowThreshold;
double highThreshold;
double lastValue;
std::unique_ptr<SGInterpTable> interpolater;
bool mirrorInterpolater = false;
};
class FGRelAxisEvent : public FGAxisEvent
{
public:
FGRelAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node );
protected:
virtual void fire( SGBinding * binding, FGEventData & eventData );
};
class FGAbsAxisEvent : public FGAxisEvent
{
public:
FGAbsAxisEvent( FGInputDevice * device, SGPropertyNode_ptr node ) : FGAxisEvent( device, node ) {}
protected:
virtual void fire( SGBinding * binding, FGEventData & eventData );
};
typedef class SGSharedPtr<FGInputEvent> FGInputEvent_ptr;
/*
* A abstract class implementing basic functionality of input devices for
* all operating systems. This is the base class for the O/S-specific
* implementation of input device handlers
*/
class FGInputDevice : public SGReferenced
{
public:
FGInputDevice() {}
FGInputDevice( std::string aName, std::string aSerial = {} ) :
name(aName), serialNumber(aSerial) {}
virtual ~FGInputDevice();
virtual bool Open() = 0;
virtual void Close() = 0;
virtual void Send( const char * eventName, double value ) = 0;
inline void Send( const std::string & eventName, double value ) {
Send( eventName.c_str(), value );
}
virtual void SendFeatureReport(unsigned int reportId, const std::string& data);
virtual const char * TranslateEventName( FGEventData & eventData ) = 0;
void SetName( std::string name );
std::string & GetName() { return name; }
void SetUniqueName(const std::string& name);
const std::string GetUniqueName() const
{ return _uniqueName; }
void SetSerialNumber( std::string serial );
std::string& GetSerialNumber() { return serialNumber; }
void HandleEvent( FGEventData & eventData );
virtual void AddHandledEvent( FGInputEvent_ptr handledEvent );
virtual void Configure( SGPropertyNode_ptr deviceNode );
virtual void update( double dt );
bool GetDebugEvents () const { return debugEvents; }
bool GetGrab() const { return grab; }
const std::string & GetNasalModule() const { return nasalModule; }
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 = false;
// grab the device exclusively, if O/S supports this
// so events are not sent to other applications
bool grab = false;
SGPropertyNode_ptr deviceNode;
std::string nasalModule;
report_setting_list_t reportSettings;
/// name, but with suffix / serial appended. This is important
/// when loading the device multiple times, to ensure the Nasal
/// module is unique
std::string _uniqueName;
};
typedef SGSharedPtr<FGInputDevice> FGInputDevice_ptr;
/*
* The Subsystem for the event input device
*/
class FGEventInput : public SGSubsystem,
FGCommonInput
{
public:
FGEventInput();
virtual ~FGEventInput();
// Subsystem API.
void init() override;
void postinit() override;
void shutdown() override;
void update(double dt) override;
const static unsigned MAX_DEVICES = 1000;
const static unsigned INVALID_DEVICE_INDEX = MAX_DEVICES + 1;
protected:
static const char * PROPERTY_ROOT;
unsigned AddDevice( FGInputDevice * inputDevice );
void RemoveDevice( unsigned index );
std::map<int,FGInputDevice*> input_devices;
FGDeviceConfigurationMap configMap;
SGPropertyNode_ptr nasalClose;
private:
std::string computeDeviceIndexName(FGInputDevice *dev) const;
};
#endif

View File

@@ -0,0 +1,971 @@
// 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 <algorithm>
#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>
const char* hexTable = "0123456789ABCDEF";
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_Rows = 0x35,
AD_Columns = 0x36,
AD_7SegmentDirectMap = 0x43,
AD_14SegmentDirectMap = 0x45,
AD_DisplayBrightness = 0x46,
AD_DisplayContrast = 0x47
};
enum class ReportType
{
Invalid = 0,
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::Undefined) {
std::stringstream os;
os << "undefined-" << usage;
return os.str();
}
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";
case AD_7SegmentDirectMap: return "seven-segment-direct";
case AD_14SegmentDirectMap: return "fourteen-segment-direct";
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;
}
ReportType reportTypeFromString(const std::string& s)
{
if (s == "input") return ReportType::In;
if (s == "output") return ReportType::Out;
if (s == "feature") return ReportType::Feature;
return ReportType::Invalid;
}
} // 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();
bool Open() override;
void Close() override;
void Configure(SGPropertyNode_ptr node) 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;
}
};
bool parseUSBHIDDescriptor();
void parseCollection(hid_item* collection);
void parseItem(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);
void defineReport(SGPropertyNode_ptr reportNode);
std::vector<Report*> _reports;
std::string _hidPath;
hid_device* _device = nullptr;
bool _haveNumberedReports = false;
bool _debugRaw = false;
/// set if we parsed the device description our XML
/// instead of from the USB data. Useful on Windows where the data
/// is inaccessible, or devices with broken descriptors
bool _haveLocalDescriptor = false;
/// allow specifying the descriptor as hex bytes in XML
std::vector<uint8_t>_rawXMLDescriptor;
// 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 manufacturerName, productName;
productName = devInfo->product_string ? std::wstring(devInfo->product_string)
: L"unknown HID device";
if (devInfo->manufacturer_string) {
manufacturerName = std::wstring(devInfo->manufacturer_string);
SetName(simgear::strutils::convertWStringToUtf8(manufacturerName) + " " +
simgear::strutils::convertWStringToUtf8(productName));
} else {
SetName(simgear::strutils::convertWStringToUtf8(productName));
}
const auto serial = devInfo->serial_number;
std::string path(devInfo->path);
// most devices return an empty serial number, unfortunately
if ((serial != nullptr) && std::wcslen(serial) > 0) {
SetSerialNumber(simgear::strutils::convertWStringToUtf8(serial));
}
SG_LOG(SG_INPUT, SG_DEBUG, "HID device:" << GetName() << " at path " << _hidPath);
}
FGHIDDevice::~FGHIDDevice()
{
if (_device) {
hid_close(_device);
}
}
void FGHIDDevice::Configure(SGPropertyNode_ptr node)
{
// base class first
FGInputDevice::Configure(node);
if (node->hasChild("hid-descriptor")) {
_haveLocalDescriptor = true;
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using local HID descriptor");
}
for (auto report : node->getChild("hid-descriptor")->getChildren("report")) {
defineReport(report);
}
}
if (node->hasChild("hid-raw-descriptor")) {
_rawXMLDescriptor = simgear::strutils::decodeHex(node->getStringValue("hid-raw-descriptor"));
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << " will configure using XML-defined raw HID descriptor");
}
}
if (node->getBoolValue("hid-debug-raw")) {
_debugRaw = true;
}
}
bool FGHIDDevice::Open()
{
_device = hid_open_path(_hidPath.c_str());
if (_device == nullptr) {
SG_LOG(SG_INPUT, SG_WARN, GetUniqueName() << ": HID: Failed to open:" << _hidPath);
SG_LOG(SG_INPUT, SG_WARN, "\tnote on Linux you may need to adjust permissions of the device using UDev rules.");
return false;
}
#if !defined(SG_WINDOWS)
if (_rawXMLDescriptor.empty()) {
_rawXMLDescriptor.resize(2048);
int descriptorSize = hid_get_descriptor(_device, _rawXMLDescriptor.data(), _rawXMLDescriptor.size());
if (descriptorSize <= 0) {
SG_LOG(SG_INPUT, SG_WARN, "HID: " << GetUniqueName() << " failed to read HID descriptor");
return false;
}
_rawXMLDescriptor.resize(descriptorSize);
}
#endif
if (!_haveLocalDescriptor) {
bool ok = parseUSBHIDDescriptor();
if (!ok)
return false;
}
for (auto& v : handledEvents) {
auto reportItem = itemWithName(v.first);
if (!reportItem.second) {
SG_LOG(SG_INPUT, SG_WARN, "HID device:" << GetUniqueName() << " has no element for event:" << v.first);
continue;
}
FGInputEvent_ptr event = v.second;
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, "\tfound item for event:" << v.first);
}
reportItem.second->event = event;
}
return true;
}
bool FGHIDDevice::parseUSBHIDDescriptor()
{
#if defined(SG_WINDOWS)
if (_rawXMLDescriptor.empty()) {
SG_LOG(SG_INPUT, SG_ALERT, GetUniqueName() << ": on Windows, there is no way to extract the UDB-HID report descriptor. "
<< "\nPlease supply the report descriptor in the device XML configuration.");
SG_LOG(SG_INPUT, SG_ALERT, "See this page:<> for information on extracting the report descriptor on Windows");
return false;
}
#endif
if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, "\nHID: descriptor for:" << GetUniqueName());
{
std::ostringstream byteString;
for (unsigned i=0; i<_rawXMLDescriptor.size(); ++i) {
byteString << hexTable[_rawXMLDescriptor[i] >> 4];
byteString << hexTable[_rawXMLDescriptor[i] & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
}
}
hid_item* rootItem = nullptr;
hid_parse_reportdesc(_rawXMLDescriptor.data(), _rawXMLDescriptor.size(), &rootItem);
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, "\nHID: scan for:" << GetUniqueName());
}
parseCollection(rootItem);
hid_free_reportdesc(rootItem);
return true;
}
void FGHIDDevice::parseCollection(hid_item* c)
{
for (hid_item* child = c->collection; child != nullptr; child = child->next) {
if (child->collection) {
parseCollection(child);
} else {
// leaf item
parseItem(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::parseItem(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();
if (debugEvents) {
SG_LOG(SG_INPUT, SG_INFO, GetUniqueName() << ": add:" << name << ", bits: " << bitOffset << ":" << (int) item->report_size
<< ", report=" << (int) item->report_id);
}
Item* itemObject = new Item{name, bitOffset, item->report_size};
itemObject->isRelative = hid_parse_is_relative(item);
itemObject->doSignExtend = (item->logical_min < 0) || (item->logical_max < 0);
report->items.push_back(itemObject);
}
void FGHIDDevice::Close()
{
if (_device) {
hid_close(_device);
_device = nullptr;
}
}
void FGHIDDevice::update(double dt)
{
if (!_device) {
return;
}
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, GetName() << ": FGHIDDevice: Unknown input report number:" <<
static_cast<int>(reportNumber));
} 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 FGHIDDevice::sendReport(Report* report) const
{
if (!_device) {
return;
}
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;
if (_debugRaw) {
std::ostringstream byteString;
for (size_t i=0; i<reportLength; ++i) {
byteString << hexTable[reportBytes[i] >> 4];
byteString << hexTable[reportBytes[i] & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "sending bytes: " << byteString.str());
}
// send the data, based on the 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 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)
{
if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, GetName() << " FGHIDDeivce received input report:" << (int) report->number << ", len=" << length);
{
std::ostringstream byteString;
for (size_t i=0; i<length; ++i) {
byteString << hexTable[data[i] >> 4];
byteString << hexTable[data[i] & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
}
}
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;
if (_debugRaw) {
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)
{
if (!_device) {
return;
}
if (_debugRaw) {
SG_LOG(SG_INPUT, SG_INFO, GetName() << ": FGHIDDevice: Sending feature report:" << (int) reportId << ", len=" << data.size());
{
std::ostringstream byteString;
for (unsigned int i=0; i<data.size(); ++i) {
byteString << hexTable[data[i] >> 4];
byteString << hexTable[data[i] & 0x0f];
byteString << " ";
}
SG_LOG(SG_INPUT, SG_INFO, "\tbytes: " << byteString.str());
}
}
uint8_t buf[65];
size_t len = std::min(data.length() + 1, sizeof(buf));
buf[0] = reportId;
memcpy(buf + 1, data.data(), len - 1);
int r = hid_send_feature_report(_device, buf, len);
if (r < 0) {
SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: Sending feature report failed, error-string is:\n"
<< simgear::strutils::error_string(errno));
}
}
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, GetName() << ": 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);
}
void FGHIDDevice::defineReport(SGPropertyNode_ptr reportNode)
{
const int nChildren = reportNode->nChildren();
uint32_t bitCount = 0;
const auto rty = HID::reportTypeFromString(reportNode->getStringValue("type"));
if (rty == HID::ReportType::Invalid) {
SG_LOG(SG_INPUT, SG_WARN, GetName() << ": FGHIDDevice: invalid report type:" <<
reportNode->getStringValue("type"));
return;
}
const auto id = reportNode->getIntValue("id");
if (id > 0) {
_haveNumberedReports = true;
}
auto report = new Report(rty, id);
_reports.push_back(report);
for (int c=0; c < nChildren; ++c) {
const auto nd = reportNode->getChild(c);
const int size = nd->getIntValue("size", 1); // default to a single bit
if (nd->getNameString() == "unused-bits") {
bitCount += size;
continue;
}
if (nd->getNameString() == "type" || nd->getNameString() == "id") {
continue; // already handled above
}
// allow repeating items
uint8_t count = nd->getIntValue("count", 1);
std::string name = nd->getNameString();
const auto lastHypen = name.rfind("-");
std::string baseName = name.substr(0, lastHypen + 1);
int baseIndex = std::stoi(name.substr(lastHypen + 1));
const bool isRelative = (name.find("rel-") == 0);
const bool isSigned = nd->getBoolValue("is-signed", false);
for (uint8_t i=0; i < count; ++i) {
std::ostringstream oss;
oss << baseName << (baseIndex + i);
Item* itemObject = new Item{oss.str(), bitCount, static_cast<uint8_t>(size)};
itemObject->isRelative = isRelative;
itemObject->doSignExtend = isSigned;
report->items.push_back(itemObject);
bitCount += size;
}
}
}
} // of anonymous namespace
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;
}
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);
}
}
FGHIDEventInput::FGHIDEventInput() :
FGEventInput(),
d(new FGHIDEventInputPrivate)
{
d->p = this; // store back pointer to outer object on pimpl
}
FGHIDEventInput::~FGHIDEventInput()
{
}
void FGHIDEventInput::init()
{
FGEventInput::init();
// have to wait until postinit since loading config files
// requires Nasal to be running
}
void FGHIDEventInput::reinit()
{
SG_LOG(SG_INPUT, SG_INFO, "Re-Initializing HID input bindings");
FGHIDEventInput::shutdown();
FGHIDEventInput::init();
FGHIDEventInput::postinit();
}
void FGHIDEventInput::postinit()
{
SG_LOG(SG_INPUT, SG_INFO, "HID event input starting up");
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()
{
SG_LOG(SG_INPUT, SG_INFO, "HID event input shutting down");
FGEventInput::shutdown();
hid_exit();
}
//
// read all elements in each input device
//
void FGHIDEventInput::update(double dt)
{
FGEventInput::update(dt);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGHIDEventInput> registrantFGHIDEventInput;
///////////////////////////////////////////////////////////////////////////////////////////////
void FGHIDEventInput::FGHIDEventInputPrivate::evaluateDevice(hid_device_info* deviceInfo)
{
// 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,58 @@
// 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"
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);
class FGHIDEventInput : public FGEventInput {
public:
FGHIDEventInput();
virtual ~FGHIDEventInput();
// Subsystem API.
void init() override;
void postinit() override;
void reinit() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input-event-hid"; }
private:
class FGHIDEventInputPrivate;
std::unique_ptr<FGHIDEventInputPrivate> d;
};
#endif

View File

@@ -0,0 +1,472 @@
// FGJoystickInput.cxx -- handle user input from joystick devices
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#include "config.h"
#include "FGJoystickInput.hxx"
#if defined(SG_WINDOWS)
# include <windows.h>
#endif
#include <cmath>
#include <simgear/debug/ErrorReportingCallback.hxx>
#include <simgear/props/props_io.hxx>
#include "FGDeviceConfigurationMap.hxx"
#include <Main/fg_props.hxx>
#include <Scripting/NasalSys.hxx>
using simgear::PropertyList;
FGJoystickInput::axis::axis ()
: last_value(9999999),
tolerance(0.002),
low_threshold(-0.9),
high_threshold(0.9),
interval_sec(0),
delay_sec(0),
release_delay_sec(0),
last_dt(0)
{
}
FGJoystickInput::axis::~axis ()
{
}
FGJoystickInput::joystick::joystick ()
: jsnum(0),
naxes(0),
nbuttons(0),
axes(0),
buttons(0),
predefined(true)
{
}
void FGJoystickInput::joystick::clearAxesAndButtons()
{
delete[] axes;
delete[] buttons;
axes = NULL;
buttons = NULL;
naxes = 0;
nbuttons = 0;
}
FGJoystickInput::joystick::~joystick ()
{
jsnum = 0;
clearAxesAndButtons();
}
FGJoystickInput::FGJoystickInput()
{
}
FGJoystickInput::~FGJoystickInput()
{
_remove(true);
}
void FGJoystickInput::_remove(bool all)
{
SGPropertyNode * js_nodes = fgGetNode("/input/joysticks", true);
for (int i = 0; i < MAX_JOYSTICKS; i++)
{
joystick* joy = &joysticks[i];
// do not remove predefined joysticks info on reinit
if (all || (!joy->predefined))
js_nodes->removeChild("js", i);
joy->plibJS.reset();
joy->clearAxesAndButtons();
}
}
void FGJoystickInput::init()
{
jsInit();
SG_LOG(SG_INPUT, SG_DEBUG, "Initializing joystick bindings");
SGPropertyNode_ptr js_nodes = fgGetNode("/input/joysticks", true);
status_node = fgGetNode("/devices/status/joysticks", true);
FGDeviceConfigurationMap configMap("Input/Joysticks",js_nodes, "js-named");
for (int i = 0; i < MAX_JOYSTICKS; i++) {
jsJoystick * js = new jsJoystick(i);
joysticks[i].plibJS.reset(js);
joysticks[i].initializing = true;
if (js->notWorking()) {
SG_LOG(SG_INPUT, SG_DEBUG, "Joystick " << i << " not found");
continue;
}
const char * name = js->getName();
SGPropertyNode_ptr js_node = js_nodes->getChild("js", i);
if (js_node) {
SG_LOG(SG_INPUT, SG_INFO, "Using existing bindings for joystick " << i);
} else {
joysticks[i].predefined = false;
SG_LOG(SG_INPUT, SG_INFO, "Looking for bindings for joystick \"" << name << '"');
SGPropertyNode_ptr named;
// allow distinguishing duplicated devices by the name
string indexedName = computeDeviceIndexName(name, i);
if (configMap.hasConfiguration(indexedName)) {
named = configMap.configurationForDeviceName(indexedName);
std::string source = named->getStringValue("source", "user defined");
SG_LOG(SG_INPUT, SG_INFO, "... found joystick: " << source);
} else if (configMap.hasConfiguration(name)) {
named = configMap.configurationForDeviceName(name);
std::string source = named->getStringValue("source", "user defined");
SG_LOG(SG_INPUT, SG_INFO, "... found joystick: " << source);
} else if ((named = configMap.configurationForDeviceName("default"))) {
std::string source = named->getStringValue("source", "user defined");
SG_LOG(SG_INPUT, SG_INFO, "No config found for joystick \"" << name
<< "\"\nUsing default: \"" << source << '"');
} else {
SG_LOG(SG_INPUT, SG_WARN, "No joystick configuration file with <name>" << name << "</name> entry found!");
}
js_node = js_nodes->getChild("js", i, true);
copyProperties(named, js_node);
js_node->setStringValue("id", name);
}
}
}
std::string FGJoystickInput::computeDeviceIndexName(const std::string& name,
int lastIndex) const
{
int index = 0;
for (int i = 0; i < lastIndex; i++) {
if (joysticks[i].plibJS && (joysticks[i].plibJS->getName() == name)) {
++index;
}
}
std::ostringstream os;
os << name << "_" << index;
return os.str();
}
void FGJoystickInput::reinit()
{
SG_LOG(SG_INPUT, SG_DEBUG, "Re-Initializing joystick bindings");
_remove(false);
#if defined(SG_MAC)
jsShutdown();
#endif
FGJoystickInput::init();
FGJoystickInput::postinit();
}
void FGJoystickInput::postinit()
{
FGNasalSys *nasalsys = (FGNasalSys *)globals->get_subsystem("nasal");
SGPropertyNode_ptr js_nodes = fgGetNode("/input/joysticks");
for (int i = 0; i < MAX_JOYSTICKS; i++) {
SGPropertyNode_ptr js_node = js_nodes->getChild("js", i);
jsJoystick *js = joysticks[i].plibJS.get();
if (!js_node || js->notWorking())
continue;
// FIXME : need to get input device path to disambiguate
simgear::ErrorReportContext errCtx("input-device", "");
#ifdef WIN32
JOYCAPS jsCaps ;
joyGetDevCaps( i, &jsCaps, sizeof(jsCaps) );
unsigned int nbuttons = jsCaps.wNumButtons;
if (nbuttons > MAX_JOYSTICK_BUTTONS) nbuttons = MAX_JOYSTICK_BUTTONS;
#else
unsigned int nbuttons = MAX_JOYSTICK_BUTTONS;
#endif
int naxes = js->getNumAxes();
if (naxes > MAX_JOYSTICK_AXES) naxes = MAX_JOYSTICK_AXES;
joysticks[i].naxes = naxes;
joysticks[i].nbuttons = nbuttons;
SG_LOG(SG_INPUT, SG_DEBUG, "Initializing joystick " << i);
// Set up range arrays
float minRange[MAX_JOYSTICK_AXES];
float maxRange[MAX_JOYSTICK_AXES];
float center[MAX_JOYSTICK_AXES];
// Initialize with default values
js->getMinRange(minRange);
js->getMaxRange(maxRange);
js->getCenter(center);
// Allocate axes and buttons
joysticks[i].axes = new axis[naxes];
joysticks[i].buttons = new FGButton[nbuttons];
//
// Initialize nasal groups.
//
std::ostringstream str;
str << "__js" << i;
std::string module = str.str();
nasalsys->createModule(module.c_str(), module.c_str(), "", 0);
PropertyList nasal = js_node->getChildren("nasal");
unsigned int j;
for (j = 0; j < nasal.size(); j++) {
nasal[j]->setStringValue("module", module.c_str());
bool ok = nasalsys->handleCommand(nasal[j], nullptr);
if (!ok) {
// TODO: get the Nasal errors logged properly
simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::InputDeviceConfig,
"Failed to parse input device Nasal");
}
}
//
// Initialize the axes.
//
PropertyList axes = js_node->getChildren("axis");
size_t nb_axes = axes.size();
for (j = 0; j < nb_axes; j++ ) {
const SGPropertyNode * axis_node = axes[j];
const SGPropertyNode * num_node = axis_node->getChild("number");
int n_axis = axis_node->getIndex();
if (num_node != 0) {
n_axis = num_node->getIntValue(TGT_PLATFORM, -1);
#ifdef SG_MAC
// Mac falls back to Unix by default - avoids specifying many
// duplicate <mac> entries in joystick config files
if (n_axis < 0) {
n_axis = num_node->getIntValue("unix", -1);
}
#endif
// Silently ignore platforms that are not specified within the
// <number></number> section
if (n_axis < 0) {
continue;
}
}
if (n_axis >= naxes) {
SG_LOG(SG_INPUT, SG_DEBUG, "Dropping bindings for axis " << n_axis);
continue;
}
axis &a = joysticks[i].axes[n_axis];
js->setDeadBand(n_axis, axis_node->getDoubleValue("dead-band", 0.0));
a.tolerance = axis_node->getDoubleValue("tolerance", 0.002);
minRange[n_axis] = axis_node->getDoubleValue("min-range", minRange[n_axis]);
maxRange[n_axis] = axis_node->getDoubleValue("max-range", maxRange[n_axis]);
center[n_axis] = axis_node->getDoubleValue("center", center[n_axis]);
read_bindings(axis_node, a.bindings, KEYMOD_NONE, module );
// Initialize the virtual axis buttons.
a.low.init(axis_node->getChild("low"), "low", module );
a.low_threshold = axis_node->getDoubleValue("low-threshold", -0.9);
a.high.init(axis_node->getChild("high"), "high", module );
a.high_threshold = axis_node->getDoubleValue("high-threshold", 0.9);
a.interval_sec = axis_node->getDoubleValue("interval-sec",0.0);
a.delay_sec = axis_node->getDoubleValue("delay-sec",0.0);
a.release_delay_sec = axis_node->getDoubleValue("release-delay-sec",0.0);
a.last_dt = 0.0;
}
//
// Initialize the buttons.
//
PropertyList buttons = js_node->getChildren("button");
for (auto button_node : buttons ) {
size_t n_but = button_node->getIndex();
const SGPropertyNode * num_node = button_node->getChild("number");
if (NULL != num_node)
n_but = num_node->getIntValue(TGT_PLATFORM,n_but);
if (n_but >= nbuttons) {
SG_LOG(SG_INPUT, SG_DEBUG, "Dropping bindings for button " << n_but);
continue;
}
std::ostringstream buf;
buf << (unsigned)n_but;
SG_LOG(SG_INPUT, SG_DEBUG, "Initializing button " << buf.str());
FGButton &b = joysticks[i].buttons[n_but];
b.init(button_node, buf.str(), module );
// get interval-sec property
b.interval_sec = button_node->getDoubleValue("interval-sec",0.0);
b.delay_sec = button_node->getDoubleValue("delay-sec",0.0);
b.release_delay_sec = button_node->getDoubleValue("release-delay-sec",0.0);
b.last_dt = 0.0;
}
js->setMinRange(minRange);
js->setMaxRange(maxRange);
js->setCenter(center);
}
}
void FGJoystickInput::updateJoystick(int index, FGJoystickInput::joystick* joy, double dt)
{
float axis_values[MAX_JOYSTICK_AXES];
int modifiers = fgGetKeyModifiers();
int buttons;
bool pressed, last_state;
bool axes_initialized;
float delay;
jsJoystick * js = joy->plibJS.get();
if (js == 0 || js->notWorking()) {
joysticks[index].initializing = true;
if (js) {
joysticks[index].init_dt += dt;
if (joysticks[index].init_dt >= 1.0) {
joysticks[index].plibJS.reset( new jsJoystick(index) );
joysticks[index].init_dt = 0.0;
}
}
return;
}
js->read(&buttons, axis_values);
if (js->notWorking()) { // If js is disconnected
joysticks[index].initializing = true;
return;
}
// Joystick axes can get initialized to extreme values, at least on Linux.
// Wait until one of the axes get a different value before continuing.
// https://sourceforge.net/p/flightgear/codetickets/2185/
axes_initialized = true;
if (joysticks[index].initializing) {
if (!joysticks[index].initialized) {
js->read(NULL, joysticks[index].values);
joysticks[index].initialized = true;
}
int j;
for (j = 0; j < joy->naxes; j++) {
if (axis_values[j] != joysticks[index].values[j]) break;
}
if (j < joy->naxes) {
joysticks[index].initializing = false;
} else {
axes_initialized = false;
}
}
// Update device status
SGPropertyNode_ptr status = status_node->getChild("joystick", index, true);
if (axes_initialized) {
for (int j = 0; j < MAX_JOYSTICK_AXES; j++) {
status->getChild("axis", j, true)->setFloatValue(axis_values[j]);
}
}
for (int j = 0; j < MAX_JOYSTICK_BUTTONS; j++) {
status->getChild("button", j, true)->setBoolValue((buttons & (1u << j)) > 0 );
}
// Fire bindings for the axes.
if (axes_initialized) {
for (int j = 0; j < joy->naxes; j++) {
axis &a = joy->axes[j];
// Do nothing if the axis position
// is unchanged; only a change in
// position fires the bindings.
// But only if there are bindings
if (fabs(axis_values[j] - a.last_value) > a.tolerance
&& a.bindings[KEYMOD_NONE].size() > 0 ) {
a.last_value = axis_values[j];
for (unsigned int k = 0; k < a.bindings[KEYMOD_NONE].size(); k++)
a.bindings[KEYMOD_NONE][k]->fire(axis_values[j]);
}
// do we have to emulate axis buttons?
last_state = joy->axes[j].low.last_state || joy->axes[j].high.last_state;
pressed = axis_values[j] < a.low_threshold || axis_values[j] > a.high_threshold;
delay = (pressed ? last_state ? a.interval_sec : a.delay_sec : a.release_delay_sec );
if(pressed || last_state) a.last_dt += dt;
else a.last_dt = 0;
if(a.last_dt >= delay) {
if (a.low.bindings[modifiers].size())
joy->axes[j].low.update( modifiers, axis_values[j] < a.low_threshold );
if (a.high.bindings[modifiers].size())
joy->axes[j].high.update( modifiers, axis_values[j] > a.high_threshold );
a.last_dt -= delay;
}
} // of axes iteration
} // axes_initialized
// Fire bindings for the buttons.
for (int j = 0; j < joy->nbuttons; j++) {
FGButton &b = joy->buttons[j];
pressed = (buttons & (1u << j)) > 0;
last_state = joy->buttons[j].last_state;
delay = (pressed ? last_state ? b.interval_sec : b.delay_sec : b.release_delay_sec );
if(pressed || last_state) b.last_dt += dt;
else b.last_dt = 0;
if(b.last_dt >= delay) {
joy->buttons[j].update( modifiers, pressed );
b.last_dt -= delay;
}
} // of butotns iterations
}
void FGJoystickInput::update( double dt )
{
for (int i = 0; i < MAX_JOYSTICKS; i++) {
updateJoystick(i, &joysticks[i], dt);
}
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGJoystickInput> registrantFGJoystickInput(
SGSubsystemMgr::GENERAL,
{{"nasal", SGSubsystemMgr::Dependency::HARD}});

View File

@@ -0,0 +1,116 @@
// FGJoystickInput.hxx -- handle user input from joystick devices
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifndef _FGJOYSTICKINPUT_HXX
#define _FGJOYSTICKINPUT_HXX
#include "FGCommonInput.hxx"
#include "FGButton.hxx"
#include <memory> // for std::unique_ptr
#include <simgear/structure/subsystem_mgr.hxx>
#include "FlightGear_js.h"
////////////////////////////////////////////////////////////////////////
// The Joystick Input Class
////////////////////////////////////////////////////////////////////////
class FGJoystickInput : public SGSubsystem,
FGCommonInput
{
public:
FGJoystickInput();
virtual ~FGJoystickInput();
// Subsystem API.
void init() override;
void postinit() override;
void reinit() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input-joystick"; }
static const int MAX_JOYSTICKS = 16;
static const int MAX_JOYSTICK_AXES = _JS_MAX_AXES;
static const int MAX_JOYSTICK_BUTTONS = 32;
private:
/**
* @brief computeDeviceIndexName - compute the name including the index, based
* on the number of identically named devices. This is used to allow multiple
* different files for identical hardware, especially throttles
* @param name - the base joystick name
* @param lastIndex - don't check names at this index or above. Needed to
* ensure we only check as far as the joystick we are currently processing
* @return
*/
std::string computeDeviceIndexName(const std::string &name, int lastIndex) const;
void _remove(bool all);
SGPropertyNode_ptr status_node;
/**
* Settings for a single joystick axis.
*/
struct axis {
axis ();
virtual ~axis ();
float last_value;
float tolerance;
binding_list_t bindings[KEYMOD_MAX];
float low_threshold;
float high_threshold;
FGButton low;
FGButton high;
float interval_sec, delay_sec, release_delay_sec;
double last_dt;
};
/**
* Settings for a joystick.
*/
struct joystick {
joystick ();
virtual ~joystick ();
int jsnum;
std::unique_ptr<jsJoystick> plibJS;
int naxes;
int nbuttons;
axis * axes;
FGButton * buttons;
bool predefined;
bool initializing = true;
bool initialized = false;
float values[MAX_JOYSTICK_AXES];
double init_dt = 0.0f;
void clearAxesAndButtons();
};
joystick joysticks[MAX_JOYSTICKS];
void updateJoystick(int index, joystick* joy, double dt);
};
#endif

View File

@@ -0,0 +1,258 @@
// FGKeyboardInput.cxx -- handle user input from keyboard devices
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "FGKeyboardInput.hxx"
#include <Main/fg_props.hxx>
#include <Scripting/NasalSys.hxx>
using simgear::PropertyList;
static int getModifiers ()
{
return fgGetKeyModifiers() >> 1;
}
static bool getModShift ()
{
return (fgGetKeyModifiers() & KEYMOD_SHIFT) != 0;
}
static bool getModCtrl ()
{
return (fgGetKeyModifiers() & KEYMOD_CTRL) != 0;
}
static bool getModAlt ()
{
return (fgGetKeyModifiers() & KEYMOD_ALT) != 0;
}
static bool getModMeta ()
{
return (fgGetKeyModifiers() & KEYMOD_META) != 0;
}
static bool getModSuper ()
{
return (fgGetKeyModifiers() & KEYMOD_SUPER) != 0;
}
static bool getModHyper ()
{
return (fgGetKeyModifiers() & KEYMOD_HYPER) != 0;
}
FGKeyboardInput * FGKeyboardInput::keyboardInput = NULL;
FGKeyboardInput::FGKeyboardInput() :
_key_event(fgGetNode("/devices/status/keyboard/event", true)),
_key_code(0),
_key_modifiers(0),
_key_pressed(0),
_key_shift(0),
_key_ctrl(0),
_key_alt(0),
_key_meta(0),
_key_super(0),
_key_hyper(0)
{
if( keyboardInput == NULL )
keyboardInput = this;
}
FGKeyboardInput::~FGKeyboardInput()
{
if( keyboardInput == this )
keyboardInput = NULL;
}
void FGKeyboardInput::init()
{
fgRegisterKeyHandler(keyHandler);
}
void FGKeyboardInput::postinit()
{
SG_LOG(SG_INPUT, SG_DEBUG, "Initializing key bindings");
std::string module = "__kbd";
SGPropertyNode * key_nodes = fgGetNode("/input/keyboard");
if (key_nodes == NULL) {
SG_LOG(SG_INPUT, SG_WARN, "No key bindings (/input/keyboard)!!");
key_nodes = fgGetNode("/input/keyboard", true);
}
FGNasalSys *nasalsys = (FGNasalSys *)globals->get_subsystem("nasal");
PropertyList nasal = key_nodes->getChildren("nasal");
for (unsigned int j = 0; j < nasal.size(); j++) {
nasal[j]->setStringValue("module", module.c_str());
nasalsys->handleCommand(nasal[j], nullptr);
}
PropertyList keys = key_nodes->getChildren("key");
for (unsigned int i = 0; i < keys.size(); i++) {
int index = keys[i]->getIndex();
SG_LOG(SG_INPUT, SG_DEBUG, "Binding key " << index);
if( index >= MAX_KEYS ) {
SG_LOG(SG_INPUT, SG_WARN, "Key binding " << index << " out of range");
continue;
}
bindings[index].bindings->clear();
bindings[index].is_repeatable = keys[i]->getBoolValue("repeatable");
bindings[index].last_state = 0;
read_bindings(keys[i], bindings[index].bindings, KEYMOD_NONE, module );
}
}
void FGKeyboardInput::bind()
{
_tiedProperties.setRoot(fgGetNode("/devices/status", true));
_tiedProperties.Tie<int>("keyboard", getModifiers);
_tiedProperties.Tie<bool>("keyboard/shift", getModShift);
_tiedProperties.Tie<bool>("keyboard/ctrl", getModCtrl);
_tiedProperties.Tie<bool>("keyboard/alt", getModAlt);
_tiedProperties.Tie<bool>("keyboard/meta", getModMeta);
_tiedProperties.Tie<bool>("keyboard/super", getModSuper);
_tiedProperties.Tie<bool>("keyboard/hyper", getModHyper);
_tiedProperties.Tie(_key_event->getNode("key", true), SGRawValuePointer<int>(&_key_code));
_tiedProperties.Tie(_key_event->getNode("pressed", true), SGRawValuePointer<bool>(&_key_pressed));
_tiedProperties.Tie(_key_event->getNode("modifier", true), SGRawValuePointer<int>(&_key_modifiers));
_tiedProperties.Tie(_key_event->getNode("modifier/shift", true), SGRawValuePointer<bool>(&_key_shift));
_tiedProperties.Tie(_key_event->getNode("modifier/ctrl", true), SGRawValuePointer<bool>(&_key_ctrl));
_tiedProperties.Tie(_key_event->getNode("modifier/alt", true), SGRawValuePointer<bool>(&_key_alt));
_tiedProperties.Tie(_key_event->getNode("modifier/meta", true), SGRawValuePointer<bool>(&_key_meta));
_tiedProperties.Tie(_key_event->getNode("modifier/super", true), SGRawValuePointer<bool>(&_key_super));
_tiedProperties.Tie(_key_event->getNode("modifier/hyper", true), SGRawValuePointer<bool>(&_key_hyper));
}
void FGKeyboardInput::unbind()
{
_tiedProperties.Untie();
}
void FGKeyboardInput::update( double dt )
{
// nothing to do
}
const FGCommonInput::binding_list_t & FGKeyboardInput::_find_key_bindings (unsigned int k, int modifiers)
{
unsigned char kc = (unsigned char)k;
FGButton &b = bindings[k];
// Try it straight, first.
if (! b.bindings[modifiers].empty())
return b.bindings[modifiers];
// Alt-Gr is CTRL+ALT
else if (modifiers&(KEYMOD_CTRL|KEYMOD_ALT))
return _find_key_bindings(k, modifiers&~(KEYMOD_CTRL|KEYMOD_ALT));
// Try removing the control modifier
// for control keys.
else if ((modifiers&KEYMOD_CTRL) && iscntrl(kc))
return _find_key_bindings(k, modifiers&~KEYMOD_CTRL);
// Try removing shift modifier
// for upper case or any punctuation
// (since different keyboards will
// shift different punctuation types)
else if ((modifiers&KEYMOD_SHIFT) && (isupper(kc) || ispunct(kc)))
return _find_key_bindings(k, modifiers&~KEYMOD_SHIFT);
// Try removing alt modifier for
// high-bit characters.
else if ((modifiers&KEYMOD_ALT) && k >= 128 && k < 256)
return _find_key_bindings(k, modifiers&~KEYMOD_ALT);
// Give up and return the empty vector.
else
return b.bindings[modifiers];
}
void FGKeyboardInput::doKey (int k, int modifiers, int x, int y)
{
// Sanity check.
if (k < 0 || k >= MAX_KEYS) {
// normal for unsupported keys (i.e. left/right shift key press events)
SG_LOG(SG_INPUT, SG_DEBUG, "Key value " << k << " out of range");
return;
}
_key_code = k;
_key_modifiers = modifiers >> 1;
_key_pressed = (modifiers & KEYMOD_RELEASED) == 0;
_key_shift = getModShift();
_key_ctrl = getModCtrl();
_key_alt = getModAlt();
_key_meta = getModMeta();
_key_super = getModSuper();
_key_hyper = getModHyper();
_key_event->fireValueChanged();
if (_key_code < 0)
return;
k = _key_code;
modifiers = _key_modifiers << 1;
if (!_key_pressed)
modifiers |= KEYMOD_RELEASED;
FGButton &b = bindings[k];
// Key pressed.
if (!(modifiers & KEYMOD_RELEASED)) {
SG_LOG( SG_INPUT, SG_DEBUG, "User pressed key " << k << " with modifiers " << modifiers );
if (!b.last_state || b.is_repeatable) {
const binding_list_t &bindings = _find_key_bindings(k, modifiers);
fireBindingList(bindings);
b.last_state = 1;
}
}
// Key released.
else {
SG_LOG(SG_INPUT, SG_DEBUG, "User released key " << k << " with modifiers " << modifiers);
if (b.last_state) {
const binding_list_t &bindings = _find_key_bindings(k, modifiers);
fireBindingList(bindings);
b.last_state = 0;
}
}
}
void FGKeyboardInput::keyHandler(int key, int keymod, int mousex, int mousey)
{
if( keyboardInput)
keyboardInput->doKey(key, keymod, mousex, mousey);
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGKeyboardInput> registrantFGKeyboardInput(
SGSubsystemMgr::GENERAL,
{{"nasal", SGSubsystemMgr::Dependency::HARD}});

View File

@@ -0,0 +1,79 @@
// FGKeyboardInput.hxx -- handle user input from keyboard devices
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifndef _FGKEYBOARDINPUT_HXX
#define _FGKEYBOARDINPUT_HXX
#ifndef __cplusplus
# error This library requires C++
#endif
#include "FGCommonInput.hxx"
#include "FGButton.hxx"
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/props/tiedpropertylist.hxx>
////////////////////////////////////////////////////////////////////////
// The Keyboard Input Class
////////////////////////////////////////////////////////////////////////
class FGKeyboardInput : public SGSubsystem,
FGCommonInput
{
public:
FGKeyboardInput();
virtual ~FGKeyboardInput();
// Subsystem API.
void bind() override;
void init() override;
void postinit() override;
void unbind() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input-keyboard"; }
static const int MAX_KEYS = 1024;
private:
const binding_list_t& _find_key_bindings (unsigned int k, int modifiers);
void doKey (int k, int modifiers, int x, int y);
static void keyHandler(int key, int keymod, int mousex, int mousey);
static FGKeyboardInput * keyboardInput;
FGButton bindings[MAX_KEYS];
SGPropertyNode_ptr _key_event;
int _key_code;
int _key_modifiers;
bool _key_pressed;
bool _key_shift;
bool _key_ctrl;
bool _key_alt;
bool _key_meta;
bool _key_super;
bool _key_hyper;
simgear::TiedPropertyList _tiedProperties;
};
#endif

View File

@@ -0,0 +1,553 @@
// FGEventInput.cxx -- handle event driven input devices for the Linux O/S
//
// Written by Torsten Dreyer, started July 2009.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
//
// 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.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <cstring>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "FGLinuxEventInput.hxx"
extern "C" {
#include <libudev.h>
}
#include <poll.h>
#include <linux/input.h>
#include <fcntl.h>
#include <string.h>
struct TypeCode {
unsigned type;
unsigned code;
inline unsigned long hashCode() const {
return (unsigned long)type << 16 | (unsigned long)code;
}
bool operator < ( const TypeCode & other) const {
return hashCode() < other.hashCode();
}
};
// event to name translation table
// events are from include <linux/input.h>
static struct EventTypes {
struct TypeCode typeCode;
const char * name;
} EVENT_TYPES[] = {
{ { EV_SYN, SYN_REPORT }, "syn-report" },
{ { EV_SYN, SYN_CONFIG }, "syn-config" },
// misc
{ { EV_KEY, BTN_0 }, "button-0" },
{ { EV_KEY, BTN_1 }, "button-1" },
{ { EV_KEY, BTN_2 }, "button-2" },
{ { EV_KEY, BTN_3 }, "button-3" },
{ { EV_KEY, BTN_4 }, "button-4" },
{ { EV_KEY, BTN_5 }, "button-5" },
{ { EV_KEY, BTN_6 }, "button-6" },
{ { EV_KEY, BTN_7 }, "button-7" },
{ { EV_KEY, BTN_8 }, "button-8" },
{ { EV_KEY, BTN_9 }, "button-9" },
// mouse
{ { EV_KEY, BTN_LEFT }, "button-left" },
{ { EV_KEY, BTN_RIGHT }, "button-right" },
{ { EV_KEY, BTN_MIDDLE }, "button-middle" },
{ { EV_KEY, BTN_SIDE }, "button-side" },
{ { EV_KEY, BTN_EXTRA }, "button-extra" },
{ { EV_KEY, BTN_FORWARD }, "button-forward" },
{ { EV_KEY, BTN_BACK }, "button-back" },
{ { EV_KEY, BTN_TASK }, "button-task" },
// joystick
{ { EV_KEY, BTN_TRIGGER }, "button-trigger" },
{ { EV_KEY, BTN_THUMB }, "button-thumb" },
{ { EV_KEY, BTN_THUMB2 }, "button-thumb2" },
{ { EV_KEY, BTN_TOP }, "button-top" },
{ { EV_KEY, BTN_TOP2 }, "button-top2" },
{ { EV_KEY, BTN_PINKIE }, "button-pinkie" },
{ { EV_KEY, BTN_BASE }, "button-base" },
{ { EV_KEY, BTN_BASE2 }, "button-base2" },
{ { EV_KEY, BTN_BASE3 }, "button-base3" },
{ { EV_KEY, BTN_BASE4 }, "button-base4" },
{ { EV_KEY, BTN_BASE5 }, "button-base5" },
{ { EV_KEY, BTN_BASE6 }, "button-base6" },
{ { EV_KEY, BTN_DEAD }, "button-dead" },
// gamepad
{ { EV_KEY, BTN_A }, "button-a" },
{ { EV_KEY, BTN_B }, "button-b" },
{ { EV_KEY, BTN_C }, "button-c" },
{ { EV_KEY, BTN_X }, "button-x" },
{ { EV_KEY, BTN_Y }, "button-y" },
{ { EV_KEY, BTN_Z }, "button-z" },
{ { EV_KEY, BTN_TL }, "button-tl" },
{ { EV_KEY, BTN_TR }, "button-tr" },
{ { EV_KEY, BTN_TL2 }, "button-tl2" },
{ { EV_KEY, BTN_TR2 }, "button-tr2" },
{ { EV_KEY, BTN_SELECT }, "button-select" },
{ { EV_KEY, BTN_START }, "button-start" },
{ { EV_KEY, BTN_MODE }, "button-mode" },
{ { EV_KEY, BTN_THUMBL }, "button-thumbl" },
{ { EV_KEY, BTN_THUMBR }, "button-thumbr" },
// digitizer
{ { EV_KEY, BTN_TOOL_PEN }, "button-pen" },
{ { EV_KEY, BTN_TOOL_RUBBER }, "button-rubber" },
{ { EV_KEY, BTN_TOOL_BRUSH }, "button-brush" },
{ { EV_KEY, BTN_TOOL_PENCIL }, "button-pencil" },
{ { EV_KEY, BTN_TOOL_AIRBRUSH }, "button-airbrush" },
{ { EV_KEY, BTN_TOOL_FINGER }, "button-finger" },
{ { EV_KEY, BTN_TOOL_MOUSE }, "button-mouse" },
{ { EV_KEY, BTN_TOOL_LENS }, "button-lens" },
{ { EV_KEY, BTN_TOUCH }, "button-touch" },
{ { EV_KEY, BTN_STYLUS }, "button-stylus" },
{ { EV_KEY, BTN_STYLUS2 }, "button-stylus2" },
{ { EV_KEY, BTN_TOOL_DOUBLETAP }, "button-doubletap" },
{ { EV_KEY, BTN_TOOL_TRIPLETAP }, "button-trippletap" },
{ { EV_KEY, BTN_WHEEL }, "button-wheel" },
{ { EV_KEY, BTN_GEAR_DOWN }, "button-gear-down" },
{ { EV_KEY, BTN_GEAR_UP }, "button-gear-up" },
{ { EV_REL, REL_X }, "rel-x-translate" },
{ { EV_REL, REL_Y}, "rel-y-translate" },
{ { EV_REL, REL_Z}, "rel-z-translate" },
{ { EV_REL, REL_RX}, "rel-x-rotate" },
{ { EV_REL, REL_RY}, "rel-y-rotate" },
{ { EV_REL, REL_RZ}, "rel-z-rotate" },
{ { EV_REL, REL_HWHEEL}, "rel-hwheel" },
{ { EV_REL, REL_DIAL}, "rel-dial" },
{ { EV_REL, REL_WHEEL}, "rel-wheel" },
{ { EV_REL, REL_MISC}, "rel-misc" },
{ { EV_ABS, ABS_X }, "abs-x-translate" },
{ { EV_ABS, ABS_Y }, "abs-y-translate" },
{ { EV_ABS, ABS_Z }, "abs-z-translate" },
{ { EV_ABS, ABS_RX }, "abs-x-rotate" },
{ { EV_ABS, ABS_RY }, "abs-y-rotate" },
{ { EV_ABS, ABS_RZ }, "abs-z-rotate" },
{ { EV_ABS, ABS_THROTTLE }, "abs-throttle" },
{ { EV_ABS, ABS_RUDDER }, "abs-rudder" },
{ { EV_ABS, ABS_WHEEL }, "abs-wheel" },
{ { EV_ABS, ABS_GAS }, "abs-gas" },
{ { EV_ABS, ABS_BRAKE }, "abs-brake" },
{ { EV_ABS, ABS_HAT0X }, "abs-hat0-x" },
{ { EV_ABS, ABS_HAT0Y }, "abs-hat0-y" },
{ { EV_ABS, ABS_HAT1X }, "abs-hat1-x" },
{ { EV_ABS, ABS_HAT1Y }, "abs-hat1-y" },
{ { EV_ABS, ABS_HAT2X }, "abs-hat2-x" },
{ { EV_ABS, ABS_HAT2Y }, "abs-hat2-y" },
{ { EV_ABS, ABS_HAT3X }, "abs-hat3-x" },
{ { EV_ABS, ABS_HAT3Y }, "abs-hat3-y" },
{ { EV_ABS, ABS_PRESSURE }, "abs-pressure" },
{ { EV_ABS, ABS_DISTANCE }, "abs-distance" },
{ { EV_ABS, ABS_TILT_X }, "abs-tilt-x" },
{ { EV_ABS, ABS_TILT_Y }, "abs-tilt-y" },
{ { EV_ABS, ABS_TOOL_WIDTH }, "abs-toold-width" },
{ { EV_ABS, ABS_VOLUME }, "abs-volume" },
{ { EV_ABS, ABS_MISC }, "abs-misc" },
{ { EV_MSC, MSC_SERIAL }, "misc-serial" },
{ { EV_MSC, MSC_PULSELED }, "misc-pulseled" },
{ { EV_MSC, MSC_GESTURE }, "misc-gesture" },
{ { EV_MSC, MSC_RAW }, "misc-raw" },
{ { EV_MSC, MSC_SCAN }, "misc-scan" },
// switch
{ { EV_SW, SW_LID }, "switch-lid" },
{ { EV_SW, SW_TABLET_MODE }, "switch-tablet-mode" },
{ { EV_SW, SW_HEADPHONE_INSERT }, "switch-headphone-insert" },
#ifdef SW_RFKILL_ALL
{ { EV_SW, SW_RFKILL_ALL }, "switch-rfkill" },
#endif
#ifdef SW_MICROPHONE_INSERT
{ { EV_SW, SW_MICROPHONE_INSERT }, "switch-microphone-insert" },
#endif
#ifdef SW_DOCK
{ { EV_SW, SW_DOCK }, "switch-dock" },
#endif
{ { EV_LED, LED_NUML}, "led-numlock" },
{ { EV_LED, LED_CAPSL}, "led-capslock" },
{ { EV_LED, LED_SCROLLL}, "led-scrolllock" },
{ { EV_LED, LED_COMPOSE}, "led-compose" },
{ { EV_LED, LED_KANA}, "led-kana" },
{ { EV_LED, LED_SLEEP}, "led-sleep" },
{ { EV_LED, LED_SUSPEND}, "led-suspend" },
{ { EV_LED, LED_MUTE}, "led-mute" },
{ { EV_LED, LED_MISC}, "led-misc" },
{ { EV_LED, LED_MAIL}, "led-mail" },
{ { EV_LED, LED_CHARGING}, "led-charging" }
};
static struct enbet {
unsigned type;
const char * name;
} EVENT_NAMES_BY_EVENT_TYPE[] = {
{ EV_SYN, "syn" },
{ EV_KEY, "button" },
{ EV_REL, "rel" },
{ EV_ABS, "abs" },
{ EV_MSC, "msc" },
{ EV_SW, "button" },
{ EV_LED, "led" },
{ EV_SND, "snd" },
{ EV_REP, "rep" },
{ EV_FF, "ff" },
{ EV_PWR, "pwr" },
{ EV_FF_STATUS, "ff-status" }
};
class EventNameByEventType : public std::map<unsigned,const char*> {
public:
EventNameByEventType() {
for( unsigned i = 0; i < sizeof(EVENT_NAMES_BY_EVENT_TYPE)/sizeof(EVENT_NAMES_BY_EVENT_TYPE[0]); i++ )
(*this)[EVENT_NAMES_BY_EVENT_TYPE[i].type] = EVENT_NAMES_BY_EVENT_TYPE[i].name;
}
};
static EventNameByEventType EVENT_NAME_BY_EVENT_TYPE;
class EventNameByType : public std::map<TypeCode,const char*> {
public:
EventNameByType() {
for( unsigned i = 0; i < sizeof(EVENT_TYPES)/sizeof(EVENT_TYPES[0]); i++ )
(*this)[EVENT_TYPES[i].typeCode] = EVENT_TYPES[i].name;
}
};
static EventNameByType EVENT_NAME_BY_TYPE;
struct ltstr {
bool operator()(const char * s1, const char * s2 ) const {
return std::string(s1).compare( s2 ) < 0;
}
};
class EventTypeByName : public std::map<const char *,TypeCode,ltstr> {
public:
EventTypeByName() {
for( unsigned i = 0; i < sizeof(EVENT_TYPES)/sizeof(EVENT_TYPES[0]); i++ )
(*this)[EVENT_TYPES[i].name] = EVENT_TYPES[i].typeCode;
}
};
static EventTypeByName EVENT_TYPE_BY_NAME;
FGLinuxInputDevice::FGLinuxInputDevice( std::string aName, std::string aDevname, std::string aSerial ) :
FGInputDevice(aName,aSerial),
devname( aDevname ),
fd(-1)
{
}
FGLinuxInputDevice::~FGLinuxInputDevice()
{
try {
Close();
}
catch(...) {
}
}
FGLinuxInputDevice::FGLinuxInputDevice() :
fd(-1)
{
}
static inline bool bitSet( unsigned char * buf, unsigned bit )
{
return (buf[bit/sizeof(unsigned char)/8] >> (bit%(sizeof(unsigned char)*8))) & 1;
}
bool FGLinuxInputDevice::Open()
{
if( fd != -1 ) return true;
if( (fd = ::open( devname.c_str(), O_RDWR )) == -1 ) {
SG_LOG( SG_INPUT, SG_ALERT, "Cannot open devname='" << devname
<< "'. errno=" << errno
<< ": " << strerror(errno)
);
//throw std::exception();
return false;
}
if( GetGrab() && ioctl( fd, EVIOCGRAB, 2 ) == -1 ) {
SG_LOG( SG_INPUT, SG_WARN, "Can't grab " << devname << " for exclusive access" );
}
{
unsigned char buf[ABS_CNT/sizeof(unsigned char)/8];
// get axes maximums
if( ioctl( fd, EVIOCGBIT(EV_ABS,ABS_MAX), buf ) == -1 ) {
SG_LOG( SG_INPUT, SG_WARN, "Can't get abs-axes for " << devname );
} else {
for( unsigned i = 0; i < ABS_MAX; i++ ) {
if( bitSet( buf, i ) ) {
struct input_absinfo ai;
if( ioctl(fd, EVIOCGABS(i), &ai) == -1 ) {
SG_LOG( SG_INPUT, SG_WARN, "Can't get abs-axes maximums for " << devname );
continue;
}
absinfo[i] = ai;
/*
SG_LOG( SG_INPUT, SG_INFO, "Axis #" << i <<
": value=" << ai.value <<
": minimum=" << ai.minimum <<
": maximum=" << ai.maximum <<
": fuzz=" << ai.fuzz <<
": flat=" << ai.flat <<
": resolution=" << ai.resolution );
*/
// kick an initial event
struct input_event event;
event.type = EV_ABS;
event.code = i;
event.value = ai.value;
FGLinuxEventData eventData( event, 0, 0 );
eventData.value = Normalize( event );
HandleEvent(eventData);
}
}
}
}
{
unsigned char mask[KEY_CNT/sizeof(unsigned char)/8];
unsigned char flag[KEY_CNT/sizeof(unsigned char)/8];
memset(mask,0,sizeof(mask));
memset(flag,0,sizeof(flag));
if( ioctl( fd, EVIOCGKEY(sizeof(flag)), flag ) == -1 ||
ioctl( fd, EVIOCGBIT(EV_KEY, sizeof(mask)), mask ) == -1 ) {
SG_LOG( SG_INPUT, SG_WARN, "Can't get keys for " << devname );
} else {
for( unsigned i = 0; i < KEY_MAX; i++ ) {
if( bitSet( mask, i ) ) {
struct input_event event;
event.type = EV_KEY;
event.code = i;
event.value = bitSet(flag,i);
FGLinuxEventData eventData( event, 0, 0 );
HandleEvent(eventData);
}
}
}
}
{
unsigned char buf[SW_CNT/sizeof(unsigned char)/8];
if( ioctl( fd, EVIOCGSW(sizeof(buf)), buf ) == -1 ) {
SG_LOG( SG_INPUT, SG_WARN, "Can't get switches for " << devname );
} else {
for( unsigned i = 0; i < SW_MAX; i++ ) {
if( bitSet( buf, i ) ) {
struct input_event event;
event.type = EV_SW;
event.code = i;
event.value = 1;
FGLinuxEventData eventData( event, 0, 0 );
HandleEvent(eventData);
}
}
}
}
return true;
}
double FGLinuxInputDevice::Normalize( struct input_event & event )
{
if( absinfo.count(event.code) > 0 ) {
const struct input_absinfo & ai = absinfo[(unsigned int)event.code];
if( ai.maximum == ai.minimum )
return 0.0;
return ((double)event.value-(double)ai.minimum)/((double)ai.maximum-(double)ai.minimum);
} else {
return (double)event.value;
}
}
void FGLinuxInputDevice::Close()
{
if( fd != -1 ) {
if( GetGrab() && ioctl( fd, EVIOCGRAB, 0 ) != 0 ) {
SG_LOG( SG_INPUT, SG_WARN, "Can't ungrab " << devname );
}
::close(fd);
}
fd = -1;
}
void FGLinuxInputDevice::Send( const char * eventName, double value )
{
if( EVENT_TYPE_BY_NAME.count( eventName ) <= 0 ) {
SG_LOG( SG_INPUT, SG_ALERT, "Can't send unknown event " << eventName );
return;
}
TypeCode & typeCode = EVENT_TYPE_BY_NAME[ eventName ];
if( fd == -1 )
return;
input_event evt;
evt.type=typeCode.type;
evt.code = typeCode.code;
evt.value = (long)value;
evt.time.tv_sec = 0;
evt.time.tv_usec = 0;
size_t bytes_written = write(fd, &evt, sizeof(evt));
if( bytes_written == sizeof(evt) )
SG_LOG( SG_INPUT,
SG_DEBUG,
"Written event " << eventName << " as type=" << evt.type
<< ", code=" << evt.code
<< " value=" << evt.value );
else
SG_LOG( SG_INPUT,
SG_WARN,
"Failed to write event: written = " << bytes_written );
}
static char ugly_buffer[128];
const char * FGLinuxInputDevice::TranslateEventName( FGEventData & eventData )
{
FGLinuxEventData & linuxEventData = (FGLinuxEventData&)eventData;
TypeCode typeCode;
typeCode.type = linuxEventData.type;
typeCode.code = linuxEventData.code;
if( EVENT_NAME_BY_TYPE.count(typeCode) == 0 ) {
// event not known in translation tables
if( EVENT_NAME_BY_EVENT_TYPE.count(linuxEventData.type) == 0 ) {
// event type not known in translation tables
sprintf( ugly_buffer, "unknown-%u-%u", (unsigned)linuxEventData.type, (unsigned)linuxEventData.code );
return ugly_buffer;
}
sprintf( ugly_buffer, "%s-%u", EVENT_NAME_BY_EVENT_TYPE[linuxEventData.type], (unsigned)linuxEventData.code );
return ugly_buffer;
}
return EVENT_NAME_BY_TYPE[typeCode];
}
void FGLinuxInputDevice::SetDevname( const std::string & name )
{
this->devname = name;
}
FGLinuxEventInput::FGLinuxEventInput()
{
}
FGLinuxEventInput::~FGLinuxEventInput()
{
}
void FGLinuxEventInput::postinit()
{
FGEventInput::postinit();
struct udev * udev = udev_new();
// REVIEW: Memory Leak - 55,004 (40 direct, 54,964 indirect) bytes in 1 blocks are definitely lost
struct udev_enumerate *enumerate = udev_enumerate_new(udev);
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
// REVIEW: Memory Leak - 4,995 (40 direct, 4,955 indirect) bytes in 1 blocks are definitely lost
struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
struct udev_list_entry *dev_list_entry;
udev_list_entry_foreach(dev_list_entry, devices) {
const char * path = udev_list_entry_get_name(dev_list_entry);
struct udev_device *dev = udev_device_new_from_syspath(udev, path);
const char * node = udev_device_get_devnode(dev);
struct udev_device * parent_dev = udev_device_get_parent( dev );
if ( parent_dev != NULL ) {
const char * name = udev_device_get_sysattr_value(parent_dev,"name");
const char * serial = udev_device_get_sysattr_value(parent_dev, "serial");
SG_LOG(SG_INPUT,SG_DEBUG, "name=" << (name?name:"<null>") << ", node=" << (node?node:"<null>"));
if( name && node ) {
std::string serialString = serial ? serial : std::string{};
AddDevice( new FGLinuxInputDevice(name, node, serialString) );
}
}
udev_device_unref(dev);
}
udev_enumerate_unref(enumerate); // REVIEW: this should fix the memory leak
udev_unref(udev);
}
void FGLinuxEventInput::update( double dt )
{
FGEventInput::update( dt );
// index the input devices by the associated fd and prepare
// the pollfd array by filling in the file descriptor
struct pollfd fds[input_devices.size()];
std::map<int,FGLinuxInputDevice*> devicesByFd;
std::map<int,FGInputDevice*>::const_iterator it;
int i;
for( i=0, it = input_devices.begin(); it != input_devices.end(); ++it, i++ ) {
FGInputDevice* p = (*it).second;
int fd = ((FGLinuxInputDevice*)p)->GetFd();
fds[i].fd = fd;
fds[i].events = POLLIN;
devicesByFd[fd] = (FGLinuxInputDevice*)p;
}
int modifiers = fgGetKeyModifiers();
// poll all devices until no more events are in the queue
// do no more than maxpolls in a single loop to prevent locking
int maxpolls = 100;
while( maxpolls-- > 0 && ::poll( fds, i, 0 ) > 0 ) {
for( unsigned i = 0; i < sizeof(fds)/sizeof(fds[0]); i++ ) {
if( fds[i].revents & POLLIN ) {
// if this device reports data ready, it should be a input_event struct
struct input_event event;
if( read( fds[i].fd, &event, sizeof(event) ) != sizeof(event) )
continue;
FGLinuxEventData eventData( event, dt, modifiers );
if( event.type == EV_ABS )
eventData.value = devicesByFd[fds[i].fd]->Normalize( event );
// let the FGInputDevice handle the data
devicesByFd[fds[i].fd]->HandleEvent( eventData );
}
}
}
}

View File

@@ -0,0 +1,84 @@
// FGEventInput.hxx -- handle event driven input devices for the Linux O/S
//
// Written by Torsten Dreyer, started July 2009
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
//
// 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.
//
// $Id$
#ifndef __FGLINUXEVENTINPUT_HXX
#define __FGLINUXEVENTINPUT_HXX
#include "FGEventInput.hxx"
#include <linux/input.h>
struct FGLinuxEventData : public FGEventData {
FGLinuxEventData( struct input_event & event, double dt, int modifiers ) :
FGEventData( (double)event.value, dt, modifiers ),
type(event.type),
code(event.code) {
}
unsigned type;
unsigned code;
};
/*
* A implementation for linux event devices
*/
class FGLinuxInputDevice : public FGInputDevice
{
public:
FGLinuxInputDevice();
FGLinuxInputDevice( std::string name, std::string devname, std::string aSerial );
virtual ~FGLinuxInputDevice();
bool Open() override;
void Close() override;
void Send( const char * eventName, double value ) override;
const char * TranslateEventName( FGEventData & eventData ) override;
void SetDevname( const std::string & name );
std::string GetDevname() const { return devname; }
int GetFd() { return fd; }
double Normalize( struct input_event & event );
private:
std::string devname;
int fd;
std::map<unsigned int,input_absinfo> absinfo;
};
class FGLinuxEventInput : public FGEventInput
{
public:
FGLinuxEventInput();
virtual ~ FGLinuxEventInput();
// Subsystem API.
void postinit() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input-event"; }
protected:
};
#endif

770
src/Input/FGMouseInput.cxx Normal file
View File

@@ -0,0 +1,770 @@
// FGMouseInput.cxx -- handle user input from mouse devices
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <memory>
#include "FGMouseInput.hxx"
#include <osgGA/GUIEventAdapter>
#include <simgear/scene/util/SGPickCallback.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/scene/model/SGPickAnimation.hxx>
#include "FGButton.hxx"
#include "Main/globals.hxx"
#include <Viewer/renderer.hxx>
#include <Viewer/sview.hxx>
#include <Model/panelnode.hxx>
#include <Cockpit/panel.hxx>
#include <Viewer/FGEventHandler.hxx>
#include <GUI/MouseCursor.hxx>
using std::ios_base;
const int MAX_MICE = 1;
const int MAX_MOUSE_BUTTONS = 8;
typedef std::vector<SGSceneryPick> SGSceneryPicks;
typedef SGSharedPtr<SGPickCallback> SGPickCallbackPtr;
typedef std::list<SGPickCallbackPtr> SGPickCallbackList;
////////////////////////////////////////////////////////////////////////
/**
* List of currently pressed mouse button events
*/
class ActivePickCallbacks:
public std::map<int, SGPickCallbackList>
{
public:
void update( double dt, unsigned int keyModState );
void init( int button, const osgGA::GUIEventAdapter* ea );
};
void ActivePickCallbacks::init( int button, const osgGA::GUIEventAdapter* ea )
{
osg::Vec2d windowPos;
flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
// Get the list of hit callbacks. Take the first callback that
// accepts the mouse button press and ignore the rest of them
// That is they get sorted by distance and by scenegraph depth.
// The nearest one is the first one and the deepest
// (the most specialized one in the scenegraph) is the first.
SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos);
if (pickList.empty()) {
return;
}
SGSceneryPicks::const_iterator i;
for (i = pickList.begin(); i != pickList.end(); ++i) {
if (i->callback->buttonPressed(button, *ea, i->info)) {
(*this)[button].push_back(i->callback);
return;
}
}
}
void ActivePickCallbacks::update( double dt, unsigned int keyModState )
{
// handle repeatable mouse press events
for( iterator mi = begin(); mi != end(); ++mi ) {
SGPickCallbackList::iterator li;
for (li = mi->second.begin(); li != mi->second.end(); ++li) {
(*li)->update(dt, keyModState);
}
}
}
////////////////////////////////////////////////////////////////////////
/**
* Settings for a mouse mode.
*/
struct mouse_mode {
mouse_mode ();
FGMouseCursor::Cursor cursor;
bool constrained;
bool pass_through;
std::unique_ptr<FGButton[]> buttons;
SGBindingList x_bindings[KEYMOD_MAX];
SGBindingList y_bindings[KEYMOD_MAX];
};
/**
* Settings for a mouse.
*/
struct mouse {
mouse ();
int x, y;
SGPropertyNode_ptr mode_node;
SGPropertyNode_ptr mouse_button_nodes[MAX_MOUSE_BUTTONS];
int nModes;
int current_mode;
SGTimeStamp timeSinceLastMove;
std::unique_ptr<mouse_mode[]> modes;
};
static
const SGSceneryPick*
getPick( const SGSceneryPicks& pick_list,
const SGPickCallback* cb )
{
for(size_t i = 0; i < pick_list.size(); ++i)
if( pick_list[i].callback == cb )
return &pick_list[i];
return 0;
}
////////////////////////////////////////////////////////////////////////
class FGMouseInput::FGMouseInputPrivate : public SGPropertyChangeListener
{
public:
FGMouseInputPrivate() :
haveWarped(false),
xSizeNode(fgGetNode("/sim/startup/xsize", false ) ),
ySizeNode(fgGetNode("/sim/startup/ysize", false ) ),
xAccelNode(fgGetNode("/devices/status/mice/mouse/accel-x", true ) ),
yAccelNode(fgGetNode("/devices/status/mice/mouse/accel-y", true ) ),
mouseXNode(fgGetNode("/devices/status/mice/mouse/x", true)),
mouseYNode(fgGetNode("/devices/status/mice/mouse/y", true))
{
tooltipTimeoutDone = false;
hoverPickScheduled = false;
fgGetNode("/sim/mouse/hide-cursor", true )->addChangeListener(this, true);
fgGetNode("/sim/mouse/cursor-timeout-sec", true )->addChangeListener(this, true);
fgGetNode("/sim/mouse/right-button-mode-cycle-enabled", true)->addChangeListener(this, true);
fgGetNode("/sim/mouse/tooltip-delay-msec", true)->addChangeListener(this, true);
fgGetNode("/sim/mouse/click-shows-tooltip", true)->addChangeListener(this, true);
fgGetNode("/sim/mouse/tooltips-enabled", true)->addChangeListener(this, true);
fgGetNode("/sim/mouse/tooltip-commands-registered", true)->addChangeListener(this, true);
fgGetNode("/sim/mouse/drag-sensitivity", true)->addChangeListener(this, true);
fgGetNode("/sim/mouse/invert-mouse-wheel", true)->addChangeListener(this, true);
}
bool areTooltipsEnabled() const
{
return _tooltipsEnabled && _tooltipsCommandsRegistered;
}
void centerMouseCursor(mouse& m)
{
// center the cursor
m.x = (xSizeNode ? xSizeNode->getIntValue() : 800) / 2;
m.y = (ySizeNode ? ySizeNode->getIntValue() : 600) / 2;
fgWarpMouse(m.x, m.y);
haveWarped = true;
}
void constrainMouse(int x, int y)
{
int new_x=x,new_y=y;
int xsize = xSizeNode ? xSizeNode->getIntValue() : 800;
int ysize = ySizeNode ? ySizeNode->getIntValue() : 600;
bool need_warp = false;
if (x <= (xsize * .25) || x >= (xsize * .75)) {
new_x = int(xsize * .5);
need_warp = true;
}
if (y <= (ysize * .25) || y >= (ysize * .75)) {
new_y = int(ysize * .5);
need_warp = true;
}
if (need_warp)
{
fgWarpMouse(new_x, new_y);
haveWarped = true;
}
}
void scheduleHoverPick(const osg::Vec2d& windowPos)
{
hoverPickScheduled = true;
hoverPos = windowPos;
}
void doHoverPick(const osg::Vec2d& windowPos)
{
FGMouseCursor::Cursor cur = FGMouseCursor::CURSOR_ARROW;
bool explicitCursor = false;
bool didPick = false;
SGPickCallback::Priority priority = SGPickCallback::PriorityScenery;
SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos);
SGSceneryPicks::const_iterator i;
for( i = pickList.begin(); i != pickList.end(); ++i )
{
bool done = i->callback->hover(windowPos, i->info);
std::string curName(i->callback->getCursor());
if (!curName.empty()) {
explicitCursor = true;
cur = FGMouseCursor::cursorFromString(curName.c_str());
}
// if the callback is of higher prioirty (lower enum index),
// record that.
if (i->callback->getPriority() < priority) {
priority = i->callback->getPriority();
}
if (done) {
didPick = true;
break;
}
} // of picks iteration
// Check if any pick from the previous iteration has disappeared. If so
// notify the callback that the mouse has left its element.
for( i = _previous_picks.begin(); i != _previous_picks.end(); ++i )
{
if( !getPick(pickList, i->callback) )
i->callback->mouseLeave(windowPos);
}
_previous_picks = pickList;
if (!explicitCursor && (priority == SGPickCallback::PriorityPanel)) {
cur = FGMouseCursor::CURSOR_HAND;
}
FGMouseCursor::instance()->setCursor(cur);
if (!didPick && areTooltipsEnabled()) {
SGPropertyNode_ptr args(new SGPropertyNode);
globals->get_commands()->execute("update-hover", args, nullptr);
}
}
void doMouseMoveWithCallbacks(const osgGA::GUIEventAdapter* ea)
{
FGMouseCursor::Cursor cur = FGMouseCursor::CURSOR_CLOSED_HAND;
osg::Vec2d windowPos;
const bool ok = flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
if (!ok) {
SG_LOG(SG_GUI, SG_WARN, "doMouseMoveWithCallbacks: ignoring mouse move with missing context/traits");
return;
}
SGSceneryPicks pickList = globals->get_renderer()->pick(windowPos);
if(pickList.empty())
return;
for( ActivePickCallbacks::iterator mi = activePickCallbacks.begin();
mi != activePickCallbacks.end();
++mi )
{
SGPickCallbackList::iterator li;
for( li = mi->second.begin(); li != mi->second.end(); ++li )
{
const SGSceneryPick* pick = getPick(pickList, *li);
(*li)->mouseMoved(*ea, pick ? &pick->info : 0);
std::string curName((*li)->getCursor());
if( !curName.empty() )
cur = FGMouseCursor::cursorFromString(curName.c_str());
}
}
FGMouseCursor::instance()->setCursor(cur);
}
// implement the property-change-listener interfacee
void valueChanged(SGPropertyNode* node) override
{
if (node->getNameString() == "drag-sensitivity") {
SGKnobAnimation::setDragSensitivity(node->getDoubleValue());
} else if (node->getNameString() == "invert-mouse-wheel") {
SGKnobAnimation::setAlternateMouseWheelDirection(node->getBoolValue());
} else if (node->getNameString() == "hide-cursor") {
hideCursor = node->getBoolValue();
} else if (node->getNameString() == "cursor-timeout-sec") {
cursorTimeoutMsec = node->getDoubleValue() * 1000;
} else if (node->getNameString() == "tooltip-delay-msec") {
tooltipDelayMsec = node->getIntValue();
} else if (node->getNameString() == "right-button-mode-cycle-enabled") {
rightClickModeCycle = node->getBoolValue();
} else if (node->getNameString() == "click-shows-tooltip") {
clickTriggersTooltip = node->getBoolValue();
} else if (node->getNameString() == "tooltips-enabled") {
_tooltipsEnabled = node->getBoolValue();
} else if (node->getNameString() == "tooltip-commands-registered") {
_tooltipsCommandsRegistered = node->getBoolValue();
}
}
ActivePickCallbacks activePickCallbacks;
SGSceneryPicks _previous_picks;
mouse mice[MAX_MICE];
bool hideCursor, haveWarped;
bool tooltipTimeoutDone;
bool clickTriggersTooltip;
int tooltipDelayMsec, cursorTimeoutMsec;
bool rightClickModeCycle;
bool _tooltipsEnabled = false;
bool _tooltipsCommandsRegistered = false; ///< avoid errors if the mouse moves before Nasal init
SGPropertyNode_ptr xSizeNode;
SGPropertyNode_ptr ySizeNode;
SGPropertyNode_ptr xAccelNode;
SGPropertyNode_ptr yAccelNode;
SGPropertyNode_ptr mouseXNode, mouseYNode;
bool hoverPickScheduled;
osg::Vec2d hoverPos;
};
////////////////////////////////////////////////////////////////////////
// The Mouse Input Implementation
////////////////////////////////////////////////////////////////////////
static FGMouseInput* global_mouseInput = nullptr;
static void mouseClickHandler(int button, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
{
if(global_mouseInput)
global_mouseInput->doMouseClick(button, updown, x, y, mainWindow, ea);
}
static void mouseMotionHandler(int x, int y, const osgGA::GUIEventAdapter* ea)
{
if (global_mouseInput != 0)
global_mouseInput->doMouseMotion(x, y, ea);
}
FGMouseInput::FGMouseInput() = default;
void FGMouseInput::init()
{
SG_LOG(SG_INPUT, SG_DEBUG, "Initializing mouse bindings");
d.reset(new FGMouseInputPrivate());
std::string module = "";
SGPropertyNode * mouse_nodes = fgGetNode("/input/mice");
if (mouse_nodes == 0) {
SG_LOG(SG_INPUT, SG_WARN, "No mouse bindings (/input/mice)!!");
mouse_nodes = fgGetNode("/input/mice", true);
}
int j;
for (int i = 0; i < MAX_MICE; i++) {
SGPropertyNode * mouse_node = mouse_nodes->getChild("mouse", i, true);
mouse &m = d->mice[i];
// Grab node pointers
std::ostringstream buf;
buf << "/devices/status/mice/mouse[" << i << "]/mode";
m.mode_node = fgGetNode(buf.str().c_str());
if (m.mode_node == NULL) {
m.mode_node = fgGetNode(buf.str().c_str(), true);
m.mode_node->setIntValue(0);
}
for (j = 0; j < MAX_MOUSE_BUTTONS; j++) {
// According to <https://stackoverflow.com/a/12112642/4756009> and other
// similar questions on stackoverflow.com, it seems safer not to try to
// reuse the 'buf' variable we have above.
std::ostringstream buf;
buf << "/devices/status/mice/mouse["<< i << "]/button[" << j << "]";
m.mouse_button_nodes[j] = fgGetNode(buf.str().c_str(), true);
m.mouse_button_nodes[j]->setBoolValue(false);
}
// Read all the modes
m.nModes = mouse_node->getIntValue("mode-count", 1);
m.modes.reset(new mouse_mode[m.nModes]);
for (int j = 0; j < m.nModes; j++) {
int k;
SGPropertyNode * mode_node = mouse_node->getChild("mode", j, true);
// Read the mouse cursor for this mode
m.modes[j].cursor = FGMouseCursor::cursorFromString(mode_node->getStringValue("cursor", "inherit").c_str());
// Read other properties for this mode
m.modes[j].constrained = mode_node->getBoolValue("constrained", false);
m.modes[j].pass_through = mode_node->getBoolValue("pass-through", false);
// Read the button bindings for this mode
m.modes[j].buttons.reset(new FGButton[MAX_MOUSE_BUTTONS]);
for (k = 0; k < MAX_MOUSE_BUTTONS; k++) {
std::ostringstream buf;
buf << "mouse button " << k;
m.modes[j].buttons[k].init( mode_node->getChild("button", k), buf.str(), module );
}
// Read the axis bindings for this mode
read_bindings(mode_node->getChild("x-axis", 0, true), m.modes[j].x_bindings, KEYMOD_NONE, module );
read_bindings(mode_node->getChild("y-axis", 0, true), m.modes[j].y_bindings, KEYMOD_NONE, module );
if (mode_node->hasChild("x-axis-ctrl")) {
read_bindings(mode_node->getChild("x-axis-ctrl"), m.modes[j].x_bindings, KEYMOD_CTRL, module );
}
if (mode_node->hasChild("x-axis-shift")) {
read_bindings(mode_node->getChild("x-axis-shift"), m.modes[j].x_bindings, KEYMOD_SHIFT, module );
}
if (mode_node->hasChild("x-axis-ctrl-shift")) {
read_bindings(mode_node->getChild("x-axis-ctrl-shift"), m.modes[j].x_bindings, KEYMOD_CTRL|KEYMOD_SHIFT, module );
}
if (mode_node->hasChild("y-axis-ctrl")) {
read_bindings(mode_node->getChild("y-axis-ctrl"), m.modes[j].y_bindings, KEYMOD_CTRL, module );
}
if (mode_node->hasChild("y-axis-shift")) {
read_bindings(mode_node->getChild("y-axis-shift"), m.modes[j].y_bindings, KEYMOD_SHIFT, module );
}
if (mode_node->hasChild("y-axis-ctrl-shift")) {
read_bindings(mode_node->getChild("y-axis-ctrl-shift"), m.modes[j].y_bindings, KEYMOD_CTRL|KEYMOD_SHIFT, module );
}
} // of modes iteration
}
fgRegisterMouseClickHandler(mouseClickHandler);
fgRegisterMouseMotionHandler(mouseMotionHandler);
global_mouseInput = this;
}
void FGMouseInput::shutdown()
{
SG_LOG(SG_INPUT, SG_DEBUG, "Shutting down mouse bindings");
// This ensures that mouseClickHandler and mouseMotionHandler are no-ops.
global_mouseInput = nullptr;
// Reset the Pimpl
d.reset();
}
void FGMouseInput::reinit()
{
shutdown();
init();
}
void FGMouseInput::update ( double dt )
{
if (!d) {
SG_LOG(SG_INPUT, SG_WARN, "update of mouse before init");
}
mouse &m = d->mice[0];
int mode = m.mode_node->getIntValue();
if (mode != m.current_mode) {
// current mode has changed
m.current_mode = mode;
m.timeSinceLastMove.stamp();
if (mode >= 0 && mode < m.nModes) {
FGMouseCursor::instance()->setCursor(m.modes[mode].cursor);
d->centerMouseCursor(m);
} else {
SG_LOG(SG_INPUT, SG_WARN, "Mouse mode " << mode << " out of range");
FGMouseCursor::instance()->setCursor(FGMouseCursor::CURSOR_ARROW);
}
}
if ((mode == 0) && d->hoverPickScheduled) {
d->doHoverPick(d->hoverPos);
d->hoverPickScheduled = false;
}
if (!d->tooltipTimeoutDone &&
d->areTooltipsEnabled() &&
(m.timeSinceLastMove.elapsedMSec() > d->tooltipDelayMsec)) {
d->tooltipTimeoutDone = true;
SGPropertyNode_ptr arg(new SGPropertyNode);
globals->get_commands()->execute("tooltip-timeout", arg, nullptr);
}
if ( d->hideCursor ) {
if ( m.timeSinceLastMove.elapsedMSec() > d->cursorTimeoutMsec) {
FGMouseCursor::instance()->hideCursorUntilMouseMove();
m.timeSinceLastMove.stamp();
}
}
d->activePickCallbacks.update( dt, fgGetKeyModifiers() );
}
mouse::mouse ()
: x(-1),
y(-1),
nModes(1),
current_mode(0),
modes()
{
}
mouse_mode::mouse_mode ()
: cursor(FGMouseCursor::CURSOR_ARROW),
constrained(false),
pass_through(false),
buttons()
{
}
void FGMouseInput::doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea)
{
if (!d) {
// can occur during reset
return;
}
int modifiers = fgGetKeyModifiers();
mouse &m = d->mice[0];
mouse_mode &mode = m.modes[m.current_mode];
// Let the property manager know.
if (b >= 0 && b < MAX_MOUSE_BUTTONS)
m.mouse_button_nodes[b]->setBoolValue(updown == MOUSE_BUTTON_DOWN);
if (!d->rightClickModeCycle && (b == 2)) {
// in spring-loaded look mode, ignore right clicks entirely here
return;
}
// Pass on to PUI and the panel if
// requested, and return if one of
// them consumes the event.
osg::Vec2d windowPos;
bool ok = flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
if (!ok) {
SG_LOG(SG_GUI, SG_WARN, "Ignoring mouse click with null context/traits");
return;
}
SGSceneryPicks pickList;
if (isRightDragLookActive() && (updown == MOUSE_BUTTON_DOWN)) {
// when spring-loaded mode is active, don't do scene selection for picks
// https://sourceforge.net/p/flightgear/codetickets/2108/
} else {
pickList = globals->get_renderer()->pick(windowPos);
}
if( updown == MOUSE_BUTTON_UP )
{
// Execute the mouse up event in any case, may be we should
// stop processing here?
SGPickCallbackList& callbacks = d->activePickCallbacks[b];
while( !callbacks.empty() )
{
SGPickCallbackPtr& cb = callbacks.front();
const SGSceneryPick* pick = getPick(pickList, cb);
cb->buttonReleased(ea->getModKeyMask(), *ea, pick ? &pick->info : nullptr);
callbacks.pop_front();
}
if (ea->getHandled()) {
// for https://sourceforge.net/p/flightgear/codetickets/2347/
// we cleared the active picks, but don't do further processing
return;
}
}
if (mode.pass_through) {
// compute a
// scenegraph intersection point corresponding to the mouse click
if (updown == MOUSE_BUTTON_DOWN) {
d->activePickCallbacks.init( b, ea );
if (d->clickTriggersTooltip && d->areTooltipsEnabled()) {
SGPropertyNode_ptr args(new SGPropertyNode);
args->setStringValue("reason", "click");
globals->get_commands()->execute("tooltip-timeout", args, nullptr);
d->tooltipTimeoutDone = true;
}
} else {
// do a hover pick now, to fix up cursor
d->doHoverPick(windowPos);
} // mouse button was released
} // of pass-through mode
if (b >= MAX_MOUSE_BUTTONS) {
SG_LOG(SG_INPUT, SG_ALERT, "Mouse button " << b
<< " where only " << MAX_MOUSE_BUTTONS << " expected");
return;
}
m.modes[m.current_mode].buttons[b].update( modifiers, 0 != updown, x, y);
}
void FGMouseInput::processMotion(int x, int y, const osgGA::GUIEventAdapter* ea)
{
if (!d->activePickCallbacks[0].empty()) {
d->doMouseMoveWithCallbacks(ea);
return;
}
if (SviewMouseMotion(x, y, *ea)) {
return;
}
mouse &m = d->mice[0];
int modeIndex = m.current_mode;
if (isRightDragLookActive()) {
// right mouse is down, force look mode
modeIndex = 3;
}
if (modeIndex == 0) {
osg::Vec2d windowPos;
flightgear::eventToWindowCoords(ea, windowPos.x(), windowPos.y());
d->scheduleHoverPick(windowPos);
// mouse has moved, so we may need to issue tooltip-timeout command again
d->tooltipTimeoutDone = false;
}
mouse_mode &mode = m.modes[modeIndex];
if (d->haveWarped)
{
// don't fire mouse-movement events at the first update after warping the mouse,
// just remember the new mouse position
d->haveWarped = false;
}
else
{
int modifiers = fgGetKeyModifiers();
int xsize = d->xSizeNode ? d->xSizeNode->getIntValue() : 800;
int ysize = d->ySizeNode ? d->ySizeNode->getIntValue() : 600;
// OK, PUI didn't want the event,
// so we can play with it.
if (x != m.x) {
int delta = x - m.x;
d->xAccelNode->setIntValue( delta );
for (unsigned int i = 0; i < mode.x_bindings[modifiers].size(); i++)
mode.x_bindings[modifiers][i]->fire(double(delta), double(xsize));
}
if (y != m.y) {
int delta = y - m.y;
d->yAccelNode->setIntValue( -delta );
for (unsigned int i = 0; i < mode.y_bindings[modifiers].size(); i++)
mode.y_bindings[modifiers][i]->fire(double(delta), double(ysize));
}
}
// Constrain the mouse if requested
if (mode.constrained) {
d->constrainMouse(x, y);
}
}
void FGMouseInput::doMouseMotion (int x, int y, const osgGA::GUIEventAdapter* ea)
{
if (!d) {
// can occur during reset
return;
}
mouse &m = d->mice[0];
if (m.current_mode < 0 || m.current_mode >= m.nModes) {
m.x = x;
m.y = y;
return;
}
m.timeSinceLastMove.stamp();
FGMouseCursor::instance()->mouseMoved();
// TODO Get rid of this as soon as soon as cursor hide timeout works globally
if (!ea->getHandled()) {
processMotion(x, y, ea);
}
m.x = x;
m.y = y;
d->mouseXNode->setIntValue(x);
d->mouseYNode->setIntValue(y);
}
bool FGMouseInput::isRightDragToLookEnabled() const
{
if (!d) {
return false;
}
return (d->rightClickModeCycle == false);
}
bool FGMouseInput::isActiveModePassThrough() const
{
if (!d) {
return false;
}
mouse &m = d->mice[0];
int mode = m.current_mode;
if (isRightDragToLookEnabled() && m.mouse_button_nodes[2]->getBoolValue()) {
mode = 3;
}
return m.modes[mode].pass_through;
}
bool FGMouseInput::isRightDragLookActive() const
{
if (!d) {
return false;
}
const auto& m = d->mice[0];
if (!d->rightClickModeCycle && m.nModes > 3) {
return m.mouse_button_nodes[2]->getBoolValue();
}
return false;
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGMouseInput> registrantFGMouseInput;

View File

@@ -0,0 +1,81 @@
// FGMouseInput.hxx -- handle user input from mouse devices
//
// Written by Torsten Dreyer, started August 2009
// Based on work from David Megginson, started May 2001.
//
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
// Copyright (C) 2001 David Megginson, david@megginson.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.
//
// $Id$
#ifndef _FGMOUSEINPUT_HXX
#define _FGMOUSEINPUT_HXX
#include "FGCommonInput.hxx"
#include <memory>
#include <simgear/structure/subsystem_mgr.hxx>
// forward decls
namespace osgGA { class GUIEventAdapter; }
////////////////////////////////////////////////////////////////////////
// The Mouse Input Class
////////////////////////////////////////////////////////////////////////
class FGMouseInput : public SGSubsystem,
FGCommonInput
{
public:
FGMouseInput();
virtual ~FGMouseInput() = default;
// Subsystem API.
void init() override;
void reinit() override;
void shutdown() override;
void update(double dt) override;
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input-mouse"; }
void doMouseClick (int b, int updown, int x, int y, bool mainWindow, const osgGA::GUIEventAdapter* ea);
void doMouseMotion (int x, int y, const osgGA::GUIEventAdapter*);
/**
* @brief isRightDragToLookEnabled - test if we're in right-mouse-drag
* to adjust the view direction/position mode.
* @return
*/
bool isRightDragToLookEnabled() const;
/**
* @brief check if the active mode passes clicks through to the UI or not
*/
bool isActiveModePassThrough() const;
private:
void processMotion(int x, int y, const osgGA::GUIEventAdapter* ea);
bool isRightDragLookActive() const;
class FGMouseInputPrivate;
std::unique_ptr<FGMouseInputPrivate> d;
};
#endif

376
src/Input/fgjs.cxx Normal file
View File

@@ -0,0 +1,376 @@
// fgjs.cxx -- assign joystick axes to flightgear properties
//
// Updated to allow xml output & added a few bits & pieces
// Laurie Bradshaw, Jun 2005
//
// Written by Tony Peden, started May 2001
//
// Copyright (C) 2001 Tony Peden (apeden@earthlink.net)
// Copyright (C) 2006 Stefan Seifert
//
// 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.
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <simgear/compiler.h>
#ifdef _WIN32
# include <winsock2.h>
#endif
#include <cmath>
#include <iostream>
#include <fstream>
#include <string>
using std::fstream;
using std::cout;
using std::cin;
using std::endl;
using std::ios;
using std::string;
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/props/props_io.hxx>
#include <Main/fg_io.hxx>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
#include "jsinput.h"
#ifdef __APPLE__
# include <CoreFoundation/CoreFoundation.h>
#endif
using simgear::PropertyList;
bool confirmAnswer() {
char answer;
do {
cout << "Is this correct? (y/n) $ ";
cin >> answer;
cin.ignore(256, '\n');
if (answer == 'y')
return true;
if (answer == 'n')
return false;
} while (true);
}
string getFGRoot( int argc, char *argv[] );
int main( int argc, char *argv[] ) {
for (int i = 1; i < argc; i++) {
if (strcmp("--help", argv[i]) == 0) {
cout << "Usage:" << endl;
cout << " --help\t\t\tShow this help" << endl;
exit(0);
} else if (strncmp("--fg-root=", argv[i], 10) == 0) {
// used later
} else {
cout << "Unknown option \"" << argv[i] << "\"" << endl;
exit(0);
}
}
jsInit();
jsSuper *jss = new jsSuper();
jsInput *jsi = new jsInput(jss);
jsi->displayValues(false);
cout << "Found " << jss->getNumJoysticks() << " joystick(s)" << endl;
if(jss->getNumJoysticks() <= 0) {
cout << "Can't find any joysticks ..." << endl;
exit(1);
}
cout << endl << "Now measuring the dead band of your joystick. The dead band is the area " << endl
<< "where the joystick is centered and should not generate any input. Move all " << endl
<< "axes around in this dead zone during the ten seconds this test will take." << endl;
cout << "Press enter to continue." << endl;
cin.ignore(1024, '\n');
jsi->findDeadBand();
cout << endl << "Dead band calibration finished. Press enter to start control assignment." << endl;
cin.ignore(1024, '\n');
jss->firstJoystick();
fstream *xfs = new fstream[ jss->getNumJoysticks() ];
SGPropertyNode_ptr *jstree = new SGPropertyNode_ptr[ jss->getNumJoysticks() ];
do {
cout << "Joystick #" << jss->getCurrentJoystickId()
<< " \"" << jss->getJoystick()->getName() << "\" has "
<< jss->getJoystick()->getNumAxes() << " axes" << endl;
char filename[16];
snprintf(filename, 16, "js%i.xml", jss->getCurrentJoystickId());
xfs[ jss->getCurrentJoystickId() ].open(filename, ios::out);
jstree[ jss->getCurrentJoystickId() ] = new SGPropertyNode();
} while ( jss->nextJoystick() );
SGPath templatefile( getFGRoot(argc, argv) );
templatefile.append("Input");
templatefile.append("Joysticks");
templatefile.append("template.xml");
SGPropertyNode *templatetree = new SGPropertyNode();
try {
readProperties(templatefile, templatetree);
} catch (sg_io_exception & e) {
cout << e.getFormattedMessage ();
}
PropertyList axes = templatetree->getChildren("axis");
for(PropertyList::iterator iter = axes.begin(); iter != axes.end(); ++iter) {
cout << "Move the control you wish to use for " << (*iter)->getStringValue("desc")
<< " " << (*iter)->getStringValue("direction") << endl;
cout << "Pressing a button skips this axis" << endl;
fflush( stdout );
jsi->getInput();
if (jsi->getInputAxis() != -1) {
cout << endl << "Assigned axis " << jsi->getInputAxis()
<< " on joystick " << jsi->getInputJoystick()
<< " to control " << (*iter)->getStringValue("desc") << endl;
if ( confirmAnswer() ) {
SGPropertyNode *axis = jstree[ jsi->getInputJoystick() ]->getChild("axis", jsi->getInputAxis(), true);
copyProperties(*iter, axis);
axis->setDoubleValue("dead-band", jss->getJoystick(jsi->getInputJoystick())
->getDeadBand(jsi->getInputAxis()));
axis->setDoubleValue("binding/factor", jsi->getInputAxisPositive() ? 1.0 : -1.0);
} else {
--iter;
}
} else {
cout << "Skipping control" << endl;
if ( ! confirmAnswer() )
--iter;
}
cout << endl;
}
PropertyList buttons = templatetree->getChildren("button");
for(PropertyList::iterator iter = buttons.begin(); iter != buttons.end(); ++iter) {
cout << "Press the button you wish to use for " << (*iter)->getStringValue("desc") << endl;
cout << "Moving a joystick axis skips this button" << endl;
fflush( stdout );
jsi->getInput();
if (jsi->getInputButton() != -1) {
cout << endl << "Assigned button " << jsi->getInputButton()
<< " on joystick " << jsi->getInputJoystick()
<< " to control " << (*iter)->getStringValue("desc") << endl;
if ( confirmAnswer() ) {
SGPropertyNode *button = jstree[ jsi->getInputJoystick() ]->getChild("button", jsi->getInputButton(), true);
copyProperties(*iter, button);
} else {
--iter;
}
} else {
cout << "Skipping control" << endl;
if (! confirmAnswer())
--iter;
}
cout << endl;
}
cout << "Your joystick settings are in ";
for (int i = 0; i < jss->getNumJoysticks(); i++) {
try {
cout << "js" << i << ".xml";
if (i + 2 < jss->getNumJoysticks())
cout << ", ";
else if (i + 1 < jss->getNumJoysticks())
cout << " and ";
jstree[i]->setStringValue("name", jss->getJoystick(i)->getName());
writeProperties(xfs[i], jstree[i], true);
} catch (sg_io_exception & e) {
cout << e.getFormattedMessage ();
}
xfs[i].close();
}
cout << "." << endl << "Check and edit as desired. Once you are happy," << endl
<< "move relevant js<n>.xml files to $FG_ROOT/Input/Joysticks/ (if you didn't use" << endl
<< "an attached controller, you don't need to move the corresponding file)" << endl;
delete jsi;
delete[] xfs;
delete jss;
delete[] jstree;
return 1;
}
char *homedir = ::getenv( "HOME" );
char *hostname = ::getenv( "HOSTNAME" );
bool free_hostname = false;
// Scan the command line options for the specified option and return
// the value.
static string fgScanForOption( const string& option, int argc, char **argv ) {
int i = 1;
if (hostname == NULL)
{
char _hostname[256];
gethostname(_hostname, 256);
hostname = strdup(_hostname);
free_hostname = true;
}
SG_LOG(SG_INPUT, SG_INFO, "Scanning command line for: " << option );
int len = option.length();
while ( i < argc ) {
SG_LOG( SG_INPUT, SG_DEBUG, "argv[" << i << "] = " << argv[i] );
string arg = argv[i];
if ( arg.find( option ) == 0 ) {
return arg.substr( len );
}
i++;
}
return "";
}
// Scan the user config files for the specified option and return
// the value.
static string fgScanForOption( const string& option, const SGPath& path ) {
sg_gzifstream in( path );
if ( !in.is_open() ) {
return "";
}
SG_LOG( SG_INPUT, SG_INFO, "Scanning " << path << " for: " << option );
int len = option.length();
in >> skipcomment;
while ( ! in.eof() ) {
string line;
getline( in, line, '\n' );
// catch extraneous (DOS) line ending character
if ( line[line.length() - 1] < 32 ) {
line = line.substr( 0, line.length()-1 );
}
if ( line.find( option ) == 0 ) {
return line.substr( len );
}
in >> skipcomment;
}
return "";
}
// Scan the user config files for the specified option and return
// the value.
static string fgScanForOption( const string& option ) {
string arg("");
#if defined( unix ) || defined( __CYGWIN__ )
// Next check home directory for .fgfsrc.hostname file
if ( arg.empty() ) {
if ( homedir != NULL ) {
SGPath config( homedir );
config.append( ".fgfsrc" );
config.concat( "." );
config.concat( hostname );
arg = fgScanForOption( option, config );
}
}
#endif
// Next check home directory for .fgfsrc file
if ( arg.empty() ) {
if ( homedir != NULL ) {
SGPath config( homedir );
config.append( ".fgfsrc" );
arg = fgScanForOption( option, config );
}
}
return arg;
}
// Read in configuration (files and command line options) but only set
// fg_root
string getFGRoot ( int argc, char **argv ) {
string root;
// First parse command line options looking for --fg-root=, this
// will override anything specified in a config file
root = fgScanForOption( "--fg-root=", argc, argv);
// Check in one of the user configuration files.
if (root.empty() )
root = fgScanForOption( "--fg-root=" );
// Next check if fg-root is set as an env variable
if ( root.empty() ) {
char *envp = ::getenv( "FG_ROOT" );
if ( envp != NULL ) {
root = envp;
}
}
// Otherwise, default to a random compiled-in location if we can't
// find fg-root any other way.
if ( root.empty() ) {
#if defined( __CYGWIN__ )
root = "../data";
#elif defined( _WIN32 )
root = "..\\data";
#elif defined(__APPLE__)
/*
The following code looks for the base package inside the application
bundle, in the standard Contents/Resources location.
*/
CFURLRef resourcesUrl = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());
// look for a 'data' subdir
CFURLRef dataDir = CFURLCreateCopyAppendingPathComponent(NULL, resourcesUrl, CFSTR("data"), true);
// now convert down to a path, and the a c-string
CFStringRef path = CFURLCopyFileSystemPath(dataDir, kCFURLPOSIXPathStyle);
root = CFStringGetCStringPtr(path, CFStringGetSystemEncoding());
// tidy up.
CFRelease(resourcesUrl);
CFRelease(dataDir);
CFRelease(path);
#else
root = PKGLIBDIR;
#endif
}
SG_LOG(SG_INPUT, SG_INFO, "fg_root = " << root );
return root;
}

105
src/Input/input.cxx Normal file
View File

@@ -0,0 +1,105 @@
// input.cxx -- handle user input from various sources.
//
// Written by David Megginson, started May 2001.
// Major redesign by Torsten Dreyer, started August 2009
//
// Copyright (C) 2001 David Megginson, david@megginson.com
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
//
// 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.
//
// $Id$
#include <config.h>
#include "input.hxx"
#include <simgear/compiler.h>
#include <Main/fg_props.hxx>
#include "FGMouseInput.hxx"
#include "FGKeyboardInput.hxx"
#if defined(ENABLE_PLIB_JOYSTICK)
#include "FGJoystickInput.hxx"
#endif
#ifdef WITH_EVENTINPUT
#if defined ( SG_MAC )
// we use HID
#elif defined(SG_WINDOWS)
// we use HID
#else
#include "FGLinuxEventInput.hxx"
#define INPUTEVENT_CLASS FGLinuxEventInput
#endif
#if defined(ENABLE_HID_INPUT)
#include "FGHIDEventInput.hxx"
#endif
#endif // of WITH_EVENTINPUT
////////////////////////////////////////////////////////////////////////
// Implementation of FGInput.
////////////////////////////////////////////////////////////////////////
FGInput::FGInput ()
{
if( fgGetBool("/sim/input/no-mouse-input",false) ) {
SG_LOG(SG_INPUT,SG_MANDATORY_INFO,"Mouse input disabled!");
} else {
set_subsystem( FGMouseInput::staticSubsystemClassId(), new FGMouseInput() );
}
if( fgGetBool("/sim/input/no-keyboard-input",false) ) {
SG_LOG(SG_INPUT,SG_MANDATORY_INFO,"Keyboard input disabled!");
} else {
set_subsystem( "input-keyboard", new FGKeyboardInput() );
}
#if defined(ENABLE_PLIB_JOYSTICK)
if( fgGetBool("/sim/input/no-joystick-input",false) ) {
SG_LOG(SG_INPUT,SG_MANDATORY_INFO,"Joystick input disabled!");
} else {
set_subsystem( "input-joystick", new FGJoystickInput() );
}
#endif
#ifdef INPUTEVENT_CLASS
if( fgGetBool("/sim/input/no-event-input",false) ) {
SG_LOG(SG_INPUT,SG_MANDATORY_INFO,"Event input disabled!");
} else {
set_subsystem( "input-event", new INPUTEVENT_CLASS() );
}
#endif
#if defined(ENABLE_HID_INPUT) && defined(WITH_EVENTINPUT)
if (fgGetBool("/sim/input/no-hid-input", false)) {
SG_LOG(SG_INPUT, SG_MANDATORY_INFO, "HID-based event input disabled");
} else {
set_subsystem( "input-event-hid", new FGHIDEventInput() );
}
#endif
}
FGInput::~FGInput ()
{
// SGSubsystemGroup deletes all subsystem in it's destructor
}
// Register the subsystem.
SGSubsystemMgr::Registrant<FGInput> registrantFGInput;

62
src/Input/input.hxx Normal file
View File

@@ -0,0 +1,62 @@
// input.hxx -- handle user input from various sources.
//
// Written by David Megginson, started May 2001.
// Major redesign by Torsten Dreyer, started August 2009
//
// Copyright (C) 2001 David Megginson, david@megginson.com
// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
//
// 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.
//
// $Id$
#ifndef _INPUT_HXX
#define _INPUT_HXX
#include <simgear/structure/subsystem_mgr.hxx>
////////////////////////////////////////////////////////////////////////
// General input mapping support.
////////////////////////////////////////////////////////////////////////
/**
* Generic input module.
*
* <p>This module is designed to handle input from multiple sources --
* keyboard, joystick, mouse, or even panel switches -- in a consistent
* way, and to allow users to rebind any of the actions at runtime.</p>
*/
class FGInput : public SGSubsystemGroup
{
public:
/**
* Default constructor.
*/
FGInput ();
/**
* Destructor.
*/
virtual ~FGInput();
// Subsystem identification.
static const char* staticSubsystemClassId() { return "input"; }
};
#endif // _INPUT_HXX

112
src/Input/js_demo.cxx Normal file
View File

@@ -0,0 +1,112 @@
#include <config.h>
#ifdef HAVE_WINDOWS_H
# include <windows.h>
#else
# include <unistd.h> // for usleep
#endif
#include "FlightGear_js.h"
#define Z 8
int main ( int, char ** )
{
jsJoystick *js[Z] ;
float *ax[Z] ;
int i, j, t, useful[Z];
jsInit();
for ( i = 0; i < Z; i++ )
js[i] = new jsJoystick ( i ) ;
printf ( "Joystick test program.\n" ) ;
printf ( "~~~~~~~~~~~~~~~~~~~~~~\n" ) ;
t = 0;
for ( i = 0; i < Z; i++ )
{ useful[i] = ! ( js[i]->notWorking () );
if ( useful[i] ) {
t++;
printf ( "Joystick %i: \"%s\"\n", i, js[i]->getName() ) ;
} else printf ( "Joystick %i not detected\n", i ) ;
}
if ( t == 0 ) exit ( 1 ) ;
for ( i = 0; i < Z; i++ )
if ( useful[i] )
ax[i] = new float [ js[i]->getNumAxes () ] ;
for ( i = 0 ; i < Z ; i++ )
if ( useful[i] )
printf ( "+--------------------JS.%d----------------------", i ) ;
printf ( "+\n" ) ;
for ( i = 0 ; i < Z ; i++ )
if ( useful[i] )
{
if ( js[i]->notWorking () )
printf ( "| ~~~ Not Detected ~~~ " ) ;
else
{
printf ( "| Btns " ) ;
for ( j = 0 ; j < js[i]->getNumAxes () ; j++ )
printf ( "Ax:%1d ", j ) ;
for ( ; j < 8 ; j++ )
printf ( " " ) ;
}
}
printf ( "|\n" ) ;
for ( i = 0 ; i < Z ; i++ )
if ( useful[i] )
printf ( "+----------------------------------------------" ) ;
printf ( "+\n" ) ;
while (1)
{
for ( i = 0 ; i < Z ; i++ )
if ( useful[i] )
{
if ( js[i]->notWorking () )
printf ( "| . . . . . . . . . . . " ) ;
else
{
int b ;
js[i]->read ( &b, ax[i] ) ;
printf ( "| %04x ", b ) ;
for ( j = 0 ; j < js[i]->getNumAxes () ; j++ )
printf ( "%+.1f ", ax[i][j] ) ;
for ( ; j < 8 ; j++ )
printf ( " . " ) ;
}
}
printf ( "|\r" ) ;
fflush ( stdout ) ;
/* give other processes a chance */
#ifdef _WIN32
Sleep ( 1 ) ;
#elif defined(sgi)
sginap ( 1 ) ;
#else
usleep ( 1000 ) ;
#endif
}
return 0 ;
}

179
src/Input/jsinput.cxx Normal file
View File

@@ -0,0 +1,179 @@
// jsinput.cxx -- wait for and identify input from joystick
//
// Written by Tony Peden, started May 2001
//
// Copyright (C) 2001 Tony Peden (apeden@earthlink.net)
//
// 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 <simgear/compiler.h>
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include "jsinput.h"
#include <simgear/timing/timestamp.hxx>
jsInput::jsInput(jsSuper *j) {
jss=j;
pretty_display=true;
joystick=axis=button=-1;
axis_threshold=0.2;
}
jsInput::~jsInput(void) {}
int jsInput::getInput() {
bool gotit=false;
float delta;
int i, current_button = 0, button_bits = 0;
joystick=axis=button=-1;
axis_positive=false;
if(pretty_display) {
printf ( "+----------------------------------------------\n" ) ;
printf ( "| Btns " ) ;
for ( i = 0 ; i < jss->getJoystick()->getNumAxes() ; i++ )
printf ( "Ax:%3d ", i ) ;
for ( ; i < 8 ; i++ )
printf ( " " ) ;
printf ( "|\n" ) ;
printf ( "+----------------------------------------------\n" ) ;
}
jss->firstJoystick();
do {
jss->getJoystick()->read ( &button_iv[jss->getCurrentJoystickId()],
axes_iv[jss->getCurrentJoystickId()] ) ;
} while( jss->nextJoystick() );
while(!gotit) {
jss->firstJoystick();
do {
jss->getJoystick()->read ( &current_button, axes ) ;
if(pretty_display) printf ( "| %04x ", current_button ) ;
for ( i = 0 ; i < jss->getJoystick()->getNumAxes(); i++ ) {
delta = axes[i] - axes_iv[jss->getCurrentJoystickId()][i];
if(pretty_display) printf ( "%+.3f ", delta ) ;
if(!gotit) {
if( fabs(delta) > axis_threshold ) {
gotit=true;
joystick=jss->getCurrentJoystickId();
axis=i;
axis_positive=(delta>0);
} else if( current_button != 0 ) {
gotit=true;
joystick=jss->getCurrentJoystickId();
button_bits=current_button;
}
}
}
if(pretty_display) {
for ( ; i < 8 ; i++ )
printf ( " . " ) ;
}
} while( jss->nextJoystick() && !gotit);
if(pretty_display) {
printf ( "|\r" ) ;
fflush ( stdout ) ;
}
SGTimeStamp::sleepForMSec(1);
}
if(button_bits != 0) {
for(int i=0;i<=31;i++) {
if( ( button_bits & (1u << i) ) > 0 ) {
button=i;
break;
}
}
}
return 0;
}
void jsInput::findDeadBand() {
float delta;
int i;
float dead_band[MAX_JOYSTICKS][_JS_MAX_AXES];
jss->firstJoystick();
do {
jss->getJoystick()->read ( NULL,
axes_iv[jss->getCurrentJoystickId()] ) ;
for ( i = 0; i < jss->getJoystick()->getNumAxes(); i++ ) {
dead_band[jss->getCurrentJoystickId()][i] = 0;
}
} while( jss->nextJoystick() );
SGTimeStamp clock = SGTimeStamp::now();
cout << 10;
cout.flush();
for (int j = 9; j >= 0; j--) {
clock.stamp();
do {
jss->firstJoystick();
do {
jss->getJoystick()->read ( NULL, axes ) ;
for ( i = 0 ; i < jss->getJoystick()->getNumAxes(); i++ ) {
delta = axes[i] - axes_iv[jss->getCurrentJoystickId()][i];
if (fabs(delta) > dead_band[jss->getCurrentJoystickId()][i])
dead_band[jss->getCurrentJoystickId()][i] = delta;
}
} while( jss->nextJoystick());
SGTimeStamp::sleepForMSec(1);
} while (clock.elapsedMSec() < 1000);
cout << " - " << j;
cout.flush();
}
cout << endl << endl;
jss->firstJoystick();
do {
for ( i = 0; i < jss->getJoystick()->getNumAxes(); i++ ) {
jss->getJoystick()->setDeadBand(i, dead_band[jss->getCurrentJoystickId()][i]);
printf("Joystick %i, axis %i: %f\n", jss->getCurrentJoystickId(), i, dead_band[jss->getCurrentJoystickId()][i]);
}
} while( jss->nextJoystick() );
}

63
src/Input/jsinput.h Normal file
View File

@@ -0,0 +1,63 @@
// jsinput.h -- wait for and identify input from joystick
//
// Written by Tony Peden, started May 2001
//
// Copyright (C) 2001 Tony Peden (apeden@earthlink.net)
//
// 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 _JSINPUT_H
#define _JSINPUT_H
#include <cmath> // for fabs
#include "jssuper.h"
class jsInput {
private:
jsSuper *jss;
bool pretty_display;
float axes[_JS_MAX_AXES];
float axes_iv[MAX_JOYSTICKS][_JS_MAX_AXES];
int button_iv[MAX_JOYSTICKS];
int joystick,axis,button;
bool axis_positive;
float axis_threshold;
public:
jsInput(jsSuper *jss);
~jsInput(void);
inline void displayValues(bool bb) { pretty_display=bb; }
int getInput(void);
void findDeadBand(void);
inline int getInputJoystick(void) { return joystick; }
inline int getInputAxis(void) { return axis; }
inline int getInputButton(void) { return button; }
inline bool getInputAxisPositive(void) { return axis_positive; }
inline float getReturnThreshold(void) { return axis_threshold; }
inline void setReturnThreshold(float ff)
{ if(fabs(ff) <= 1.0) axis_threshold=ff; }
};
#endif

80
src/Input/jssuper.cxx Normal file
View File

@@ -0,0 +1,80 @@
// jssuper.cxx -- manage access to multiple joysticks
//
// Written by Tony Peden, started May 2001
//
// Copyright (C) 2001 Tony Peden (apeden@earthlink.net)
//
// 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 "jssuper.h"
jsSuper::jsSuper(void) {
int i;
activeJoysticks=0;
currentJoystick=0;
first=-1;
last=0;
for ( i = 0; i < MAX_JOYSTICKS; i++ )
js[i] = new jsJoystick( i );
for ( i = 0; i < MAX_JOYSTICKS; i++ )
{
active[i] = ! ( js[i]->notWorking () );
if ( active[i] ) {
activeJoysticks++;
if(first < 0) {
first=i;
}
last=i;
}
}
}
int jsSuper::nextJoystick(void) {
// int i;
if(!activeJoysticks) return 0;
if(currentJoystick == last ) return 0;
currentJoystick++;
while(!active[currentJoystick]){ currentJoystick++; };
return 1;
}
int jsSuper::prevJoystick(void) {
// int i;
if(!activeJoysticks) return 0;
if(currentJoystick == first ) return 0;
currentJoystick--;
while(!active[currentJoystick]){ currentJoystick--; };
return 1;
}
jsSuper::~jsSuper(void) {
int i;
for ( i = 0; i < MAX_JOYSTICKS; i++ )
delete js[i];
}

61
src/Input/jssuper.h Normal file
View File

@@ -0,0 +1,61 @@
// jssuper.h -- manage access to multiple joysticks
//
// Written by Tony Peden, started May 2001
//
// Copyright (C) 2001 Tony Peden (apeden@earthlink.net)
//
// 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 _JSSUPER_H
#define _JSSUPER_H
#include <config.h>
#include "FlightGear_js.h"
#define MAX_JOYSTICKS 8
class jsSuper {
private:
int activeJoysticks;
int active[MAX_JOYSTICKS];
int currentJoystick;
int first, last;
jsJoystick* js[MAX_JOYSTICKS];
public:
jsSuper(void);
~jsSuper(void);
inline int getNumJoysticks(void) { return activeJoysticks; }
inline int atFirst(void) { return currentJoystick == first; }
inline int atLast(void) { return currentJoystick == last; }
inline void firstJoystick(void) { currentJoystick=first; }
inline void lastJoystick(void) { currentJoystick=last; }
int nextJoystick(void);
int prevJoystick(void);
inline jsJoystick* getJoystick(int Joystick)
{ currentJoystick=Joystick; return js[Joystick]; }
inline jsJoystick* getJoystick(void) { return js[currentJoystick]; }
inline int getCurrentJoystickId(void) { return currentJoystick; }
};
#endif