FlightGear next
FGLinuxEventInput.cxx
Go to the documentation of this file.
1// FGEventInput.cxx -- handle event driven input devices for the Linux O/S
2//
3// Written by Torsten Dreyer, started July 2009.
4//
5// Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
21#ifdef HAVE_CONFIG_H
22# include <config.h>
23#endif
24
25#include <cstring>
26#include <cstdio>
27#include <sys/types.h>
28#include <sys/stat.h>
29#include <unistd.h>
30#include "FGLinuxEventInput.hxx"
31
32extern "C" {
33 #include <libudev.h>
34}
35
36#include <poll.h>
37#include <linux/input.h>
38#include <fcntl.h>
39
40#include <string.h>
41
42/*
43 * Handle old kernel headers (< v4.16) which don't define input_event_sec or
44 * input_event_usec macros (for use with struct input_event).
45 */
46#ifndef input_event_sec
47#define input_event_sec time.tv_sec
48#define input_event_usec time.tv_usec
49#endif
50
51struct TypeCode {
52 unsigned type;
53 unsigned code;
54
55 inline unsigned long hashCode() const {
56 return (unsigned long)type << 16 | (unsigned long)code;
57 }
58
59 bool operator < ( const TypeCode & other) const {
60 return hashCode() < other.hashCode();
61 }
62};
63
64// event to name translation table
65// events are from include <linux/input.h>
66
67static struct EventTypes {
69 const char * name;
70} EVENT_TYPES[] = {
71 { { EV_SYN, SYN_REPORT }, "syn-report" },
72 { { EV_SYN, SYN_CONFIG }, "syn-config" },
73
74 // misc
75 { { EV_KEY, BTN_0 }, "button-0" },
76 { { EV_KEY, BTN_1 }, "button-1" },
77 { { EV_KEY, BTN_2 }, "button-2" },
78 { { EV_KEY, BTN_3 }, "button-3" },
79 { { EV_KEY, BTN_4 }, "button-4" },
80 { { EV_KEY, BTN_5 }, "button-5" },
81 { { EV_KEY, BTN_6 }, "button-6" },
82 { { EV_KEY, BTN_7 }, "button-7" },
83 { { EV_KEY, BTN_8 }, "button-8" },
84 { { EV_KEY, BTN_9 }, "button-9" },
85
86 // mouse
87 { { EV_KEY, BTN_LEFT }, "button-left" },
88 { { EV_KEY, BTN_RIGHT }, "button-right" },
89 { { EV_KEY, BTN_MIDDLE }, "button-middle" },
90 { { EV_KEY, BTN_SIDE }, "button-side" },
91 { { EV_KEY, BTN_EXTRA }, "button-extra" },
92 { { EV_KEY, BTN_FORWARD }, "button-forward" },
93 { { EV_KEY, BTN_BACK }, "button-back" },
94 { { EV_KEY, BTN_TASK }, "button-task" },
95
96 // joystick
97 { { EV_KEY, BTN_TRIGGER }, "button-trigger" },
98 { { EV_KEY, BTN_THUMB }, "button-thumb" },
99 { { EV_KEY, BTN_THUMB2 }, "button-thumb2" },
100 { { EV_KEY, BTN_TOP }, "button-top" },
101 { { EV_KEY, BTN_TOP2 }, "button-top2" },
102 { { EV_KEY, BTN_PINKIE }, "button-pinkie" },
103 { { EV_KEY, BTN_BASE }, "button-base" },
104 { { EV_KEY, BTN_BASE2 }, "button-base2" },
105 { { EV_KEY, BTN_BASE3 }, "button-base3" },
106 { { EV_KEY, BTN_BASE4 }, "button-base4" },
107 { { EV_KEY, BTN_BASE5 }, "button-base5" },
108 { { EV_KEY, BTN_BASE6 }, "button-base6" },
109 { { EV_KEY, BTN_DEAD }, "button-dead" },
110
111 // gamepad
112 { { EV_KEY, BTN_A }, "button-a" },
113 { { EV_KEY, BTN_B }, "button-b" },
114 { { EV_KEY, BTN_C }, "button-c" },
115 { { EV_KEY, BTN_X }, "button-x" },
116 { { EV_KEY, BTN_Y }, "button-y" },
117 { { EV_KEY, BTN_Z }, "button-z" },
118 { { EV_KEY, BTN_TL }, "button-tl" },
119 { { EV_KEY, BTN_TR }, "button-tr" },
120 { { EV_KEY, BTN_TL2 }, "button-tl2" },
121 { { EV_KEY, BTN_TR2 }, "button-tr2" },
122 { { EV_KEY, BTN_SELECT }, "button-select" },
123 { { EV_KEY, BTN_START }, "button-start" },
124 { { EV_KEY, BTN_MODE }, "button-mode" },
125 { { EV_KEY, BTN_THUMBL }, "button-thumbl" },
126 { { EV_KEY, BTN_THUMBR }, "button-thumbr" },
127
128 // digitizer
129 { { EV_KEY, BTN_TOOL_PEN }, "button-pen" },
130 { { EV_KEY, BTN_TOOL_RUBBER }, "button-rubber" },
131 { { EV_KEY, BTN_TOOL_BRUSH }, "button-brush" },
132 { { EV_KEY, BTN_TOOL_PENCIL }, "button-pencil" },
133 { { EV_KEY, BTN_TOOL_AIRBRUSH }, "button-airbrush" },
134 { { EV_KEY, BTN_TOOL_FINGER }, "button-finger" },
135 { { EV_KEY, BTN_TOOL_MOUSE }, "button-mouse" },
136 { { EV_KEY, BTN_TOOL_LENS }, "button-lens" },
137 { { EV_KEY, BTN_TOUCH }, "button-touch" },
138 { { EV_KEY, BTN_STYLUS }, "button-stylus" },
139 { { EV_KEY, BTN_STYLUS2 }, "button-stylus2" },
140 { { EV_KEY, BTN_TOOL_DOUBLETAP }, "button-doubletap" },
141 { { EV_KEY, BTN_TOOL_TRIPLETAP }, "button-trippletap" },
142
143 { { EV_KEY, BTN_WHEEL }, "button-wheel" },
144 { { EV_KEY, BTN_GEAR_DOWN }, "button-gear-down" },
145 { { EV_KEY, BTN_GEAR_UP }, "button-gear-up" },
146
147 { { EV_REL, REL_X }, "rel-x-translate" },
148 { { EV_REL, REL_Y}, "rel-y-translate" },
149 { { EV_REL, REL_Z}, "rel-z-translate" },
150 { { EV_REL, REL_RX}, "rel-x-rotate" },
151 { { EV_REL, REL_RY}, "rel-y-rotate" },
152 { { EV_REL, REL_RZ}, "rel-z-rotate" },
153 { { EV_REL, REL_HWHEEL}, "rel-hwheel" },
154 { { EV_REL, REL_DIAL}, "rel-dial" },
155 { { EV_REL, REL_WHEEL}, "rel-wheel" },
156 { { EV_REL, REL_MISC}, "rel-misc" },
157
158 { { EV_ABS, ABS_X }, "abs-x-translate" },
159 { { EV_ABS, ABS_Y }, "abs-y-translate" },
160 { { EV_ABS, ABS_Z }, "abs-z-translate" },
161 { { EV_ABS, ABS_RX }, "abs-x-rotate" },
162 { { EV_ABS, ABS_RY }, "abs-y-rotate" },
163 { { EV_ABS, ABS_RZ }, "abs-z-rotate" },
164 { { EV_ABS, ABS_THROTTLE }, "abs-throttle" },
165 { { EV_ABS, ABS_RUDDER }, "abs-rudder" },
166 { { EV_ABS, ABS_WHEEL }, "abs-wheel" },
167 { { EV_ABS, ABS_GAS }, "abs-gas" },
168 { { EV_ABS, ABS_BRAKE }, "abs-brake" },
169 { { EV_ABS, ABS_HAT0X }, "abs-hat0-x" },
170 { { EV_ABS, ABS_HAT0Y }, "abs-hat0-y" },
171 { { EV_ABS, ABS_HAT1X }, "abs-hat1-x" },
172 { { EV_ABS, ABS_HAT1Y }, "abs-hat1-y" },
173 { { EV_ABS, ABS_HAT2X }, "abs-hat2-x" },
174 { { EV_ABS, ABS_HAT2Y }, "abs-hat2-y" },
175 { { EV_ABS, ABS_HAT3X }, "abs-hat3-x" },
176 { { EV_ABS, ABS_HAT3Y }, "abs-hat3-y" },
177 { { EV_ABS, ABS_PRESSURE }, "abs-pressure" },
178 { { EV_ABS, ABS_DISTANCE }, "abs-distance" },
179 { { EV_ABS, ABS_TILT_X }, "abs-tilt-x" },
180 { { EV_ABS, ABS_TILT_Y }, "abs-tilt-y" },
181 { { EV_ABS, ABS_TOOL_WIDTH }, "abs-toold-width" },
182 { { EV_ABS, ABS_VOLUME }, "abs-volume" },
183 { { EV_ABS, ABS_MISC }, "abs-misc" },
184
185 { { EV_MSC, MSC_SERIAL }, "misc-serial" },
186 { { EV_MSC, MSC_PULSELED }, "misc-pulseled" },
187 { { EV_MSC, MSC_GESTURE }, "misc-gesture" },
188 { { EV_MSC, MSC_RAW }, "misc-raw" },
189 { { EV_MSC, MSC_SCAN }, "misc-scan" },
190
191 // switch
192 { { EV_SW, SW_LID }, "switch-lid" },
193 { { EV_SW, SW_TABLET_MODE }, "switch-tablet-mode" },
194 { { EV_SW, SW_HEADPHONE_INSERT }, "switch-headphone-insert" },
195#ifdef SW_RFKILL_ALL
196 { { EV_SW, SW_RFKILL_ALL }, "switch-rfkill" },
197#endif
198#ifdef SW_MICROPHONE_INSERT
199 { { EV_SW, SW_MICROPHONE_INSERT }, "switch-microphone-insert" },
200#endif
201#ifdef SW_DOCK
202 { { EV_SW, SW_DOCK }, "switch-dock" },
203#endif
204
205 { { EV_LED, LED_NUML}, "led-numlock" },
206 { { EV_LED, LED_CAPSL}, "led-capslock" },
207 { { EV_LED, LED_SCROLLL}, "led-scrolllock" },
208 { { EV_LED, LED_COMPOSE}, "led-compose" },
209 { { EV_LED, LED_KANA}, "led-kana" },
210 { { EV_LED, LED_SLEEP}, "led-sleep" },
211 { { EV_LED, LED_SUSPEND}, "led-suspend" },
212 { { EV_LED, LED_MUTE}, "led-mute" },
213 { { EV_LED, LED_MISC}, "led-misc" },
214 { { EV_LED, LED_MAIL}, "led-mail" },
215 { { EV_LED, LED_CHARGING}, "led-charging" }
216
218
219static struct enbet {
220 unsigned type;
221 const char * name;
223 { EV_SYN, "syn" },
224 { EV_KEY, "button" },
225 { EV_REL, "rel" },
226 { EV_ABS, "abs" },
227 { EV_MSC, "msc" },
228 { EV_SW, "button" },
229 { EV_LED, "led" },
230 { EV_SND, "snd" },
231 { EV_REP, "rep" },
232 { EV_FF, "ff" },
233 { EV_PWR, "pwr" },
234 { EV_FF_STATUS, "ff-status" }
236
237
238class EventNameByEventType : public std::map<unsigned,const char*> {
239public:
241 for( unsigned i = 0; i < sizeof(EVENT_NAMES_BY_EVENT_TYPE)/sizeof(EVENT_NAMES_BY_EVENT_TYPE[0]); i++ )
243 }
244};
246
247class EventNameByType : public std::map<TypeCode,const char*> {
248public:
250 for( unsigned i = 0; i < sizeof(EVENT_TYPES)/sizeof(EVENT_TYPES[0]); i++ )
251 (*this)[EVENT_TYPES[i].typeCode] = EVENT_TYPES[i].name;
252 }
253};
255
256
257struct ltstr {
258 bool operator()(const char * s1, const char * s2 ) const {
259 return std::string(s1).compare( s2 ) < 0;
260 }
261};
262
263class EventTypeByName : public std::map<const char *,TypeCode,ltstr> {
264public:
266 for( unsigned i = 0; i < sizeof(EVENT_TYPES)/sizeof(EVENT_TYPES[0]); i++ )
267 (*this)[EVENT_TYPES[i].name] = EVENT_TYPES[i].typeCode;
268 }
269};
271
272
273FGLinuxInputDevice::FGLinuxInputDevice( std::string aName, std::string aDevname, std::string aSerial, std::string aDevpath ) :
274 FGInputDevice(aName,aSerial),
275 devfile(aDevname),
276 devpath(aDevpath),
277 fd(-1)
278{
279 class_id = "FGLinuxInputDevice";
280 SG_LOG(SG_INPUT, SG_DEBUG, "FGLinuxInputDevice " << aName << " (s/n: " << aSerial << ") " << aDevname);
281}
282
284{
285 try {
286 Close();
287 }
288 catch(...) {
289 }
290}
291
296
297static inline bool bitSet( unsigned char * buf, unsigned bit )
298{
299 return (buf[bit/sizeof(unsigned char)/8] >> (bit%(sizeof(unsigned char)*8))) & 1;
300}
301
303{
304 if( fd != -1 ) return true;
305 if( (fd = ::open( devfile.c_str(), O_RDWR )) == -1 ) {
306 SG_LOG( SG_INPUT, SG_ALERT, "Cannot open '" << devfile
307 << "'. errno=" << errno
308 << ": " << strerror(errno)
309 );
310 //throw std::exception();
311 return false;
312 }
313
314 if( GetGrab() && ioctl( fd, EVIOCGRAB, 2 ) == -1 ) {
315 SG_LOG( SG_INPUT, SG_WARN, "Can't grab " << devfile << " for exclusive access" );
316 }
317
318 {
319 unsigned char buf[ABS_CNT/sizeof(unsigned char)/8];
320 // get axes maximums
321 if( ioctl( fd, EVIOCGBIT(EV_ABS,ABS_MAX), buf ) == -1 ) {
322 SG_LOG( SG_INPUT, SG_WARN, "Can't get abs-axes for " << devfile );
323 } else {
324 for( unsigned i = 0; i < ABS_MAX; i++ ) {
325 if( bitSet( buf, i ) ) {
326 struct input_absinfo ai;
327 if( ioctl(fd, EVIOCGABS(i), &ai) == -1 ) {
328 SG_LOG( SG_INPUT, SG_WARN, "Can't get abs-axes maximums for " << devfile );
329 continue;
330 }
331 absinfo[i] = ai;
332/*
333 SG_LOG( SG_INPUT, SG_INFO, "Axis #" << i <<
334 ": value=" << ai.value <<
335 ": minimum=" << ai.minimum <<
336 ": maximum=" << ai.maximum <<
337 ": fuzz=" << ai.fuzz <<
338 ": flat=" << ai.flat <<
339 ": resolution=" << ai.resolution );
340*/
341
342 // kick an initial event
343 struct input_event event;
344 event.type = EV_ABS;
345 event.code = i;
346 event.value = ai.value;
347 FGLinuxEventData eventData( event, 0, 0 );
348 eventData.value = Normalize( event );
349 HandleEvent(eventData);
350 }
351 }
352 }
353 }
354 {
355 unsigned char mask[KEY_CNT/sizeof(unsigned char)/8];
356 unsigned char flag[KEY_CNT/sizeof(unsigned char)/8];
357 memset(mask,0,sizeof(mask));
358 memset(flag,0,sizeof(flag));
359 if( ioctl( fd, EVIOCGKEY(sizeof(flag)), flag ) == -1 ||
360 ioctl( fd, EVIOCGBIT(EV_KEY, sizeof(mask)), mask ) == -1 ) {
361 SG_LOG( SG_INPUT, SG_WARN, "Can't get keys for " << devfile );
362 } else {
363 for( unsigned i = 0; i < KEY_MAX; i++ ) {
364 if( bitSet( mask, i ) ) {
365 struct input_event event;
366 event.type = EV_KEY;
367 event.code = i;
368 event.value = bitSet(flag,i);
369 FGLinuxEventData eventData( event, 0, 0 );
370 HandleEvent(eventData);
371 }
372 }
373 }
374 }
375 {
376 unsigned char buf[SW_CNT/sizeof(unsigned char)/8];
377 if( ioctl( fd, EVIOCGSW(sizeof(buf)), buf ) == -1 ) {
378 SG_LOG( SG_INPUT, SG_WARN, "Can't get switches for " << devfile );
379 } else {
380 for( unsigned i = 0; i < SW_MAX; i++ ) {
381 if( bitSet( buf, i ) ) {
382 struct input_event event;
383 event.type = EV_SW;
384 event.code = i;
385 event.value = 1;
386 FGLinuxEventData eventData( event, 0, 0 );
387 HandleEvent(eventData);
388 }
389 }
390 }
391 }
392
393 return true;
394}
395
396double FGLinuxInputDevice::Normalize( struct input_event & event )
397{
398 if( absinfo.count(event.code) > 0 ) {
399 const struct input_absinfo & ai = absinfo[(unsigned int)event.code];
400 if( ai.maximum == ai.minimum )
401 return 0.0;
402 return ((double)event.value-(double)ai.minimum)/((double)ai.maximum-(double)ai.minimum);
403 } else {
404 return (double)event.value;
405 }
406}
407
409{
410 if( fd != -1 ) {
411 if( GetGrab() && ioctl( fd, EVIOCGRAB, 0 ) != 0 ) {
412 SG_LOG( SG_INPUT, SG_WARN, "Can't ungrab " << devfile );
413 }
414 ::close(fd);
415 }
416 fd = -1;
417}
418
419void FGLinuxInputDevice::Send( const char * eventName, double value )
420{
421 if( EVENT_TYPE_BY_NAME.count( eventName ) <= 0 ) {
422 SG_LOG( SG_INPUT, SG_DEBUG, "Can't send unknown event " << eventName );
423 return;
424 }
425
426 TypeCode & typeCode = EVENT_TYPE_BY_NAME[ eventName ];
427
428 if( fd == -1 )
429 return;
430
431 input_event evt;
432 evt.type=typeCode.type;
433 evt.code = typeCode.code;
434 evt.value = (long)value;
435 evt.input_event_sec = 0;
436 evt.input_event_usec = 0;
437 size_t bytes_written = write(fd, &evt, sizeof(evt));
438 lastEventName->setStringValue(eventName);
439 lastEventValue->setDoubleValue(value);
440 if( bytes_written == sizeof(evt) )
441 SG_LOG(SG_INPUT, SG_DEBUG, "Written event " << eventName << " as type=" << evt.type
442 << ", code=" << evt.code << " value=" << evt.value);
443 else
444 SG_LOG(SG_INPUT, SG_WARN, "Failed to write event: written = " << bytes_written);
445}
446
447static char ugly_buffer[128];
449{
450 FGLinuxEventData & linuxEventData = (FGLinuxEventData&)eventData;
451 TypeCode typeCode;
452 typeCode.type = linuxEventData.type;
453 typeCode.code = linuxEventData.code;
454 if( EVENT_NAME_BY_TYPE.count(typeCode) == 0 ) {
455 // event not known in translation tables
456 if( EVENT_NAME_BY_EVENT_TYPE.count(linuxEventData.type) == 0 ) {
457 // event type not known in translation tables
458 sprintf( ugly_buffer, "unknown-%u-%u", (unsigned)linuxEventData.type, (unsigned)linuxEventData.code );
459 return ugly_buffer;
460 }
461 sprintf( ugly_buffer, "%s-%u", EVENT_NAME_BY_EVENT_TYPE[linuxEventData.type], (unsigned)linuxEventData.code );
462 return ugly_buffer;
463 }
464
465 return EVENT_NAME_BY_TYPE[typeCode];
466}
467
468void FGLinuxInputDevice::SetDevname( const std::string & name )
469{
470 this->devfile = name;
471}
472
473FGLinuxEventInput::FGLinuxEventInput() : FGEventInput("Input/Event", "/input/event")
474{
475}
476
480
482{
484
485 struct udev * udev = udev_new();
486
487 // REVIEW: Memory Leak - 55,004 (40 direct, 54,964 indirect) bytes in 1 blocks are definitely lost
488 struct udev_enumerate *enumerate = udev_enumerate_new(udev);
489 udev_enumerate_add_match_subsystem(enumerate, "input");
490 udev_enumerate_scan_devices(enumerate);
491 // REVIEW: Memory Leak - 4,995 (40 direct, 4,955 indirect) bytes in 1 blocks are definitely lost
492 struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
493 struct udev_list_entry *dev_list_entry;
494
495 udev_list_entry_foreach(dev_list_entry, devices) {
496 const char * syspath = udev_list_entry_get_name(dev_list_entry);
497 struct udev_device *dev = udev_device_new_from_syspath(udev, syspath);
498 const char * devFile = udev_device_get_devnode(dev);
499
500 // get device path by removing trailing sysname from syspath
501 std::string sysname = udev_device_get_sysname(dev);
502 std::string devpath = syspath;
503 devpath.erase(devpath.length() - sysname.length());
504
505 struct udev_device * parent_dev = udev_device_get_parent( dev );
506 if ( parent_dev != NULL ) {
507 const char * name = udev_device_get_sysattr_value(parent_dev,"name");
508 if( name && devFile ) {
509 const char * serial = udev_device_get_sysattr_value(parent_dev, "serial");
510 SG_LOG(SG_INPUT,SG_DEBUG, "FGLinuxEventInput: path=" << devpath << ": " <<
511 " name=" << (name?name:"<null>") << ", devFile=" << (devFile?devFile:"<null>") );
512
513 std::string serialString = serial ? serial : std::string{};
514 AddDevice( new FGLinuxInputDevice(name, devFile, serialString, devpath) );
515 }
516 }
517
518 udev_device_unref(dev);
519 }
520
521 udev_enumerate_unref(enumerate); // REVIEW: this should fix the memory leak
522 udev_unref(udev);
523
524}
525
527{
529 // index the input devices by the associated fd and prepare
530 // the pollfd array by filling in the file descriptor
531 struct pollfd fds[inputDevices.size()];
532 std::map<int,FGLinuxInputDevice*> devicesByFd;
533 std::map<int,FGInputDevice*>::const_iterator it;
534 int i;
535 for( i=0, it = inputDevices.begin(); it != inputDevices.end(); ++it, i++ ) {
536 FGInputDevice* p = (*it).second;
537 int fd = ((FGLinuxInputDevice*)p)->GetFd();
538 fds[i].fd = fd;
539 fds[i].events = POLLIN;
540 devicesByFd[fd] = (FGLinuxInputDevice*)p;
541 }
542
543 int modifiers = fgGetKeyModifiers();
544 // poll all devices until no more events are in the queue
545 // do no more than maxpolls in a single loop to prevent locking
546 int maxpolls = 100;
547 while( maxpolls-- > 0 && ::poll( fds, i, 0 ) > 0 ) {
548 for( unsigned i = 0; i < sizeof(fds)/sizeof(fds[0]); i++ ) {
549 if( fds[i].revents & POLLIN ) {
550
551 // if this device reports data ready, it should be a input_event struct
552 struct input_event event;
553
554 if( read( fds[i].fd, &event, sizeof(event) ) != sizeof(event) )
555 continue;
556
557 FGLinuxEventData eventData( event, dt, modifiers );
558
559 if( event.type == EV_ABS )
560 eventData.value = devicesByFd[fds[i].fd]->Normalize( event );
561
562 // let the FGInputDevice handle the data
563 devicesByFd[fds[i].fd]->HandleEvent( eventData );
564 }
565 }
566 }
567}
#define p(x)
static EventNameByType EVENT_NAME_BY_TYPE
static struct enbet EVENT_NAMES_BY_EVENT_TYPE[]
static EventTypeByName EVENT_TYPE_BY_NAME
static struct EventTypes EVENT_TYPES[]
static char ugly_buffer[128]
static EventNameByEventType EVENT_NAME_BY_EVENT_TYPE
static bool bitSet(unsigned char *buf, unsigned bit)
#define i(x)
void update(double dt) override
std::map< int, FGInputDevice * > inputDevices
void postinit() override
unsigned AddDevice(FGInputDevice *inputDevice)
std::string class_id
std::string name
SGPropertyNode_ptr lastEventValue
SGPropertyNode_ptr lastEventName
void HandleEvent(FGEventData &eventData)
bool GetGrab() const
void postinit() override
void update(double dt) override
double Normalize(struct input_event &event)
void SetDevname(const std::string &name)
void Send(const char *eventName, double value) override
const char * TranslateEventName(FGEventData &eventData) override
const char * name
int fgGetKeyModifiers()
const char * name
struct TypeCode typeCode
bool operator<(const TypeCode &other) const
unsigned long hashCode() const
unsigned type
const char * name
bool operator()(const char *s1, const char *s2) const