first commit
This commit is contained in:
84
src/Input/CMakeLists.txt
Normal file
84
src/Input/CMakeLists.txt
Normal 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
83
src/Input/FGButton.cxx
Normal 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
45
src/Input/FGButton.hxx
Normal 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
|
||||
76
src/Input/FGCommonInput.cxx
Normal file
76
src/Input/FGCommonInput.cxx
Normal 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);
|
||||
}
|
||||
|
||||
52
src/Input/FGCommonInput.hxx
Normal file
52
src/Input/FGCommonInput.hxx
Normal 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
|
||||
194
src/Input/FGDeviceConfigurationMap.cxx
Normal file
194
src/Input/FGDeviceConfigurationMap.cxx
Normal 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);
|
||||
}
|
||||
}
|
||||
69
src/Input/FGDeviceConfigurationMap.hxx
Normal file
69
src/Input/FGDeviceConfigurationMap.hxx
Normal 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
593
src/Input/FGEventInput.cxx
Normal 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
350
src/Input/FGEventInput.hxx
Normal 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
|
||||
971
src/Input/FGHIDEventInput.cxx
Normal file
971
src/Input/FGHIDEventInput.cxx
Normal 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));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
58
src/Input/FGHIDEventInput.hxx
Normal file
58
src/Input/FGHIDEventInput.hxx
Normal 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
|
||||
472
src/Input/FGJoystickInput.cxx
Normal file
472
src/Input/FGJoystickInput.cxx
Normal 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}});
|
||||
116
src/Input/FGJoystickInput.hxx
Normal file
116
src/Input/FGJoystickInput.hxx
Normal 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
|
||||
258
src/Input/FGKeyboardInput.cxx
Normal file
258
src/Input/FGKeyboardInput.cxx
Normal 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}});
|
||||
79
src/Input/FGKeyboardInput.hxx
Normal file
79
src/Input/FGKeyboardInput.hxx
Normal 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
|
||||
553
src/Input/FGLinuxEventInput.cxx
Normal file
553
src/Input/FGLinuxEventInput.cxx
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
84
src/Input/FGLinuxEventInput.hxx
Normal file
84
src/Input/FGLinuxEventInput.hxx
Normal 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
770
src/Input/FGMouseInput.cxx
Normal 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;
|
||||
81
src/Input/FGMouseInput.hxx
Normal file
81
src/Input/FGMouseInput.hxx
Normal 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
376
src/Input/fgjs.cxx
Normal 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
105
src/Input/input.cxx
Normal 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
62
src/Input/input.hxx
Normal 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
112
src/Input/js_demo.cxx
Normal 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
179
src/Input/jsinput.cxx
Normal 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 ( ¤t_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
63
src/Input/jsinput.h
Normal 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
80
src/Input/jssuper.cxx
Normal 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
61
src/Input/jssuper.h
Normal 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
|
||||
Reference in New Issue
Block a user