joystick 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ext/extconf.rb +5 -0
- data/ext/joystick.c +451 -0
- metadata +63 -0
data/ext/extconf.rb
ADDED
data/ext/joystick.c
ADDED
@@ -0,0 +1,451 @@
|
|
1
|
+
/**
|
2
|
+
* Joystick - Ruby binding for linux kernel joystick
|
3
|
+
* Copyright (C) 2008 Claudio Fiorini <claudio@cfiorini.it>
|
4
|
+
*
|
5
|
+
* This program is free software: you can redistribute it and/or modify
|
6
|
+
* it under the terms of the GNU General Public License as published by
|
7
|
+
* the Free Software Foundation, either version 3 of the License, or
|
8
|
+
* (at your option) any later version.
|
9
|
+
*
|
10
|
+
* This program is distributed in the hope that it will be useful,
|
11
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
* GNU General Public License for more details.
|
14
|
+
*
|
15
|
+
* You should have received a copy of the GNU General Public License
|
16
|
+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
**/
|
18
|
+
|
19
|
+
/* Any help and suggestions are always welcome */
|
20
|
+
/* TODO change klass to obj when it's an Object and not a Class... */
|
21
|
+
|
22
|
+
|
23
|
+
#include "ruby.h"
|
24
|
+
#include "ruby/io.h"
|
25
|
+
#include <linux/joystick.h>
|
26
|
+
#include <sys/select.h>
|
27
|
+
#include <sys/time.h>
|
28
|
+
#include <sys/types.h>
|
29
|
+
#include <fcntl.h>
|
30
|
+
#include <unistd.h>
|
31
|
+
#include <string.h>
|
32
|
+
#include <errno.h>
|
33
|
+
#include <stdint.h>
|
34
|
+
|
35
|
+
#define MAX_JS 32
|
36
|
+
#define NAME_LENGTH 128
|
37
|
+
|
38
|
+
static VALUE rb_mJoystick;
|
39
|
+
static VALUE rb_cDevice;
|
40
|
+
static VALUE rb_cEvent;
|
41
|
+
static VALUE rb_cSixaxis;
|
42
|
+
|
43
|
+
void Init_joystick();
|
44
|
+
|
45
|
+
static struct js_event jse[MAX_JS];
|
46
|
+
|
47
|
+
void jsdevice_mark(int* fd)
|
48
|
+
{
|
49
|
+
rb_gc_mark(*fd);
|
50
|
+
}
|
51
|
+
|
52
|
+
void jsdevice_free(int* fd)
|
53
|
+
{
|
54
|
+
free(fd);
|
55
|
+
}
|
56
|
+
|
57
|
+
void jssix_mark(int* fh)
|
58
|
+
{
|
59
|
+
rb_gc_mark(*fh);
|
60
|
+
}
|
61
|
+
|
62
|
+
void jssix_free(int* fh)
|
63
|
+
{
|
64
|
+
free(fh);
|
65
|
+
}
|
66
|
+
|
67
|
+
/*
|
68
|
+
* Document-method: Joystick::Device.new
|
69
|
+
* call-seq: new(path)
|
70
|
+
*
|
71
|
+
* Construct a new Joystick::Device object. +path+ is the file
|
72
|
+
* path to the joystick's input device e.g. "/dev/input/js0"
|
73
|
+
*/
|
74
|
+
VALUE js_dev_init(VALUE klass, VALUE dev_path)
|
75
|
+
{
|
76
|
+
int *fd;
|
77
|
+
VALUE dev;
|
78
|
+
|
79
|
+
if((fd = malloc(sizeof(int))) != NULL) {
|
80
|
+
if((*fd = open(RSTRING_PTR(dev_path), O_RDONLY)) >= 0) {
|
81
|
+
if(*fd >= MAX_JS)
|
82
|
+
rb_raise(rb_eException, "Error");
|
83
|
+
|
84
|
+
dev = Data_Wrap_Struct(klass, jsdevice_mark, jsdevice_free, fd);
|
85
|
+
rb_ivar_set(dev, rb_intern("@axis"), rb_ary_new());
|
86
|
+
rb_ivar_set(dev, rb_intern("@button"), rb_ary_new());
|
87
|
+
return dev;
|
88
|
+
}
|
89
|
+
}
|
90
|
+
return Qnil;
|
91
|
+
}
|
92
|
+
|
93
|
+
/*
|
94
|
+
* Document-method: Joystick::Device#axes
|
95
|
+
* call-seq: axes()
|
96
|
+
*
|
97
|
+
* Returns the number of axes of the device.
|
98
|
+
*/
|
99
|
+
VALUE js_dev_axes(VALUE klass)
|
100
|
+
{
|
101
|
+
int *fd;
|
102
|
+
unsigned char axes;
|
103
|
+
|
104
|
+
Data_Get_Struct(klass, int, fd);
|
105
|
+
if(ioctl(*fd, JSIOCGAXES, &axes) == -1) {
|
106
|
+
rb_raise(rb_eException, "cannot retrieve axes");
|
107
|
+
}
|
108
|
+
return INT2FIX(axes);
|
109
|
+
}
|
110
|
+
|
111
|
+
/*
|
112
|
+
* Document-method: Joystick::Device#buttons
|
113
|
+
* call-seq: buttons()
|
114
|
+
*
|
115
|
+
* Returns the number of buttons on the device.
|
116
|
+
*/
|
117
|
+
VALUE js_dev_buttons(VALUE klass)
|
118
|
+
{
|
119
|
+
int *fd;
|
120
|
+
unsigned char buttons;
|
121
|
+
Data_Get_Struct(klass, int, fd);
|
122
|
+
if(ioctl(*fd, JSIOCGBUTTONS, &buttons) == -1) {
|
123
|
+
rb_raise(rb_eException, "cannot retrieve buttons");
|
124
|
+
}
|
125
|
+
|
126
|
+
return INT2FIX(buttons);
|
127
|
+
}
|
128
|
+
|
129
|
+
/*
|
130
|
+
* Document-method: Joystick::Device#axis
|
131
|
+
* call-seq: axis()
|
132
|
+
*
|
133
|
+
* Reader for @axis which stores the latest axis values.
|
134
|
+
*/
|
135
|
+
VALUE js_dev_axis(VALUE klass)
|
136
|
+
{
|
137
|
+
return rb_ivar_get(klass, rb_intern("@axis"));
|
138
|
+
}
|
139
|
+
|
140
|
+
/*
|
141
|
+
* Document-method: Joystick::Device#button
|
142
|
+
* call-seq: button()
|
143
|
+
*
|
144
|
+
* Reader for @button which stores the latest button values.
|
145
|
+
*/
|
146
|
+
VALUE js_dev_button(VALUE klass)
|
147
|
+
{
|
148
|
+
return rb_ivar_get(klass, rb_intern("@button"));
|
149
|
+
}
|
150
|
+
|
151
|
+
/*
|
152
|
+
* Document-method: Joystick::Device#name
|
153
|
+
* call-seq: name()
|
154
|
+
*
|
155
|
+
* Returns the name of the device.
|
156
|
+
*/
|
157
|
+
VALUE js_dev_name(VALUE klass)
|
158
|
+
{
|
159
|
+
int *fd;
|
160
|
+
char name[NAME_LENGTH] = "Unknown";
|
161
|
+
|
162
|
+
Data_Get_Struct(klass, int, fd);
|
163
|
+
if(ioctl(*fd, JSIOCGNAME(NAME_LENGTH), name) == -1) {
|
164
|
+
rb_raise(rb_eException, "cannot retrieve name");
|
165
|
+
}
|
166
|
+
return rb_str_new2(name);
|
167
|
+
}
|
168
|
+
|
169
|
+
/*
|
170
|
+
* Document-method: Joystick::Device#axes_maps
|
171
|
+
* call-seq: axes_maps()
|
172
|
+
*
|
173
|
+
* TODO figure this out
|
174
|
+
*/
|
175
|
+
VALUE js_dev_axes_maps(VALUE klass)
|
176
|
+
{
|
177
|
+
int *fd;
|
178
|
+
|
179
|
+
uint8_t axes_maps[ABS_MAX + 1];
|
180
|
+
Data_Get_Struct(klass, int, fd);
|
181
|
+
if(ioctl(*fd, JSIOCGAXMAP, &axes_maps) == -1) {
|
182
|
+
rb_raise(rb_eException, "cannot retrive axes");
|
183
|
+
}
|
184
|
+
return INT2FIX(axes_maps);
|
185
|
+
}
|
186
|
+
|
187
|
+
/*
|
188
|
+
* Document-method: Joystick::Device#version
|
189
|
+
* call-seq: version()
|
190
|
+
*
|
191
|
+
* Returns a string containing the version of the device.
|
192
|
+
*/
|
193
|
+
VALUE js_dev_version(VALUE klass)
|
194
|
+
{
|
195
|
+
int *fd;
|
196
|
+
int version = 0x000800;
|
197
|
+
char js_version[16];
|
198
|
+
Data_Get_Struct(klass, int, fd);
|
199
|
+
if(ioctl(*fd, JSIOCGVERSION, &version) == -1) {
|
200
|
+
rb_raise(rb_eException, "version error");
|
201
|
+
}
|
202
|
+
|
203
|
+
sprintf(js_version, "%d.%d.%d\n",
|
204
|
+
version >> 16, (version >> 8) & 0xff, version & 0xff);
|
205
|
+
|
206
|
+
return rb_str_new2(js_version);
|
207
|
+
}
|
208
|
+
|
209
|
+
/* used for blocking calls to Joystick::Device#event */
|
210
|
+
struct event_arg {
|
211
|
+
int *fd;
|
212
|
+
ssize_t l;
|
213
|
+
};
|
214
|
+
|
215
|
+
/* general idea stolen from curses.c */
|
216
|
+
static VALUE
|
217
|
+
js_event_func(void *_arg)
|
218
|
+
{
|
219
|
+
struct event_arg *arg = (struct event_arg *)_arg;
|
220
|
+
arg->l = read(*(arg->fd), &jse[*(arg->fd)], sizeof(struct js_event));
|
221
|
+
return Qnil;
|
222
|
+
}
|
223
|
+
|
224
|
+
/*
|
225
|
+
* Document-method: Joystick::Device#event
|
226
|
+
* call-seq: event(+nonblocking+)
|
227
|
+
*
|
228
|
+
* Get a Joystick::Event object from the device.
|
229
|
+
*
|
230
|
+
* The optional +nonblocking+ argument determines whether or not
|
231
|
+
* this is a blocking call. It is blocking by default.
|
232
|
+
*/
|
233
|
+
VALUE js_dev_event_get(int argc, VALUE *argv, VALUE klass)
|
234
|
+
{
|
235
|
+
struct event_arg arg;
|
236
|
+
int *fd;
|
237
|
+
ssize_t length;
|
238
|
+
VALUE nonblocking;
|
239
|
+
|
240
|
+
rb_scan_args(argc, argv, "01", &nonblocking);
|
241
|
+
|
242
|
+
Data_Get_Struct(klass, int, fd);
|
243
|
+
|
244
|
+
if(RTEST(nonblocking))
|
245
|
+
{
|
246
|
+
/* TODO I'm not sure how big of a performance hit this is */
|
247
|
+
fcntl(*fd, F_SETFL, O_NONBLOCK); /* non-blocking mode */
|
248
|
+
length = read(*fd, &jse[*fd], sizeof(struct js_event));
|
249
|
+
fcntl(*fd, F_SETFL, fcntl(*fd, F_GETFL) & ~O_NONBLOCK); /* revert to blocking mode */
|
250
|
+
} else {
|
251
|
+
arg.fd = fd;
|
252
|
+
rb_thread_blocking_region(js_event_func, (void *)&arg, RUBY_UBF_IO, 0);
|
253
|
+
length = arg.l;
|
254
|
+
}
|
255
|
+
|
256
|
+
if(length > 0)
|
257
|
+
{
|
258
|
+
switch(jse[*fd].type & ~JS_EVENT_INIT) /* TODO I think it's safe to assume we have a valid event now */
|
259
|
+
{
|
260
|
+
case JS_EVENT_AXIS:
|
261
|
+
rb_ary_store(rb_ivar_get(klass, rb_intern("@axis")), jse[*fd].number, INT2FIX(jse[*fd].value));
|
262
|
+
break;
|
263
|
+
case JS_EVENT_BUTTON:
|
264
|
+
rb_ary_store(rb_ivar_get(klass, rb_intern("@button")), jse[*fd].number, INT2FIX(jse[*fd].value));
|
265
|
+
}
|
266
|
+
return Data_Wrap_Struct(rb_cEvent, 0, 0, fd);
|
267
|
+
}
|
268
|
+
|
269
|
+
return Qnil;
|
270
|
+
}
|
271
|
+
|
272
|
+
/*
|
273
|
+
* Document-method: Joystick::Device#close
|
274
|
+
* call-seq: close()
|
275
|
+
*
|
276
|
+
* Close the file handle for the device. This should be called
|
277
|
+
* for all Joystick::Devices before the script terminates.
|
278
|
+
*/
|
279
|
+
VALUE js_dev_close(VALUE klass)
|
280
|
+
{
|
281
|
+
int *fd;
|
282
|
+
|
283
|
+
Data_Get_Struct(klass, int, fd);
|
284
|
+
close(*fd);
|
285
|
+
return Qnil;
|
286
|
+
}
|
287
|
+
|
288
|
+
/*
|
289
|
+
* Document-method: Joystick::Event#number
|
290
|
+
* call-seq: number()
|
291
|
+
*
|
292
|
+
* Returns the number of the axis or button responsible for
|
293
|
+
* the event.
|
294
|
+
*/
|
295
|
+
VALUE js_event_number(VALUE klass)
|
296
|
+
{
|
297
|
+
int *fd;
|
298
|
+
Data_Get_Struct(klass, int, fd);
|
299
|
+
return INT2FIX((fd && *fd >= 0) ? jse[*fd].number : -1);
|
300
|
+
}
|
301
|
+
|
302
|
+
/*
|
303
|
+
* Document-method: Joystick::Event#type
|
304
|
+
* call-seq: type()
|
305
|
+
*
|
306
|
+
* Returns the type of the event. Normally this should either
|
307
|
+
* be either :axis or :button. If "something goes wrong", the
|
308
|
+
* numerical type is returned.
|
309
|
+
*/
|
310
|
+
VALUE js_event_type(VALUE klass)
|
311
|
+
{
|
312
|
+
int *fd;
|
313
|
+
Data_Get_Struct(klass, int, fd);
|
314
|
+
switch(((fd && *fd >= 0) ? jse[*fd].type : -1) & ~JS_EVENT_INIT)
|
315
|
+
{
|
316
|
+
case JS_EVENT_AXIS:
|
317
|
+
return ID2SYM(rb_intern("axis"));
|
318
|
+
case JS_EVENT_BUTTON:
|
319
|
+
return ID2SYM(rb_intern("button"));
|
320
|
+
default:
|
321
|
+
return INT2FIX(((fd && *fd >= 0) ? jse[*fd].type : -1) & ~JS_EVENT_INIT);
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
/*
|
326
|
+
* Document-method: Joystick::Event#time
|
327
|
+
* call-seq: time()
|
328
|
+
*
|
329
|
+
* Returns the time, in milliseconds, that the event occurred.
|
330
|
+
* TODO what is time 0?
|
331
|
+
*/
|
332
|
+
VALUE js_event_time(VALUE klass)
|
333
|
+
{
|
334
|
+
int *fd;
|
335
|
+
Data_Get_Struct(klass, int, fd);
|
336
|
+
return INT2FIX((fd && *fd >= 0) ? jse[*fd].time : -1);
|
337
|
+
}
|
338
|
+
|
339
|
+
/*
|
340
|
+
* Document-method: Joystick::Event#value
|
341
|
+
* call-seq: value()
|
342
|
+
*
|
343
|
+
* Returns the value of the event, which is internally a
|
344
|
+
* signed 16-bit integer. It can range from -32768 to 32767.
|
345
|
+
*/
|
346
|
+
VALUE js_event_value(VALUE klass)
|
347
|
+
{
|
348
|
+
int *fd;
|
349
|
+
Data_Get_Struct(klass, int, fd);
|
350
|
+
return INT2FIX((fd && *fd >= 0) ? jse[*fd].value : -1);
|
351
|
+
}
|
352
|
+
|
353
|
+
/*
|
354
|
+
* Document-method: Joystick::SixAxis.new
|
355
|
+
* call-seq: new(path)
|
356
|
+
*
|
357
|
+
* TODO
|
358
|
+
*/
|
359
|
+
VALUE js_six_init(VALUE klass, VALUE path)
|
360
|
+
{
|
361
|
+
int *fh;
|
362
|
+
if((fh = malloc(sizeof(int))) != NULL) {
|
363
|
+
if((*fh = open(RSTRING_PTR(path), O_RDONLY)) >= 0) {
|
364
|
+
return Data_Wrap_Struct(klass, jssix_mark, jssix_free, fh);
|
365
|
+
} else
|
366
|
+
rb_raise(rb_eException, "Error opening %s", RSTRING_PTR(path));
|
367
|
+
}
|
368
|
+
return Qnil;
|
369
|
+
}
|
370
|
+
|
371
|
+
/*
|
372
|
+
* Document-method: Joystick::SixAxis#get_sixaxis
|
373
|
+
* call-seq: get_sixaxis()
|
374
|
+
*
|
375
|
+
* TODO
|
376
|
+
*/
|
377
|
+
VALUE js_six_get_six(VALUE klass)
|
378
|
+
{
|
379
|
+
int *fh;
|
380
|
+
int res;
|
381
|
+
int x = -1;
|
382
|
+
int y = -1;
|
383
|
+
int z = -1;
|
384
|
+
unsigned char buf[128];
|
385
|
+
VALUE saxis = rb_hash_new();
|
386
|
+
|
387
|
+
Data_Get_Struct(klass, int, fh);
|
388
|
+
if(res = read(*fh, buf, sizeof(buf))) {
|
389
|
+
if(res == 48) {
|
390
|
+
x = buf[40]<<8 | buf[41];
|
391
|
+
y = buf[42]<<8 | buf[43];
|
392
|
+
z = buf[44]<<8 | buf[45];
|
393
|
+
} else if(res == 49) {
|
394
|
+
x = buf[41]<<8 | buf[42];
|
395
|
+
y = buf[43]<<8 | buf[44];
|
396
|
+
z = buf[45]<<8 | buf[46];
|
397
|
+
}
|
398
|
+
|
399
|
+
rb_hash_aset(saxis, ID2SYM(rb_intern("x")), INT2FIX(x));
|
400
|
+
rb_hash_aset(saxis, ID2SYM(rb_intern("y")), INT2FIX(y));
|
401
|
+
rb_hash_aset(saxis, ID2SYM(rb_intern("z")), INT2FIX(z));
|
402
|
+
|
403
|
+
return saxis;
|
404
|
+
} else
|
405
|
+
rb_raise(rb_eException, "error");
|
406
|
+
|
407
|
+
return Qnil;
|
408
|
+
}
|
409
|
+
|
410
|
+
/*
|
411
|
+
* Document-method: Joystick::SixAxis#close
|
412
|
+
* call-seq: close(path)
|
413
|
+
*
|
414
|
+
* TODO
|
415
|
+
*/
|
416
|
+
VALUE js_six_close(VALUE klass)
|
417
|
+
{
|
418
|
+
int *fh;
|
419
|
+
|
420
|
+
Data_Get_Struct(klass, int, fh);
|
421
|
+
|
422
|
+
return INT2FIX(close(*fh));
|
423
|
+
}
|
424
|
+
|
425
|
+
void Init_joystick()
|
426
|
+
{
|
427
|
+
rb_mJoystick = rb_define_module("Joystick");
|
428
|
+
|
429
|
+
rb_cDevice = rb_define_class_under(rb_mJoystick, "Device", rb_cObject);
|
430
|
+
rb_define_singleton_method(rb_cDevice, "new", js_dev_init, 1);
|
431
|
+
rb_define_method(rb_cDevice, "axes", js_dev_axes, 0);
|
432
|
+
rb_define_method(rb_cDevice, "buttons", js_dev_buttons, 0);
|
433
|
+
rb_define_method(rb_cDevice, "axis", js_dev_axis, 0);
|
434
|
+
rb_define_method(rb_cDevice, "button", js_dev_button, 0);
|
435
|
+
rb_define_method(rb_cDevice, "axes_maps", js_dev_axes_maps, 0);
|
436
|
+
rb_define_method(rb_cDevice, "name", js_dev_name, 0);
|
437
|
+
rb_define_method(rb_cDevice, "version", js_dev_version, 0);
|
438
|
+
rb_define_method(rb_cDevice, "event", js_dev_event_get, -1);
|
439
|
+
rb_define_method(rb_cDevice, "close", js_dev_close, 0);
|
440
|
+
|
441
|
+
rb_cEvent = rb_define_class_under(rb_mJoystick, "Event", rb_cObject);
|
442
|
+
rb_define_method(rb_cEvent, "time", js_event_time, 0);
|
443
|
+
rb_define_method(rb_cEvent, "value", js_event_value, 0);
|
444
|
+
rb_define_method(rb_cEvent, "number", js_event_number, 0);
|
445
|
+
rb_define_method(rb_cEvent, "type", js_event_type, 0);
|
446
|
+
|
447
|
+
rb_cSixaxis = rb_define_class_under(rb_mJoystick, "SixAxis", rb_cObject);
|
448
|
+
rb_define_singleton_method(rb_cSixaxis, "new", js_six_init, 1);
|
449
|
+
rb_define_method(rb_cSixaxis, "get_sixaxis", js_six_get_six, 0);
|
450
|
+
rb_define_method(rb_cSixaxis, "close", js_six_close, 0);
|
451
|
+
}
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: joystick
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Max Anselm
|
9
|
+
- Claudio Fiorini
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-03-18 00:00:00.000000000 Z
|
14
|
+
dependencies: []
|
15
|
+
description: ! 'This is a Ruby extension that wraps the fuctionality provided by
|
16
|
+
|
17
|
+
linux/joystick.h, allowing Ruby scripts to use input from Xbox 360
|
18
|
+
|
19
|
+
controllers, PS3 Sixaxis controllers, etc.
|
20
|
+
|
21
|
+
|
22
|
+
I originally took this code from Claudio Fiorini''s rjoystick gem, made it a
|
23
|
+
|
24
|
+
little easier to use, made it work with Ruby''s Threads, and added
|
25
|
+
|
26
|
+
documentation.
|
27
|
+
|
28
|
+
'
|
29
|
+
email: silverhammermba@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions:
|
32
|
+
- ext/extconf.rb
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ext/joystick.c
|
36
|
+
- ext/extconf.rb
|
37
|
+
homepage: http://github.com/silverhammermba/joystick
|
38
|
+
licenses:
|
39
|
+
- GPL-3
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
- ext
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.11
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: binding for Linux kernel joysticks
|
63
|
+
test_files: []
|