1 /// Translated from C to D
2 module glfw3.linux_joystick;
3 
4 extern(C): @nogc: nothrow: __gshared:
5 
6 //========================================================================
7 // GLFW 3.3 Linux - www.glfw.org
8 //------------------------------------------------------------------------
9 // Copyright (c) 2002-2006 Marcus Geelnard
10 // Copyright (c) 2006-2017 Camilla Löwy <elmindreda@glfw.org>
11 //
12 // This software is provided 'as-is', without any express or implied
13 // warranty. In no event will the authors be held liable for any damages
14 // arising from the use of this software.
15 //
16 // Permission is granted to anyone to use this software for any purpose,
17 // including commercial applications, and to alter it and redistribute it
18 // freely, subject to the following restrictions:
19 //
20 // 1. The origin of this software must not be misrepresented; you must not
21 //    claim that you wrote the original software. If you use this software
22 //    in a product, an acknowledgment in the product documentation would
23 //    be appreciated but is not required.
24 //
25 // 2. Altered source versions must be plainly marked as such, and must not
26 //    be misrepresented as being the original software.
27 //
28 // 3. This notice may not be removed or altered from any source
29 //    distribution.
30 //
31 //========================================================================
32 // It is fine to use C99 in this file because it will not be built with VS
33 //========================================================================
34 
35 import glfw3.internal;
36 
37 import core.sys.posix.sys.types;
38 import core.sys.posix.sys.stat;
39 import core.sys.linux.sys.inotify;
40 import core.sys.posix.fcntl;
41 import core.stdc.errno;
42 import core.sys.posix.dirent;
43 import core.stdc.stdio;
44 import core.stdc.stdlib;
45 import core.stdc.string;
46 import core.sys.posix.unistd;
47 
48 public import glfw3.linuxinput;
49 import core.stdc.limits;
50 
51 import core.sys.posix.sys.ioctl: ioctl;
52 
53 mixin template _GLFW_PLATFORM_JOYSTICK_STATE() {        _GLFWjoystickLinux linjs; }
54 mixin template _GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE() {_GLFWlibraryLinux  linjs; }
55 
56 enum _GLFW_PLATFORM_MAPPING_NAME = "Linux";
57 
58 // Linux-specific joystick data
59 //
60 struct _GLFWjoystickLinux {
61     int fd;
62     char[PATH_MAX] path = '\0';
63     int[KEY_CNT - BTN_MISC] keyMap;
64     int[ABS_CNT] absMap;
65     input_absinfo[ABS_CNT] absInfo;
66     int[2][4] hats;
67 }
68 
69 // Linux-specific joystick API data
70 //
71 struct _GLFWlibraryLinux {
72     int inotify;
73     int watch;
74     version(none) {
75         regex_t regex;
76     }
77     GLFWbool dropped;
78 }
79 
80 version(none) { // < v2.6.39 kernel headers
81     // Workaround for CentOS-6, which is supported till 2020-11-30, but still on v2.6.32
82     enum SYN_DROPPED = 3;
83 }
84 
85 // Apply an EV_KEY event to the specified joystick
86 //
87 private void handleKeyEvent(_GLFWjoystick* js, int code, int value) {
88     _glfwInputJoystickButton(js,
89                              js.linjs.keyMap[code - BTN_MISC],
90                              value ? GLFW_PRESS : GLFW_RELEASE);
91 }
92 
93 // Apply an EV_ABS event to the specified joystick
94 //
95 static void handleAbsEvent(_GLFWjoystick* js, int code, int value) {
96     const(int) index = js.linjs.absMap[code];
97 
98     if (code >= ABS_HAT0X && code <= ABS_HAT3Y)
99     {
100         static const(char)[3][3] stateMap = [
101             [ GLFW_HAT_CENTERED, GLFW_HAT_UP,       GLFW_HAT_DOWN ],
102             [ GLFW_HAT_LEFT,     GLFW_HAT_LEFT_UP,  GLFW_HAT_LEFT_DOWN ],
103             [ GLFW_HAT_RIGHT,    GLFW_HAT_RIGHT_UP, GLFW_HAT_RIGHT_DOWN ],
104         ];
105 
106         const(int) hat = (code - ABS_HAT0X) / 2;
107         const(int) axis = (code - ABS_HAT0X) % 2;
108         int* state = js.linjs.hats[hat].ptr;
109 
110         // NOTE: Looking at several input drivers, it seems all hat events use
111         //       -1 for left / up, 0 for centered and 1 for right / down
112         if (value == 0)
113             state[axis] = 0;
114         else if (value < 0)
115             state[axis] = 1;
116         else if (value > 0)
117             state[axis] = 2;
118 
119         _glfwInputJoystickHat(js, index, stateMap[state[0]][state[1]]);
120     }
121     else
122     {
123         input_absinfo* info = &js.linjs.absInfo[code];
124         float normalized = value;
125 
126         const(int) range = info.maximum - info.minimum;
127         if (range)
128         {
129             // Normalize to 0.0 -> 1.0
130             normalized = (normalized - info.minimum) / range;
131             // Normalize to -1.0 -> 1.0
132             normalized = normalized * 2.0f - 1.0f;
133         }
134 
135         _glfwInputJoystickAxis(js, index, normalized);
136     }
137 }
138 
139 // Poll state of absolute axes
140 //
141 private void pollAbsState(_GLFWjoystick* js) {
142     for (int code = 0;  code < ABS_CNT;  code++)
143     {
144         if (js.linjs.absMap[code] < 0)
145             continue;
146 
147         input_absinfo* info = &js.linjs.absInfo[code];
148 
149         if (ioctl(js.linjs.fd, EVIOCGABS(code), info) < 0)
150             continue;
151 
152         handleAbsEvent(js, code, info.value);
153     }
154 }
155 
156 //  #define isBitSet(bit, arr) (arr[(bit) / 8] & (1 << ((bit) % 8)))
157 enum string isBitSet(string bit, string arr) = ` (`~arr~`[(`~bit~`) / 8] & (1 << ((`~bit~`) % 8)))`;
158 
159 // Attempt to open the specified joystick device
160 //
161 private GLFWbool openJoystickDevice(const(char)* path) {
162     for (int jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
163     {
164         if (!_glfw.joysticks[jid].present)
165             continue;
166         if (strcmp(_glfw.joysticks[jid].linjs.path.ptr, path) == 0)
167             return GLFW_FALSE;
168     }
169 
170     _GLFWjoystickLinux linjs = _GLFWjoystickLinux(0);
171     linjs.fd = open(path, O_RDONLY | O_NONBLOCK);
172     if (linjs.fd == -1)
173         return GLFW_FALSE;
174 
175     char[(EV_CNT + 7) / 8] evBits = '\0';
176     char[(KEY_CNT + 7) / 8] keyBits = '\0';
177     char[(ABS_CNT + 7) / 8] absBits = '\0';
178     input_id id;
179 
180     if (ioctl(linjs.fd, EVIOCGBIT!(typeof(evBits) )(     0), evBits.ptr) < 0 ||
181         ioctl(linjs.fd, EVIOCGBIT!(typeof(keyBits))(EV_KEY), keyBits.ptr) < 0 ||
182         ioctl(linjs.fd, EVIOCGBIT!(typeof(absBits))(EV_ABS), absBits.ptr) < 0 ||
183         ioctl(linjs.fd, EVIOCGID, &id) < 0)
184     {
185         _glfwInputError(GLFW_PLATFORM_ERROR,
186                         "Linux: Failed to query input device: %s",
187                         strerror(errno));
188         close(linjs.fd);
189         return GLFW_FALSE;
190     }
191 
192     // Ensure this device supports the events expected of a joystick
193     if (!mixin(isBitSet!("EV_KEY", "evBits")) || !mixin(isBitSet!("EV_ABS", "evBits")))
194     {
195         close(linjs.fd);
196         return GLFW_FALSE;
197     }
198 
199     char[256] name = "";
200 
201     if (ioctl(linjs.fd, EVIOCGNAME!(typeof(name))(), name.ptr) < 0)
202         strncpy(name.ptr, "Unknown", name.length);
203 
204     char[33] guid = "";
205 
206     // Generate a joystick GUID that matches the SDL 2.0.5+ one
207     if (id.vendor && id.product && id.version_)
208     {
209         sprintf(guid.ptr, "%02x%02x0000%02x%02x0000%02x%02x0000%02x%02x0000",
210                 id.bustype & 0xff, id.bustype >> 8,
211                 id.vendor & 0xff,  id.vendor >> 8,
212                 id.product & 0xff, id.product >> 8,
213                 id.version_ & 0xff, id.version_ >> 8);
214     }
215     else
216     {
217         sprintf(guid.ptr, "%02x%02x0000%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x00",
218                 id.bustype & 0xff, id.bustype >> 8,
219                 name[0], name[1], name[2], name[3],
220                 name[4], name[5], name[6], name[7],
221                 name[8], name[9], name[10]);
222     }
223 
224     int axisCount = 0;int buttonCount = 0;int hatCount = 0;
225 
226     for (int code = BTN_MISC;  code < KEY_CNT;  code++)
227     {
228         if (!mixin(isBitSet!("code", "keyBits")))
229             continue;
230 
231         linjs.keyMap[code - BTN_MISC] = buttonCount;
232         buttonCount++;
233     }
234 
235     for (int code = 0;  code < ABS_CNT;  code++)
236     {
237         linjs.absMap[code] = -1;
238         if (!mixin(isBitSet!("code", "absBits")))
239             continue;
240 
241         if (code >= ABS_HAT0X && code <= ABS_HAT3Y)
242         {
243             linjs.absMap[code] = hatCount;
244             hatCount++;
245             // Skip the Y axis
246             code++;
247         }
248         else
249         {
250             if (ioctl(linjs.fd, EVIOCGABS(code), &linjs.absInfo[code]) < 0)
251                 continue;
252 
253             linjs.absMap[code] = axisCount;
254             axisCount++;
255         }
256     }
257 
258     _GLFWjoystick* js = _glfwAllocJoystick(name.ptr, guid.ptr, axisCount, buttonCount, hatCount);
259     if (!js)
260     {
261         close(linjs.fd);
262         return GLFW_FALSE;
263     }
264 
265     strncpy(linjs.path.ptr, path, linjs.path.length - 1);
266     memcpy(&js.linjs, &linjs, linjs.sizeof); // twab: wrong size, linjs.path.length
267 
268     pollAbsState(js);
269 
270     _glfwInputJoystick(js, GLFW_CONNECTED);
271     return GLFW_TRUE;
272 }
273 
274 // Frees all resources associated with the specified joystick
275 //
276 private void closeJoystick(_GLFWjoystick* js) {
277     close(js.linjs.fd);
278     _glfwFreeJoystick(js);
279     _glfwInputJoystick(js, GLFW_DISCONNECTED);
280 }
281 
282 // Lexically compare joysticks by name; used by qsort
283 //
284 private int compareJoysticks(const(void)* fp, const(void)* sp) {
285     auto fj = cast(const(_GLFWjoystick)*) fp;
286     auto sj = cast(const(_GLFWjoystick)*) sp;
287     return strcmp(fj.linjs.path.ptr, sj.linjs.path.ptr);
288 }
289 
290 
291 //////////////////////////////////////////////////////////////////////////
292 //////                       GLFW internal API                      //////
293 //////////////////////////////////////////////////////////////////////////
294 
295 /// Returns: `true` if `str` matches the regex `^event[0-9]\\+$`
296 private extern(D) bool isEventFile(const(char)* str) {
297     import core.stdc.string: strlen;
298     const len = strlen(str);
299     if (len < "event0".length) {
300         return false;
301     }
302     if (str[0..5] != "event") {
303         return false;
304     }
305     foreach(i; 5..len) {
306         if (str[i] < '0' || str[i] > '9') {
307             return false;
308         }
309     }
310     return true;
311 }
312 
313 @("is event file") unittest {
314     assert(isEventFile("event0"));
315     assert(isEventFile("event1234567890"));
316     assert(!isEventFile("event"));
317     assert(!isEventFile("even0"));
318     assert(!isEventFile("event0A"));
319 }
320 
321 static if (__VERSION__ < 2094) {
322     // workaround for missing @nogc attribute in core.sys.linux.sys.inotify
323     // should be fixed in dmd 2.094
324     extern(C) nothrow @nogc int inotify_init();
325     extern(C) nothrow @nogc int inotify_init1(int flags);
326     extern(C) nothrow @nogc int inotify_add_watch(int fd, const(char)* name, uint mask);
327     extern(C) nothrow @nogc int inotify_rm_watch(int fd, uint wd);
328 }
329 
330 // Initialize joystick interface
331 //
332 GLFWbool _glfwInitJoysticksLinux() {
333     const(char)* dirname = "/dev/input";
334 
335     _glfw.linjs.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
336     if (_glfw.linjs.inotify > 0)
337     {
338         // HACK: Register for IN_ATTRIB to get notified when udev is done
339         //       This works well in practice but the true way is libudev
340 
341         _glfw.linjs.watch = inotify_add_watch(_glfw.linjs.inotify,
342                                               dirname,
343                                               IN_CREATE | IN_ATTRIB | IN_DELETE);
344     }
345 
346     // Continue without device connection notifications if inotify fails
347     version(none) {
348         // remove regex dependency
349         if (regcomp(&_glfw.linjs.regex, "^event[0-9]\\+$", 0) != 0)
350         {
351             _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex");
352             return GLFW_FALSE;
353         }
354     }
355 
356     int count = 0;
357 
358     DIR* dir = opendir(dirname);
359     if (dir)
360     {
361         dirent* entry;
362 
363         while (true)
364         {
365             entry = readdir(dir);
366             if (!entry) {
367                 break;
368             }
369 
370             version(none) {
371                 regmatch_t match;
372                 if (regexec(&_glfw.linjs.regex, entry.d_name, 1, &match, 0) != 0)
373                     continue;
374             } else {
375                 // remove regex dependency
376                 if (!isEventFile(entry.d_name.ptr)) {
377                     continue;
378                 }
379             }
380 
381             char[PATH_MAX] path;
382 
383             snprintf(path.ptr, path.length, "%s/%s", dirname, entry.d_name.ptr);
384 
385             if (openJoystickDevice(path.ptr))
386                 count++;
387         }
388 
389         closedir(dir);
390     }
391 
392     // Continue with no joysticks if enumeration fails
393 
394     qsort(_glfw.joysticks.ptr, count, _GLFWjoystick.sizeof, &compareJoysticks);
395     return GLFW_TRUE;
396 }
397 
398 // Close all opened joystick handles
399 //
400 void _glfwTerminateJoysticksLinux() {
401     int jid;
402 
403     for (jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
404     {
405         _GLFWjoystick* js = _glfw.joysticks.ptr + jid;
406         if (js.present)
407             closeJoystick(js);
408     }
409 
410     version(none) {
411         regfree(&_glfw.linjs.regex);
412     }
413 
414     if (_glfw.linjs.inotify > 0)
415     {
416         if (_glfw.linjs.watch > 0)
417             inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch);
418 
419         close(_glfw.linjs.inotify);
420     }
421 }
422 
423 void _glfwDetectJoystickConnectionLinux() {
424     if (_glfw.linjs.inotify <= 0)
425         return;
426 
427     ssize_t offset = 0;
428     char[16384] buffer;
429     const(ssize_t) size = read(_glfw.linjs.inotify, buffer.ptr, typeof(buffer).sizeof);
430 
431     while (size > offset)
432     {
433         const(inotify_event)* e = cast(inotify_event*) (buffer.ptr + offset);
434 
435         offset += typeof(cast(inotify_event) + e.len).sizeof;
436 
437         version(none) {
438             regmatch_t match;
439             if (regexec(&_glfw.linjs.regex, e.name, 1, &match, 0) != 0)
440                 continue;
441         } else {
442             if (!isEventFile(e.name.ptr)) {
443                 continue;
444             }
445         }
446 
447         char[PATH_MAX] path;
448         snprintf(path.ptr, path.length, "/dev/input/%s", e.name.ptr);
449 
450         if (e.mask & (IN_CREATE | IN_ATTRIB))
451             openJoystickDevice(path.ptr);
452         else if (e.mask & IN_DELETE)
453         {
454             for (int jid = 0;  jid <= GLFW_JOYSTICK_LAST;  jid++)
455             {
456                 if (strcmp(_glfw.joysticks[jid].linjs.path.ptr, path.ptr) == 0)
457                 {
458                     closeJoystick(_glfw.joysticks.ptr + jid);
459                     break;
460                 }
461             }
462         }
463     }
464 }
465 
466 
467 //////////////////////////////////////////////////////////////////////////
468 //////                       GLFW platform API                      //////
469 //////////////////////////////////////////////////////////////////////////
470 
471 int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode) {
472     // Read all queued events (non-blocking)
473     for (;;)
474     {
475         input_event e;
476 
477         errno = 0;
478         if (read(js.linjs.fd, &e, typeof(e).sizeof) < 0)
479         {
480             // Reset the joystick slot if the device was disconnected
481             if (errno == ENODEV)
482                 closeJoystick(js);
483 
484             break;
485         }
486 
487         if (e.type == EV_SYN)
488         {
489             if (e.code == SYN_DROPPED)
490                 _glfw.linjs.dropped = GLFW_TRUE;
491             else if (e.code == SYN_REPORT)
492             {
493                 _glfw.linjs.dropped = GLFW_FALSE;
494                 pollAbsState(js);
495             }
496         }
497 
498         if (_glfw.linjs.dropped)
499             continue;
500 
501         if (e.type == EV_KEY)
502             handleKeyEvent(js, e.code, e.value);
503         else if (e.type == EV_ABS)
504             handleAbsEvent(js, e.code, e.value);
505     }
506 
507     return js.present;
508 }
509 
510 void _glfwPlatformUpdateGamepadGUID(char* guid) {
511 }