1 /*
2 Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
3 Copyright (C) 2020 Collabora Ltd.
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely.
12 */
13
14 #include "../src/SDL_internal.h"
15
16 #include <stdio.h>
17 #include <string.h>
18
19 static int run_test(void);
20
21 #if HAVE_LIBUDEV_H || defined(SDL_JOYSTICK_LINUX)
22
23 #include <stdint.h>
24
25 #include "SDL_stdinc.h"
26 #include "SDL_endian.h"
27 #include "../src/core/linux/SDL_evdev_capabilities.h"
28 #include "../src/core/linux/SDL_evdev_capabilities.c"
29
30 static const struct
31 {
32 int code;
33 const char *name;
34 } device_classes[] =
35 {
36 #define CLS(x) \
37 { SDL_UDEV_DEVICE_ ## x, #x }
38 CLS(MOUSE),
39 CLS(KEYBOARD),
40 CLS(JOYSTICK),
41 CLS(SOUND),
42 CLS(TOUCHSCREEN),
43 CLS(ACCELEROMETER),
44 #undef CLS
45 { 0, NULL }
46 };
47
48 typedef struct
49 {
50 const char *name;
51 uint16_t bus_type;
52 uint16_t vendor_id;
53 uint16_t product_id;
54 uint16_t version;
55 uint8_t ev[(EV_MAX + 1) / 8];
56 uint8_t keys[(KEY_MAX + 1) / 8];
57 uint8_t abs[(ABS_MAX + 1) / 8];
58 uint8_t rel[(REL_MAX + 1) / 8];
59 uint8_t ff[(FF_MAX + 1) / 8];
60 uint8_t props[INPUT_PROP_MAX / 8];
61 int expected;
62 } GuessTest;
63
64 /*
65 * Test-cases for guessing a device type from its capabilities.
66 *
67 * The bytes in ev, etc. are in little-endian byte order
68 * Trailing zeroes can be omitted.
69 *
70 * The evemu-describe tool is a convenient way to add a test-case for
71 * a physically available device.
72 */
73 #define ZEROx4 0, 0, 0, 0
74 #define ZEROx8 ZEROx4, ZEROx4
75 #define FFx4 0xff, 0xff, 0xff, 0xff
76 #define FFx8 FFx4, FFx4
77
78 /* Test-cases derived from real devices or from Linux kernel source */
79 static const GuessTest guess_tests[] =
80 {
81 {
82 .name = "Xbox 360 wired USB controller",
83 .bus_type = 0x0003,
84 .vendor_id = 0x045e,
85 .product_id = 0x028e,
86 .expected = SDL_UDEV_DEVICE_JOYSTICK,
87 /* SYN, KEY, ABS, FF */
88 .ev = { 0x0b, 0x00, 0x20 },
89 /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */
90 .abs = { 0x3f, 0x00, 0x03 },
91 .keys = {
92 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
93 /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */
94 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c,
95 },
96 },
97 {
98 .name = "X-Box One Elite",
99 .bus_type = 0x0003,
100 .vendor_id = 0x045e,
101 .product_id = 0x02e3,
102 .expected = SDL_UDEV_DEVICE_JOYSTICK,
103 /* SYN, KEY, ABS */
104 .ev = { 0x0b },
105 /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */
106 .abs = { 0x3f, 0x00, 0x03 },
107 .keys = {
108 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
109 /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */
110 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c,
111 },
112 },
113 {
114 .name = "X-Box One S via Bluetooth",
115 .bus_type = 0x0005,
116 .vendor_id = 0x045e,
117 .product_id = 0x02e0,
118 .version = 0x1130,
119 .expected = SDL_UDEV_DEVICE_JOYSTICK,
120 /* SYN, KEY, ABS */
121 .ev = { 0x0b },
122 /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */
123 .abs = { 0x3f, 0x00, 0x03 },
124 .keys = {
125 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
126 /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */
127 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c,
128 },
129 },
130 {
131 .name = "X-Box One S wired",
132 .bus_type = 0x0003,
133 .vendor_id = 0x045e,
134 .product_id = 0x02ea,
135 .version = 0x0301,
136 .expected = SDL_UDEV_DEVICE_JOYSTICK,
137 /* SYN, KEY, ABS */
138 .ev = { 0x0b },
139 /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */
140 .abs = { 0x3f, 0x00, 0x03 },
141 .keys = {
142 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
143 /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */
144 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c,
145 },
146 },
147 {
148 .name = "DualShock 4 - gamepad",
149 .bus_type = 0x0003,
150 .vendor_id = 0x054c,
151 .product_id = 0x09cc,
152 .expected = SDL_UDEV_DEVICE_JOYSTICK,
153 /* SYN, KEY, ABS, MSC, FF */
154 /* Some versions only have 0x0b, SYN, KEY, ABS, like the
155 * Bluetooth example below */
156 .ev = { 0x1b, 0x00, 0x20 },
157 /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */
158 .abs = { 0x3f, 0x00, 0x03 },
159 .keys = {
160 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
161 /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE,
162 * THUMBL, THUMBR */
163 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f,
164 },
165 },
166 {
167 .name = "DualShock 4 - gamepad via Bluetooth",
168 .bus_type = 0x0005,
169 .vendor_id = 0x054c,
170 .product_id = 0x09cc,
171 .expected = SDL_UDEV_DEVICE_JOYSTICK,
172 /* SYN, KEY, ABS */
173 .ev = { 0x0b },
174 /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */
175 .abs = { 0x3f, 0x00, 0x03 },
176 .keys = {
177 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
178 /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE,
179 * THUMBL, THUMBR */
180 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f,
181 },
182 },
183 {
184 .name = "DualShock 4 - touchpad",
185 .bus_type = 0x0003,
186 .vendor_id = 0x054c,
187 .product_id = 0x09cc,
188 /* TODO: Should this be MOUSE? That's what it most closely
189 * resembles */
190 .expected = SDL_UDEV_DEVICE_UNKNOWN,
191 /* SYN, KEY, ABS */
192 .ev = { 0x0b },
193 /* X, Y, multitouch */
194 .abs = { 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x02 },
195 .keys = {
196 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
197 /* Left mouse button */
198 /* 0x100 */ 0x00, 0x00, 0x01, 0x00, ZEROx4,
199 /* BTN_TOOL_FINGER and some multitouch stuff */
200 /* 0x140 */ 0x20, 0x24, 0x00, 0x00
201 },
202 /* POINTER, BUTTONPAD */
203 .props = { 0x05 },
204 },
205 {
206 .name = "DualShock 4 - accelerometer",
207 .bus_type = 0x0003,
208 .vendor_id = 0x054c,
209 .product_id = 0x09cc,
210 .expected = SDL_UDEV_DEVICE_ACCELEROMETER,
211 /* SYN, ABS, MSC */
212 .ev = { 0x19 },
213 /* X, Y, Z, RX, RY, RZ */
214 .abs = { 0x3f },
215 /* ACCELEROMETER */
216 .props = { 0x40 },
217 },
218 {
219 .name = "DualShock 4 via USB dongle",
220 .bus_type = 0x0003,
221 .vendor_id = 0x054c,
222 .product_id = 0x0ba0,
223 .version = 0x8111,
224 .expected = SDL_UDEV_DEVICE_JOYSTICK,
225 /* SYN, ABS, KEY */
226 .ev = { 0x0b },
227 /* X, Y, Z, RX, RY, RZ, HAT0X, HAT0Y */
228 .abs = { 0x3f, 0x00, 0x03 },
229 .keys = {
230 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
231 /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE,
232 * THUMBL, THUMBR */
233 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f,
234 },
235 },
236 {
237 .name = "DualShock 3 - gamepad",
238 .bus_type = 0x0003,
239 .vendor_id = 0x054c,
240 .product_id = 0x0268,
241 .expected = SDL_UDEV_DEVICE_JOYSTICK,
242 /* SYN, KEY, ABS, MSC, FF */
243 .ev = { 0x1b, 0x00, 0x20 },
244 /* X, Y, Z, RX, RY, RZ */
245 .abs = { 0x3f },
246 .keys = {
247 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
248 /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE,
249 * THUMBL, THUMBR */
250 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f,
251 /* 0x140 */ ZEROx8,
252 /* 0x180 */ ZEROx8,
253 /* 0x1c0 */ ZEROx8,
254 /* Digital dpad */
255 /* 0x200 */ ZEROx4, 0x0f, 0x00, 0x00, 0x00,
256 },
257 },
258 {
259 .name = "DualShock 3 - accelerometer",
260 .bus_type = 0x0003,
261 .vendor_id = 0x054c,
262 .product_id = 0x0268,
263 .expected = SDL_UDEV_DEVICE_ACCELEROMETER,
264 /* SYN, ABS */
265 .ev = { 0x09 },
266 /* X, Y, Z */
267 .abs = { 0x07 },
268 /* ACCELEROMETER */
269 .props = { 0x40 },
270 },
271 {
272 .name = "Steam Controller - gamepad",
273 .bus_type = 0x0003,
274 .vendor_id = 0x28de,
275 .product_id = 0x1142,
276 .expected = SDL_UDEV_DEVICE_JOYSTICK,
277 /* SYN, KEY, ABS */
278 .ev = { 0x0b },
279 /* X, Y, RX, RY, HAT0X, HAT0Y, HAT2X, HAT2Y */
280 .abs = { 0x1b, 0x00, 0x33 },
281 .keys = {
282 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
283 /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE,
284 * THUMBL, THUMBR, joystick THUMB, joystick THUMB2 */
285 /* 0x100 */ ZEROx4, 0x06, 0x00, 0xdb, 0x7f,
286 /* GEAR_DOWN, GEAR_UP */
287 /* 0x140 */ 0x00, 0x00, 0x03, 0x00, ZEROx4,
288 /* 0x180 */ ZEROx8,
289 /* 0x1c0 */ ZEROx8,
290 /* Digital dpad */
291 /* 0x200 */ ZEROx4, 0x0f, 0x00, 0x00, 0x00,
292 },
293 },
294 {
295 /* Present to support lizard mode, even if no Steam Controller
296 * is connected */
297 .name = "Steam Controller - dongle",
298 .bus_type = 0x0003,
299 .vendor_id = 0x28de,
300 .product_id = 0x1142,
301 .expected = (SDL_UDEV_DEVICE_KEYBOARD
302 | SDL_UDEV_DEVICE_MOUSE),
303 /* SYN, KEY, REL, MSC, LED, REP */
304 .ev = { 0x17, 0x00, 0x12 },
305 /* X, Y, mouse wheel, high-res mouse wheel */
306 .rel = { 0x03, 0x09 },
307 .keys = {
308 /* 0x00 */ 0xfe, 0xff, 0xff, 0xff, FFx4,
309 /* 0x40 */ 0xff, 0xff, 0xcf, 0x01, 0xdf, 0xff, 0x80, 0xe0,
310 /* 0x80 */ ZEROx8,
311 /* 0xc0 */ ZEROx8,
312 /* 0x100 */ 0x00, 0x00, 0x1f, 0x00, ZEROx4,
313 },
314 },
315 {
316 .name = "Guitar Hero for PS3",
317 .bus_type = 0x0003,
318 .vendor_id = 0x12ba,
319 .product_id = 0x0100,
320 .version = 0x0110,
321 .expected = SDL_UDEV_DEVICE_JOYSTICK,
322 /* SYN, KEY, ABS */
323 .ev = { 0x0b },
324 /* X, Y, Z, RZ, HAT0X, HAT0Y */
325 .abs = { 0x27, 0x00, 0x03 },
326 .keys = {
327 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
328 /* ABC, XYZ, TL, TR, TL2, TR2, SELECT, START, MODE */
329 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xff, 0x1f,
330 },
331 },
332 {
333 .name = "G27 Racing Wheel, 0003:046d:c29b v0111",
334 .bus_type = 0x0003,
335 .vendor_id = 0x046d,
336 .product_id = 0xc29b,
337 .version = 0x0111,
338 .expected = SDL_UDEV_DEVICE_JOYSTICK,
339 /* SYN, KEY, ABS */
340 .ev = { 0x0b },
341 /* X, Y, Z, RZ, HAT0X, HAT0Y */
342 .abs = { 0x27, 0x00, 0x03 },
343 .keys = {
344 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
345 /* 16 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE,
346 * BASE2..BASE6, unregistered event codes 0x12c-0x12e, DEAD */
347 /* 0x100 */ ZEROx4, 0xff, 0xff, 0x00, 0x00,
348 /* 0x140 */ ZEROx8,
349 /* 0x180 */ ZEROx8,
350 /* 0x1c0 */ ZEROx8,
351 /* 0x200 */ ZEROx8,
352 /* 0x240 */ ZEROx8,
353 /* 0x280 */ ZEROx8,
354 /* TRIGGER_HAPPY1..TRIGGER_HAPPY7 */
355 /* 0x2c0 */ 0x7f,
356 },
357 },
358 {
359 .name = "Logitech Driving Force, 0003:046d:c294 v0100",
360 .bus_type = 0x0003,
361 .vendor_id = 0x046d,
362 .product_id = 0xc294,
363 .version = 0x0100,
364 .expected = SDL_UDEV_DEVICE_JOYSTICK,
365 /* SYN, KEY, ABS */
366 .ev = { 0x0b },
367 /* X, Y, RZ, HAT0X, HAT0Y */
368 .abs = { 0x23, 0x00, 0x03 },
369 .keys = {
370 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
371 /* 12 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE,
372 * BASE2..BASE6 */
373 /* 0x100 */ ZEROx4, 0xff, 0x0f, 0x00, 0x00,
374 },
375 },
376 {
377 .name = "Logitech Dual Action",
378 .bus_type = 0x0003,
379 .vendor_id = 0x046d,
380 .product_id = 0xc216,
381 .version = 0x0110,
382 /* Logitech RumblePad 2 USB, 0003:046d:c218 v0110, is the same
383 * except for having force feedback, which we don't use in our
384 * heuristic */
385 /* Jess Tech GGE909 PC Recoil Pad, 0003:0f30:010b v0110, is the same */
386 /* 8BitDo SNES30, 0003:2dc8:ab20 v0110, is the same */
387 .expected = SDL_UDEV_DEVICE_JOYSTICK,
388 /* SYN, KEY, ABS */
389 .ev = { 0x0b },
390 /* X, Y, Z, RZ, HAT0X, HAT0Y */
391 .abs = { 0x27, 0x00, 0x03 },
392 .keys = {
393 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
394 /* 12 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE,
395 * BASE2..BASE6 */
396 /* 0x100 */ ZEROx4, 0xff, 0x0f, 0x00, 0x00,
397 },
398 },
399 {
400 .name = "Saitek ST290 Pro flight stick",
401 .bus_type = 0x0003,
402 .vendor_id = 0x06a3,
403 .product_id = 0x0160, /* 0x0460 seems to be the same */
404 .version = 0x0100,
405 .expected = SDL_UDEV_DEVICE_JOYSTICK,
406 /* SYN, KEY, ABS, MSC */
407 .ev = { 0x1b },
408 /* X, Y, Z, RZ, HAT0X, HAT0Y */
409 .abs = { 0x27, 0x00, 0x03 },
410 .keys = {
411 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
412 /* TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE */
413 /* 0x100 */ ZEROx4, 0x3f, 0x00, 0x00, 0x00,
414 },
415 },
416 {
417 .name = "Saitek X52 Pro Flight Control System",
418 .bus_type = 0x0003,
419 .vendor_id = 0x06a3,
420 .product_id = 0x0762,
421 .version = 0x0111,
422 .expected = SDL_UDEV_DEVICE_JOYSTICK,
423 .ev = { 0x0b },
424 /* XYZ, RXYZ, throttle, hat0, MISC, unregistered event code 0x29 */
425 .abs = { 0x7f, 0x00, 0x03, 0x00, 0x00, 0x03 },
426 .keys = {
427 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
428 /* 16 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE,
429 * BASE2..BASE6, unregistered event codes 0x12c-0x12e, DEAD */
430 /* 0x100 */ ZEROx4, 0xff, 0xff, 0x00, 0x00,
431 /* 0x140 */ ZEROx8,
432 /* 0x180 */ ZEROx8,
433 /* 0x1c0 */ ZEROx8,
434 /* 0x200 */ ZEROx8,
435 /* 0x240 */ ZEROx8,
436 /* 0x280 */ ZEROx8,
437 /* TRIGGER_HAPPY1..TRIGGER_HAPPY23 */
438 /* 0x2c0 */ 0xff, 0xff, 0x7f,
439 },
440 },
441 {
442 .name = "Logitech Extreme 3D",
443 .bus_type = 0x0003,
444 .vendor_id = 0x046d,
445 .product_id = 0xc215,
446 .version = 0x0110,
447 .expected = SDL_UDEV_DEVICE_JOYSTICK,
448 /* SYN, KEY, ABS, MSC */
449 .ev = { 0x0b },
450 /* X, Y, RZ, throttle, hat 0 */
451 .abs = { 0x63, 0x00, 0x03 },
452 .keys = {
453 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
454 /* 12 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE,
455 * BASE2..BASE6 */
456 /* 0x100 */ ZEROx4, 0xff, 0x0f, 0x00, 0x00,
457 },
458 },
459 {
460 .name = "Hori Real Arcade Pro VX-SA",
461 .bus_type = 0x0003,
462 .vendor_id = 0x24c6,
463 .product_id = 0x5501,
464 .version = 0x0533,
465 .expected = SDL_UDEV_DEVICE_JOYSTICK,
466 /* SYN, KEY, ABS */
467 .ev = { 0x0b },
468 /* X, Y, Z, RX, RY, RZ, hat 0 */
469 .abs = { 0x3f, 0x00, 0x03 },
470 .keys = {
471 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
472 /* A, B, X, Y, TL, TR, SELECT, START, MODE, THUMBL, THUMBR */
473 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7c,
474 },
475 },
476 {
477 .name = "Switch Pro Controller via Bluetooth",
478 .bus_type = 0x0005,
479 .vendor_id = 0x057e,
480 .product_id = 0x2009,
481 .version = 0x0001,
482 .expected = SDL_UDEV_DEVICE_JOYSTICK,
483 /* SYN, KEY, ABS */
484 .ev = { 0x0b },
485 /* X, Y, RX, RY, hat 0 */
486 .abs = { 0x1b, 0x00, 0x03 },
487 .keys = {
488 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
489 /* 16 buttons: TRIGGER, THUMB, THUMB2, TOP, TOP2, PINKIE, BASE,
490 * BASE2..BASE6, unregistered event codes 0x12c-0x12e, DEAD */
491 /* 0x100 */ ZEROx4, 0xff, 0xff, 0x00, 0x00,
492 /* 0x140 */ ZEROx8,
493 /* 0x180 */ ZEROx8,
494 /* 0x1c0 */ ZEROx8,
495 /* 0x200 */ ZEROx8,
496 /* 0x240 */ ZEROx8,
497 /* 0x280 */ ZEROx8,
498 /* TRIGGER_HAPPY1..TRIGGER_HAPPY2 */
499 /* 0x2c0 */ 0x03,
500 },
501 },
502 {
503 .name = "Switch Pro Controller via USB",
504 .bus_type = 0x0003,
505 .vendor_id = 0x057e,
506 .product_id = 0x2009,
507 .version = 0x0111,
508 .expected = SDL_UDEV_DEVICE_JOYSTICK,
509 /* SYN, KEY, ABS */
510 .ev = { 0x0b },
511 /* X, Y, Z, RZ, HAT0X, HAT0Y */
512 .abs = { 0x27, 0x00, 0x03 },
513 .keys = {
514 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
515 },
516 },
517 {
518 .name = "Thrustmaster Racing Wheel FFB",
519 /* Mad Catz FightStick TE S+ PS4, 0003:0738:8384:0111 v0111
520 * (aka Street Fighter V Arcade FightStick TES+)
521 * is functionally the same */
522 .bus_type = 0x0003,
523 .vendor_id = 0x044f,
524 .product_id = 0xb66d,
525 .version = 0x0110,
526 .expected = SDL_UDEV_DEVICE_JOYSTICK,
527 .ev = { 0x0b },
528 /* XYZ, RXYZ, hat 0 */
529 .abs = { 0x3f, 0x00, 0x03 },
530 .keys = {
531 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
532 /* ABC, XYZ, TL, TR, TL2, TR2, SELECT, START, MODE,
533 * THUMBL */
534 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xff, 0x3f,
535 },
536 },
537 {
538 .name = "Thrustmaster T.Flight Hotas X",
539 .bus_type = 0x0003,
540 .vendor_id = 0x044f,
541 .product_id = 0xb108,
542 .version = 0x0100,
543 .expected = SDL_UDEV_DEVICE_JOYSTICK,
544 .ev = { 0x0b },
545 /* XYZ, RZ, throttle, hat 0 */
546 .abs = { 0x67, 0x00, 0x03 },
547 .keys = {
548 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
549 /* trigger, thumb, thumb2, top, top2, pinkie, base,
550 * base2..base6 */
551 /* 0x100 */ ZEROx4, 0xff, 0x0f
552 },
553 },
554 {
555 .name = "8BitDo N30 Pro 2",
556 .bus_type = 0x0003,
557 .vendor_id = 0x2dc8,
558 .product_id = 0x9015,
559 .version = 0x0111,
560 /* 8BitDo NES30 Pro, 0003:2dc8:9001 v0111, is the same */
561 .expected = SDL_UDEV_DEVICE_JOYSTICK,
562 .ev = { 0x0b },
563 /* XYZ, RZ, gas, brake, hat0 */
564 .abs = { 0x27, 0x06, 0x03 },
565 .keys = {
566 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
567 /* ABC, XYZ, TL, TR, TL2, TR2, select, start, mode, thumbl,
568 * thumbr */
569 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xff, 0x7f,
570 },
571 },
572 {
573 .name = "Retro Power SNES-style controller, 0003:0079:0011 v0110",
574 .bus_type = 0x0003,
575 .vendor_id = 0x0079,
576 .product_id = 0x0011,
577 .version = 0x0110,
578 .expected = SDL_UDEV_DEVICE_JOYSTICK,
579 .ev = { 0x0b },
580 /* X, Y */
581 .abs = { 0x03 },
582 .keys = {
583 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
584 /* trigger, thumb, thumb2, top, top2, pinkie, base,
585 * base2..base4 */
586 /* 0x100 */ ZEROx4, 0xff, 0x03, 0x00, 0x00,
587 },
588 },
589 {
590 .name = "Wiimote - buttons",
591 .bus_type = 0x0005,
592 .vendor_id = 0x057e,
593 .product_id = 0x0306,
594 .version = 0x8600,
595 /* This one is a bit weird because some of the buttons are mapped
596 * to the arrow, page up and page down keys, so it's a joystick
597 * with a subset of a keyboard attached. */
598 /* TODO: Should this be JOYSTICK, or even JOYSTICK|KEYBOARD? */
599 .expected = SDL_UDEV_DEVICE_UNKNOWN,
600 /* SYN, KEY */
601 .ev = { 0x03 },
602 .keys = {
603 /* 0x00 */ ZEROx8,
604 /* left, right, up down */
605 /* 0x40 */ ZEROx4, 0x80, 0x16, 0x00, 0x00,
606 /* 0x80 */ ZEROx8,
607 /* 0xc0 */ ZEROx8,
608 /* BTN_1, BTN_2, BTN_A, BTN_B, BTN_MODE */
609 /* 0x100 */ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10,
610 /* 0x140 */ ZEROx8,
611 /* next, previous */
612 /* 0x180 */ 0x00, 0x00, 0x80, 0x10, ZEROx4,
613 },
614 },
615 {
616 .name = "Wiimote - Motion Plus or accelerometer",
617 .bus_type = 0x0005,
618 .vendor_id = 0x057e,
619 .product_id = 0x0306,
620 .version = 0x8600,
621 .expected = SDL_UDEV_DEVICE_ACCELEROMETER,
622 /* SYN, ABS */
623 .ev = { 0x09 },
624 /* RX, RY, RZ */
625 .abs = { 0x38 },
626 },
627 {
628 .name = "Wiimote - IR positioning",
629 .bus_type = 0x0005,
630 .vendor_id = 0x057e,
631 .product_id = 0x0306,
632 .version = 0x8600,
633 .expected = SDL_UDEV_DEVICE_UNKNOWN,
634 /* SYN, ABS */
635 .ev = { 0x09 },
636 /* HAT0 to HAT3 */
637 .abs = { 0x00, 0x1f },
638 },
639 {
640 .name = "Wiimote - Nunchuck",
641 .bus_type = 0x0005,
642 .vendor_id = 0x057e,
643 .product_id = 0x0306,
644 .version = 0x8600,
645 /* TODO: Should this be JOYSTICK? It has one stick and two buttons */
646 .expected = SDL_UDEV_DEVICE_UNKNOWN,
647 /* SYN, KEY, ABS */
648 .ev = { 0x0b },
649 /* RX, RY, RZ, hat 0 */
650 .abs = { 0x38, 0x00, 0x03 },
651 .keys = {
652 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
653 /* C and Z buttons */
654 /* 0x100 */ ZEROx4, 0x00, 0x00, 0x24, 0x00,
655 },
656 },
657 {
658 /* Flags guessed from kernel source code */
659 .name = "Wiimote - Classic Controller",
660 /* TODO: Should this be JOYSTICK, or maybe JOYSTICK|KEYBOARD?
661 * It's unusual in the same ways as the Wiimote */
662 .expected = SDL_UDEV_DEVICE_UNKNOWN,
663 /* SYN, KEY, ABS */
664 .ev = { 0x0b },
665 /* Hat 1-3 */
666 .abs = { 0x00, 0x1c },
667 .keys = {
668 /* 0x00 */ ZEROx8,
669 /* left, right, up down */
670 /* 0x40 */ ZEROx4, 0x80, 0x16, 0x00, 0x00,
671 /* 0x80 */ ZEROx8,
672 /* 0xc0 */ ZEROx8,
673 /* A, B, X, Y, MODE, TL, TL2, TR, TR2 */
674 /* 0x100 */ ZEROx4, 0x00, 0x13, 0xdb, 0x10,
675 /* 0x140 */ ZEROx8,
676 /* next, previous */
677 /* 0x180 */ 0x00, 0x00, 0x80, 0x10, ZEROx4,
678 },
679 },
680 {
681 /* Flags guessed from kernel source code */
682 .name = "Wiimote - Balance Board",
683 /* TODO: Should this be JOYSTICK? */
684 .expected = SDL_UDEV_DEVICE_UNKNOWN,
685 /* SYN, KEY, ABS */
686 .ev = { 0x0b },
687 /* Hat 0-1 */
688 .abs = { 0x00, 0x0f },
689 .keys = {
690 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
691 /* A */
692 /* 0x100 */ ZEROx4, 0x00, 0x00, 0x01, 0x00,
693 },
694 },
695 {
696 /* Flags guessed from kernel source code */
697 .name = "Wiimote - Wii U Pro Controller",
698 .expected = SDL_UDEV_DEVICE_JOYSTICK,
699 /* SYN, KEY, ABS */
700 .ev = { 0x0b },
701 /* X, Y, RX, RY */
702 .abs = { 0x1b },
703 .keys = {
704 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
705 /* A, B, X, Y, TL, TR, TL2, TR2, SELECT, START, MODE,
706 * THUMBL, THUMBR */
707 /* 0x100 */ ZEROx4, 0x00, 0x00, 0xdb, 0x7f,
708 /* 0x140 */ ZEROx8,
709 /* 0x180 */ ZEROx8,
710 /* 0x1c0 */ ZEROx8,
711 /* Digital dpad */
712 /* 0x200 */ ZEROx4, 0x0f, 0x00, 0x00, 0x00,
713 },
714 },
715 {
716 .name = "Synaptics TM3381-002 (Thinkpad X280 trackpad)",
717 .bus_type = 0x001d, /* BUS_RMI */
718 .vendor_id = 0x06cb,
719 .product_id = 0x0000,
720 .version = 0x0000,
721 /* TODO: Should this be MOUSE? That's what it most closely
722 * resembles */
723 .expected = SDL_UDEV_DEVICE_UNKNOWN,
724 /* SYN, KEY, ABS */
725 .ev = { 0x0b },
726 /* X, Y, pressure, multitouch */
727 .abs = { 0x03, 0x00, 0x00, 0x01, 0x00, 0x80, 0xf3, 0x06 },
728 .keys = {
729 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
730 /* Left mouse button */
731 /* 0x100 */ 0x00, 0x00, 0x01, 0x00, ZEROx4,
732 /* BTN_TOOL_FINGER and some multitouch gestures */
733 /* 0x140 */ 0x20, 0xe5
734 },
735 /* POINTER, BUTTONPAD */
736 .props = { 0x05 },
737 },
738 {
739 .name = "TPPS/2 Elan TrackPoint (Thinkpad X280)",
740 .bus_type = 0x0011, /* BUS_I8042 */
741 .vendor_id = 0x0002,
742 .product_id = 0x000a,
743 .version = 0x0000,
744 .expected = SDL_UDEV_DEVICE_MOUSE,
745 /* SYN, KEY, REL */
746 .ev = { 0x07 },
747 /* X, Y */
748 .rel = { 0x03 },
749 .keys = {
750 /* 0x00-0xff */ ZEROx8, ZEROx8, ZEROx8, ZEROx8,
751 /* Left, middle, right mouse buttons */
752 /* 0x100 */ 0x00, 0x00, 0x07,
753 },
754 /* POINTER, POINTING_STICK */
755 .props = { 0x21 },
756 },
757 {
758 .name = "Thinkpad ACPI buttons",
759 .expected = SDL_UDEV_DEVICE_UNKNOWN,
760 /* SYN, KEY, MSC, SW */
761 .ev = { 0x33 },
762 .keys = {
763 /* 0x00 */ ZEROx8,
764 /* 0x40 */ ZEROx4, 0x00, 0x00, 0x0e, 0x01,
765 /* 0x80 */ 0x00, 0x50, 0x11, 0x51, 0x00, 0x28, 0x00, 0xc0,
766 /* 0xc0 */ 0x04, 0x20, 0x10, 0x02, 0x1b, 0x70, 0x01, 0x00,
767 /* 0x100 */ ZEROx8,
768 /* 0x140 */ ZEROx4, 0x00, 0x00, 0x50, 0x00,
769 /* 0x180 */ ZEROx8,
770 /* 0x1c0 */ 0x00, 0x00, 0x04, 0x18, ZEROx4,
771 /* 0x200 */ ZEROx8,
772 /* 0x240 */ 0x40, 0x00, 0x01, 0x00, ZEROx4,
773 },
774 },
775 {
776 .name = "PC speaker",
777 .bus_type = 0x0010, /* BUS_ISA */
778 .vendor_id = 0x001f,
779 .product_id = 0x0001,
780 .version = 0x0100,
781 .expected = SDL_UDEV_DEVICE_UNKNOWN,
782 /* SYN, SND */
783 .ev = { 0x01, 0x00, 0x04 },
784 },
785 {
786 .name = "ALSA headphone detection, etc.",
787 .bus_type = 0x0000,
788 .vendor_id = 0x0000,
789 .product_id = 0x0000,
790 .version = 0x0000,
791 .expected = SDL_UDEV_DEVICE_UNKNOWN,
792 /* SYN, SW */
793 .ev = { 0x21 },
794 },
795 {
796 /* Assumed to be a reasonably typical i8042 (PC AT) keyboard */
797 .name = "Thinkpad T520 and X280 keyboards",
798 .bus_type = 0x0011, /* BUS_I8042 */
799 .vendor_id = 0x0001,
800 .product_id = 0x0001,
801 .version = 0xab54,
802 .expected = SDL_UDEV_DEVICE_KEYBOARD,
803 /* SYN, KEY, MSC, LED, REP */
804 .ev = { 0x13, 0x00, 0x12 },
805 .keys = {
806 /* 0x00 */ 0xfe, 0xff, 0xff, 0xff, FFx4,
807 /* 0x40 */ 0xff, 0xff, 0xef, 0xff, 0xdf, 0xff, 0xff, 0xfe,
808 /* 0x80 */ 0x01, 0xd0, 0x00, 0xf8, 0x78, 0x30, 0x80, 0x03,
809 /* 0xc0 */ 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00,
810 },
811 },
812 {
813 .name = "Thinkpad X280 sleep button",
814 .bus_type = 0x0019, /* BUS_HOST */
815 .vendor_id = 0x0000,
816 .product_id = 0x0003,
817 .version = 0x0000,
818 .expected = SDL_UDEV_DEVICE_UNKNOWN,
819 /* SYN, KEY */
820 .ev = { 0x03 },
821 .keys = {
822 /* 0x00 */ ZEROx8,
823 /* 0x40 */ ZEROx8,
824 /* KEY_SLEEP */
825 /* 0x80 */ 0x00, 0x40,
826 },
827 },
828 {
829 .name = "Thinkpad X280 lid switch",
830 .bus_type = 0x0019, /* BUS_HOST */
831 .vendor_id = 0x0000,
832 .product_id = 0x0005,
833 .version = 0x0000,
834 .expected = SDL_UDEV_DEVICE_UNKNOWN,
835 /* SYN, SW */
836 .ev = { 0x21 },
837 },
838 {
839 .name = "Thinkpad X280 power button",
840 .bus_type = 0x0019, /* BUS_HOST */
841 .vendor_id = 0x0000,
842 .product_id = 0x0001,
843 .version = 0x0000,
844 .expected = SDL_UDEV_DEVICE_UNKNOWN,
845 /* SYN, KEY */
846 .ev = { 0x03 },
847 .keys = {
848 /* 0x00 */ ZEROx8,
849 /* KEY_POWER */
850 /* 0x40 */ ZEROx4, 0x00, 0x00, 0x10, 0x00,
851 },
852 },
853 {
854 .name = "Thinkpad X280 video bus",
855 .bus_type = 0x0019, /* BUS_HOST */
856 .vendor_id = 0x0000,
857 .product_id = 0x0006,
858 .version = 0x0000,
859 .expected = SDL_UDEV_DEVICE_UNKNOWN,
860 /* SYN, KEY */
861 .ev = { 0x03 },
862 .keys = {
863 /* 0x00 */ ZEROx8,
864 /* 0x40 */ ZEROx8,
865 /* 0x80 */ ZEROx8,
866 /* brightness control, video mode, display off */
867 /* 0xc0 */ ZEROx4, 0x0b, 0x00, 0x3e, 0x00,
868 },
869 },
870 {
871 .name = "Thinkpad X280 extra buttons",
872 .bus_type = 0x0019, /* BUS_HOST */
873 .vendor_id = 0x17aa,
874 .product_id = 0x5054,
875 .version = 0x4101,
876 .expected = SDL_UDEV_DEVICE_UNKNOWN,
877 /* SYN, KEY */
878 .ev = { 0x03 },
879 .keys = {
880 /* 0x00 */ ZEROx8,
881 /* 0x40 */ ZEROx4, 0x00, 0x00, 0x0e, 0x01,
882 /* 0x80 */ 0x00, 0x50, 0x11, 0x51, 0x00, 0x28, 0x00, 0xc0,
883 /* 0xc0 */ 0x04, 0x20, 0x10, 0x02, 0x1b, 0x70, 0x01, 0x00,
884 /* 0x100 */ ZEROx8,
885 /* 0x140 */ ZEROx4, 0x00, 0x00, 0x50, 0x00,
886 /* 0x180 */ ZEROx8,
887 /* 0x1c0 */ 0x00, 0x00, 0x04, 0x18, ZEROx4,
888 /* 0x200 */ ZEROx8,
889 /* 0x240 */ 0x40, 0x00, 0x01, 0x00, ZEROx4,
890 },
891 },
892 {
893 .name = "Thinkpad USB keyboard with Trackpoint - keyboard",
894 .bus_type = 0x0003,
895 .vendor_id = 0x17ef,
896 .product_id = 0x6009,
897 .expected = SDL_UDEV_DEVICE_KEYBOARD,
898 /* SYN, KEY, MSC, LED, REP */
899 .ev = { 0x13, 0x00, 0x12 },
900 .keys = {
901 /* 0x00 */ 0xfe, 0xff, 0xff, 0xff, FFx4,
902 /* 0x40 */ 0xff, 0xff, 0xef, 0xff, 0xdf, 0xff, 0xbe, 0xfe,
903 /* 0x80 */ 0xff, 0x57, 0x40, 0xc1, 0x7a, 0x20, 0x9f, 0xff,
904 /* 0xc0 */ 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
905 },
906 },
907 {
908 .name = "Thinkpad USB keyboard with Trackpoint - Trackpoint",
909 .bus_type = 0x0003,
910 .vendor_id = 0x17ef,
911 .product_id = 0x6009,
912 /* For some reason the special keys like mute and wlan toggle
913 * show up here instead of, or in addition to, as part of
914 * the keyboard - so udev reports this as having keys too.
915 * SDL currently doesn't. */
916 .expected = SDL_UDEV_DEVICE_MOUSE,
917 /* SYN, KEY, REL, MSC, LED */
918 .ev = { 0x17, 0x00, 0x02 },
919 /* X, Y */
920 .rel = { 0x03 },
921 .keys = {
922 /* 0x00 */ ZEROx8,
923 /* 0x40 */ ZEROx4, 0x00, 0x00, 0x1e, 0x00,
924 /* 0x80 */ 0x00, 0xcc, 0x11, 0x01, 0x78, 0x40, 0x00, 0xc0,
925 /* 0xc0 */ 0x00, 0x20, 0x10, 0x00, 0x0b, 0x50, 0x00, 0x00,
926 /* Mouse buttons: left, right, middle, "task" */
927 /* 0x100 */ 0x00, 0x00, 0x87, 0x68, ZEROx4,
928 /* 0x140 */ ZEROx4, 0x00, 0x00, 0x10, 0x00,
929 /* 0x180 */ ZEROx4, 0x00, 0x00, 0x40, 0x00,
930 },
931 },
932 {
933 .name = "No information",
934 .expected = SDL_UDEV_DEVICE_UNKNOWN,
935 }
936 };
937
938 #if ULONG_MAX == 0xFFFFFFFFUL
939 # define SwapLongLE(X) SDL_SwapLE32(X)
940 #else
941 /* assume 64-bit */
942 # define SwapLongLE(X) SDL_SwapLE64(X)
943 #endif
944
945 static int
run_test(void)946 run_test(void)
947 {
948 int success = 1;
949 size_t i;
950
951 for (i = 0; i < SDL_arraysize(guess_tests); i++) {
952 const GuessTest *t = &guess_tests[i];
953 size_t j;
954 int actual;
955 struct {
956 unsigned long ev[NBITS(EV_MAX)];
957 unsigned long abs[NBITS(ABS_MAX)];
958 unsigned long keys[NBITS(KEY_MAX)];
959 unsigned long rel[NBITS(REL_MAX)];
960 } caps = {};
961
962 printf("%s...\n", t->name);
963
964 memset(&caps, '\0', sizeof(caps));
965 memcpy(caps.ev, t->ev, sizeof(t->ev));
966 memcpy(caps.keys, t->keys, sizeof(t->keys));
967 memcpy(caps.abs, t->abs, sizeof(t->abs));
968 memcpy(caps.rel, t->rel, sizeof(t->rel));
969
970 for (j = 0; j < SDL_arraysize(caps.ev); j++) {
971 caps.ev[j] = SwapLongLE(caps.ev[j]);
972 }
973
974 for (j = 0; j < SDL_arraysize(caps.keys); j++) {
975 caps.keys[j] = SwapLongLE(caps.keys[j]);
976 }
977
978 for (j = 0; j < SDL_arraysize(caps.abs); j++) {
979 caps.abs[j] = SwapLongLE(caps.abs[j]);
980 }
981
982 for (j = 0; j < SDL_arraysize(caps.rel); j++) {
983 caps.rel[j] = SwapLongLE(caps.rel[j]);
984 }
985
986 actual = SDL_EVDEV_GuessDeviceClass(caps.ev, caps.abs, caps.keys,
987 caps.rel);
988
989 if (actual == t->expected) {
990 printf("\tOK\n");
991 }
992 else {
993 printf("\tExpected 0x%08x\n", t->expected);
994
995 for (j = 0; device_classes[j].code != 0; j++) {
996 if (t->expected & device_classes[j].code) {
997 printf("\t\t%s\n", device_classes[j].name);
998 }
999 }
1000
1001 printf("\tGot 0x%08x\n", actual);
1002
1003 for (j = 0; device_classes[j].code != 0; j++) {
1004 if (actual & device_classes[j].code) {
1005 printf("\t\t%s\n", device_classes[j].name);
1006 }
1007 }
1008
1009 success = 0;
1010 }
1011 }
1012
1013 return success;
1014 }
1015
1016 #else /* !(HAVE_LIBUDEV_H || defined(SDL_JOYSTICK_LINUX)) */
1017
1018 static int
run_test(void)1019 run_test(void)
1020 {
1021 printf("SDL compiled without evdev capability check.\n");
1022 return 0;
1023 }
1024
1025 #endif
1026
1027 int
main(int argc,char * argv[])1028 main(int argc, char *argv[])
1029 {
1030 return run_test() ? 0 : 1;
1031 }
1032