FlightGear next
FGJoystickInput.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: FGJoystickInput.cxx
3 * SPDX-FileComment: handle user input from joystick devices, based on work from David Megginson, started May 2001.
4 * SPDX-FileCopyrightText: Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
5 * SPDX-FileContributor: Copyright (C) 2001 David Megginson, david@megginson.com
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "config.h"
10
11#include "FGJoystickInput.hxx"
12
13#if defined(SG_WINDOWS)
14# include <windows.h>
15#endif
16
17#include <cmath>
18
19#include <simgear/debug/ErrorReportingCallback.hxx>
20#include <simgear/props/props_io.hxx>
21
23#include <Main/fg_props.hxx>
25
26using simgear::PropertyList;
27
28FGJoystickInput::axis::axis ()
29 : last_value(9999999),
30 tolerance(0.002),
31 low_threshold(-0.9),
32 high_threshold(0.9),
33 interval_sec(0),
34 delay_sec(0),
35 release_delay_sec(0),
36 last_dt(0)
37{
38}
39
40FGJoystickInput::axis::~axis ()
41{
42}
43
44FGJoystickInput::joystick::joystick ()
45 : jsnum(0),
46 naxes(0),
47 nbuttons(0),
48 axes(0),
49 buttons(0),
50 predefined(true)
51{
52}
53
54void FGJoystickInput::joystick::clearAxesAndButtons()
55{
56 delete[] axes;
57 delete[] buttons;
58 axes = NULL;
59 buttons = NULL;
60 naxes = 0;
61 nbuttons = 0;
62}
63
64FGJoystickInput::joystick::~joystick ()
65{
66 jsnum = 0;
67 clearAxesAndButtons();
68}
69
70
74
76{
77 _remove(true);
78}
79
80void FGJoystickInput::_remove(bool all)
81{
82 SGPropertyNode * js_nodes = fgGetNode("/input/joysticks", true);
83
84 for (int i = 0; i < MAX_JOYSTICKS; i++)
85 {
86 joystick* joy = &joysticks[i];
87 // do not remove predefined joysticks info on reinit
88 if (all || (!joy->predefined))
89 js_nodes->removeChild("js", i);
90
91 joy->plibJS.reset();
92 joy->clearAxesAndButtons();
93 }
94}
95
97{
98 jsInit();
99 SG_LOG(SG_INPUT, SG_DEBUG, "Initializing joystick bindings");
100 SGPropertyNode_ptr js_nodes = fgGetNode("/input/joysticks", true);
101 status_node = fgGetNode("/devices/status/joysticks", true);
102
103 FGDeviceConfigurationMap configMap("Input/Joysticks",js_nodes, "js-named");
104
105 for (int i = 0; i < MAX_JOYSTICKS; i++) {
106 jsJoystick * js = new jsJoystick(i);
107 joysticks[i].plibJS.reset(js);
108 joysticks[i].initializing = true;
109
110 if (js->notWorking()) {
111 SG_LOG(SG_INPUT, SG_DEBUG, "Joystick " << i << " not found");
112 continue;
113 }
114
115 const char * name = js->getName();
116 SGPropertyNode_ptr js_node = js_nodes->getChild("js", i);
117
118 if (js_node) {
119 SG_LOG(SG_INPUT, SG_INFO, "Using existing bindings for joystick " << i);
120
121 } else {
122 joysticks[i].predefined = false;
123 SG_LOG(SG_INPUT, SG_INFO, "Looking for bindings for joystick \"" << name << '"');
124 SGPropertyNode_ptr named;
125
126 // allow distinguishing duplicated devices by the name
127 std::string indexedName = computeDeviceIndexName(name, i);
128 if (configMap.hasConfiguration(indexedName)) {
129 named = configMap.configurationForDeviceName(indexedName);
130 std::string source = named->getStringValue("source", "user defined");
131 SG_LOG(SG_INPUT, SG_INFO, "... found joystick: " << source);
132 } else if (configMap.hasConfiguration(name)) {
133 named = configMap.configurationForDeviceName(name);
134 std::string source = named->getStringValue("source", "user defined");
135 SG_LOG(SG_INPUT, SG_INFO, "... found joystick: " << source);
136 } else if ((named = configMap.configurationForDeviceName("default"))) {
137 std::string source = named->getStringValue("source", "user defined");
138 SG_LOG(SG_INPUT, SG_INFO, "No config found for joystick \"" << name
139 << "\"\nUsing default: \"" << source << '"');
140
141 } else {
142 SG_LOG(SG_INPUT, SG_WARN, "No joystick configuration file with <name>" << name << "</name> entry found!");
143 }
144
145 js_node = js_nodes->getChild("js", i, true);
146 copyProperties(named, js_node);
147 js_node->setStringValue("id", name);
148 }
149 }
150}
151
152std::string FGJoystickInput::computeDeviceIndexName(const std::string& name,
153 int lastIndex) const
154{
155 int index = 0;
156 for (int i = 0; i < lastIndex; i++) {
157 if (joysticks[i].plibJS && (joysticks[i].plibJS->getName() == name)) {
158 ++index;
159 }
160 }
161
162 std::ostringstream os;
163 os << name << "_" << index;
164 return os.str();
165}
166
168{
169 SG_LOG(SG_INPUT, SG_DEBUG, "Re-Initializing joystick bindings");
170 _remove(false);
171
172#if defined(SG_MAC)
173 jsShutdown();
174#endif
175
178}
179
181{
182 auto nasalsys = globals->get_subsystem<FGNasalSys>();
183 SGPropertyNode_ptr js_nodes = fgGetNode("/input/joysticks");
184
185 for (int i = 0; i < MAX_JOYSTICKS; i++) {
186 SGPropertyNode_ptr js_node = js_nodes->getChild("js", i);
187 jsJoystick *js = joysticks[i].plibJS.get();
188 if (!js_node || js->notWorking())
189 continue;
190
191 // FIXME : need to get input device path to disambiguate
192 simgear::ErrorReportContext errCtx("input-device", "");
193
194#ifdef WIN32
195 JOYCAPS jsCaps ;
196 joyGetDevCaps( i, &jsCaps, sizeof(jsCaps) );
197 unsigned int nbuttons = jsCaps.wNumButtons;
198 if (nbuttons > MAX_JOYSTICK_BUTTONS) nbuttons = MAX_JOYSTICK_BUTTONS;
199#else
200 unsigned int nbuttons = MAX_JOYSTICK_BUTTONS;
201#endif
202
203 int naxes = js->getNumAxes();
204 if (naxes > MAX_JOYSTICK_AXES) naxes = MAX_JOYSTICK_AXES;
205 joysticks[i].naxes = naxes;
206 joysticks[i].nbuttons = nbuttons;
207
208 SG_LOG(SG_INPUT, SG_DEBUG, "Initializing joystick " << i);
209
210 // Set up range arrays
211 float minRange[MAX_JOYSTICK_AXES];
212 float maxRange[MAX_JOYSTICK_AXES];
213 float center[MAX_JOYSTICK_AXES];
214
215 // Initialize with default values
216 js->getMinRange(minRange);
217 js->getMaxRange(maxRange);
218 js->getCenter(center);
219
220 // Allocate axes and buttons
221 joysticks[i].axes = new axis[naxes];
222 joysticks[i].buttons = new FGButton[nbuttons];
223
224 //
225 // Initialize nasal groups.
226 //
227 std::ostringstream str;
228 str << "__js" << i;
229 std::string module = str.str();
230 nasalsys->createModule(module.c_str(), module.c_str(), "", 0);
231
232 PropertyList nasal = js_node->getChildren("nasal");
233 unsigned int j;
234 for (j = 0; j < nasal.size(); j++) {
235 nasal[j]->setStringValue("module", module.c_str());
236 bool ok = nasalsys->handleCommand(nasal[j], nullptr);
237 if (!ok) {
238 // TODO: get the Nasal errors logged properly
239 simgear::reportFailure(simgear::LoadFailure::BadData, simgear::ErrorCode::InputDeviceConfig,
240 "Failed to parse input device Nasal");
241 }
242 }
243
244 //
245 // Initialize the axes.
246 //
247 PropertyList axes = js_node->getChildren("axis");
248 size_t nb_axes = axes.size();
249 for (j = 0; j < nb_axes; j++ ) {
250 const SGPropertyNode * axis_node = axes[j];
251 const SGPropertyNode * num_node = axis_node->getChild("number");
252 int n_axis = axis_node->getIndex();
253 if (num_node != 0) {
254 n_axis = num_node->getIntValue(TGT_PLATFORM, -1);
255
256 #ifdef SG_MAC
257 // Mac falls back to Unix by default - avoids specifying many
258 // duplicate <mac> entries in joystick config files
259 if (n_axis < 0) {
260 n_axis = num_node->getIntValue("unix", -1);
261 }
262 #endif
263
264 // Silently ignore platforms that are not specified within the
265 // <number></number> section
266 if (n_axis < 0) {
267 continue;
268 }
269 }
270
271 if (n_axis >= naxes) {
272 SG_LOG(SG_INPUT, SG_DEBUG, "Dropping bindings for axis " << n_axis);
273 continue;
274 }
275 axis &a = joysticks[i].axes[n_axis];
276
277 js->setDeadBand(n_axis, axis_node->getDoubleValue("dead-band", 0.0));
278
279 a.tolerance = axis_node->getDoubleValue("tolerance", 0.002);
280 minRange[n_axis] = axis_node->getDoubleValue("min-range", minRange[n_axis]);
281 maxRange[n_axis] = axis_node->getDoubleValue("max-range", maxRange[n_axis]);
282 center[n_axis] = axis_node->getDoubleValue("center", center[n_axis]);
283
284 read_bindings(axis_node, a.bindings, KEYMOD_NONE, module );
285
286 // Initialize the virtual axis buttons.
287 a.low.init(axis_node->getChild("low"), "low", module );
288 a.low_threshold = axis_node->getDoubleValue("low-threshold", -0.9);
289
290 a.high.init(axis_node->getChild("high"), "high", module );
291 a.high_threshold = axis_node->getDoubleValue("high-threshold", 0.9);
292 a.interval_sec = axis_node->getDoubleValue("interval-sec",0.0);
293 a.delay_sec = axis_node->getDoubleValue("delay-sec",0.0);
294 a.release_delay_sec = axis_node->getDoubleValue("release-delay-sec",0.0);
295 a.last_dt = 0.0;
296 }
297
298 //
299 // Initialize the buttons.
300 //
301 PropertyList buttons = js_node->getChildren("button");
302 for (auto button_node : buttons ) {
303 size_t n_but = button_node->getIndex();
304
305 const SGPropertyNode * num_node = button_node->getChild("number");
306 if (NULL != num_node)
307 n_but = num_node->getIntValue(TGT_PLATFORM,n_but);
308
309 if (n_but >= nbuttons) {
310 SG_LOG(SG_INPUT, SG_DEBUG, "Dropping bindings for button " << n_but);
311 continue;
312 }
313
314 std::ostringstream buf;
315 buf << (unsigned)n_but;
316
317 SG_LOG(SG_INPUT, SG_DEBUG, "Initializing button " << buf.str());
318 FGButton &b = joysticks[i].buttons[n_but];
319 b.init(button_node, buf.str(), module );
320 // get interval-sec property
321 b.interval_sec = button_node->getDoubleValue("interval-sec",0.0);
322 b.delay_sec = button_node->getDoubleValue("delay-sec",0.0);
323 b.release_delay_sec = button_node->getDoubleValue("release-delay-sec",0.0);
324 b.last_dt = 0.0;
325 }
326
327 js->setMinRange(minRange);
328 js->setMaxRange(maxRange);
329 js->setCenter(center);
330 }
331}
332
333void FGJoystickInput::updateJoystick(int index, FGJoystickInput::joystick* joy, double dt)
334{
335 float axis_values[MAX_JOYSTICK_AXES];
336 int modifiers = fgGetKeyModifiers();
337 int buttons;
338 bool pressed, last_state;
339 bool axes_initialized;
340 float delay;
341
342 jsJoystick * js = joy->plibJS.get();
343 if (js == 0 || js->notWorking()) {
344 joysticks[index].initializing = true;
345 if (js) {
346 joysticks[index].init_dt += dt;
347 if (joysticks[index].init_dt >= 1.0) {
348 joysticks[index].plibJS.reset( new jsJoystick(index) );
349 joysticks[index].init_dt = 0.0;
350 }
351 }
352 return;
353 }
354
355 js->read(&buttons, axis_values);
356 if (js->notWorking()) { // If js is disconnected
357 joysticks[index].initializing = true;
358 return;
359 }
360
361 // Joystick axes can get initialized to extreme values, at least on Linux.
362 // Wait until one of the axes get a different value before continuing.
363 // https://sourceforge.net/p/flightgear/codetickets/2185/
364 axes_initialized = true;
365 if (joysticks[index].initializing) {
366
367 if (!joysticks[index].initialized) {
368 js->read(NULL, joysticks[index].values);
369 joysticks[index].initialized = true;
370 }
371
372 int j;
373 for (j = 0; j < joy->naxes; j++) {
374 if (axis_values[j] != joysticks[index].values[j]) break;
375 }
376 if (j < joy->naxes) {
377 joysticks[index].initializing = false;
378 } else {
379 axes_initialized = false;
380 }
381 }
382
383 // Update device status
384 SGPropertyNode_ptr status = status_node->getChild("joystick", index, true);
385 if (axes_initialized) {
386 for (int j = 0; j < MAX_JOYSTICK_AXES; j++) {
387 status->getChild("axis", j, true)->setFloatValue(axis_values[j]);
388 }
389 }
390
391 for (int j = 0; j < MAX_JOYSTICK_BUTTONS; j++) {
392 status->getChild("button", j, true)->setBoolValue((buttons & (1u << j)) > 0 );
393 }
394
395 // Fire bindings for the axes.
396 if (axes_initialized) {
397 for (int j = 0; j < joy->naxes; j++) {
398 axis &a = joy->axes[j];
399
400 // Do nothing if the axis position
401 // is unchanged; only a change in
402 // position fires the bindings.
403 // But only if there are bindings
404 if (fabs(axis_values[j] - a.last_value) > a.tolerance
405 && a.bindings[KEYMOD_NONE].size() > 0 ) {
406 a.last_value = axis_values[j];
407 for (unsigned int k = 0; k < a.bindings[KEYMOD_NONE].size(); k++)
408 a.bindings[KEYMOD_NONE][k]->fire(axis_values[j]);
409 }
410
411 // do we have to emulate axis buttons?
412 last_state = joy->axes[j].low.last_state || joy->axes[j].high.last_state;
413 pressed = axis_values[j] < a.low_threshold || axis_values[j] > a.high_threshold;
414 delay = (pressed ? last_state ? a.interval_sec : a.delay_sec : a.release_delay_sec );
415 if(pressed || last_state) a.last_dt += dt;
416 else a.last_dt = 0;
417 if(a.last_dt >= delay) {
418 if (a.low.bindings[modifiers].size())
419 joy->axes[j].low.update( modifiers, axis_values[j] < a.low_threshold );
420
421 if (a.high.bindings[modifiers].size())
422 joy->axes[j].high.update( modifiers, axis_values[j] > a.high_threshold );
423
424 a.last_dt -= delay;
425 }
426 } // of axes iteration
427 } // axes_initialized
428
429 // Fire bindings for the buttons.
430 for (int j = 0; j < joy->nbuttons; j++) {
431 FGButton &b = joy->buttons[j];
432 pressed = (buttons & (1u << j)) > 0;
433 last_state = joy->buttons[j].last_state;
434 delay = (pressed ? last_state ? b.interval_sec : b.delay_sec : b.release_delay_sec );
435 if(pressed || last_state) b.last_dt += dt;
436 else b.last_dt = 0;
437 if(b.last_dt >= delay) {
438 joy->buttons[j].update( modifiers, pressed );
439 b.last_dt -= delay;
440 }
441 } // of butotns iterations
442
443}
444
445void FGJoystickInput::update( double dt )
446{
447 for (int i = 0; i < MAX_JOYSTICKS; i++) {
448 updateJoystick(i, &joysticks[i], dt);
449 }
450}
451
452
453// Register the subsystem.
454SGSubsystemMgr::Registrant<FGJoystickInput> registrantFGJoystickInput(
455 SGSubsystemMgr::GENERAL,
456 {{"nasal", SGSubsystemMgr::Dependency::HARD}});
#define TGT_PLATFORM
SGSubsystemMgr::Registrant< FGJoystickInput > registrantFGJoystickInput(SGSubsystemMgr::GENERAL, {{"nasal", SGSubsystemMgr::Dependency::HARD}})
#define i(x)
void init(const SGPropertyNode *node, const std::string &name, const std::string &module)
Definition FGButton.cxx:49
void update(int modifiers, bool pressed, int x=-1, int y=-1)
Definition FGButton.cxx:61
int last_state
Definition FGButton.hxx:41
float interval_sec
Definition FGButton.hxx:39
float last_dt
Definition FGButton.hxx:40
float delay_sec
Definition FGButton.hxx:39
float release_delay_sec
Definition FGButton.hxx:39
static void read_bindings(const SGPropertyNode *base, binding_list_t *binding_list, int modifiers, const std::string &module)
SGPropertyNode_ptr configurationForDeviceName(const std::string &name)
bool hasConfiguration(const std::string &name) const
static const int MAX_JOYSTICKS
void update(double dt) override
static const int MAX_JOYSTICK_AXES
void reinit() override
void postinit() override
static const int MAX_JOYSTICK_BUTTONS
virtual ~FGJoystickInput()
void init() override
const char * name
@ KEYMOD_NONE
Definition fg_os.hxx:24
int fgGetKeyModifiers()
static int status
FGGlobals * globals
Definition globals.cxx:142
#define MAX_JOYSTICKS
Definition jssuper.h:28
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27