1//========================================================================
2// GLFW 3.2 Linux - www.glfw.org
3//------------------------------------------------------------------------
4// Copyright (c) 2002-2006 Marcus Geelnard
5// Copyright (c) 2006-2016 Camilla Berglund <elmindreda@glfw.org>
6//
7// This software is provided 'as-is', without any express or implied
8// warranty. In no event will the authors be held liable for any damages
9// arising from the use of this software.
10//
11// Permission is granted to anyone to use this software for any purpose,
12// including commercial applications, and to alter it and redistribute it
13// freely, subject to the following restrictions:
14//
15// 1. The origin of this software must not be misrepresented; you must not
16// claim that you wrote the original software. If you use this software
17// in a product, an acknowledgment in the product documentation would
18// be appreciated but is not required.
19//
20// 2. Altered source versions must be plainly marked as such, and must not
21// be misrepresented as being the original software.
22//
23// 3. This notice may not be removed or altered from any source
24// distribution.
25//
26//========================================================================
27
28#include "internal.h"
29
30#if defined(__linux__)
31#include <linux/joystick.h>
32
33#include <sys/types.h>
34#include <sys/stat.h>
35#include <sys/inotify.h>
36#include <fcntl.h>
37#include <errno.h>
38#include <dirent.h>
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43#endif // __linux__
44
45
46// Attempt to open the specified joystick device
47//
48#if defined(__linux__)
49static GLFWbool openJoystickDevice(const char* path)
50{
51 char axisCount, buttonCount;
52 char name[256];
53 int joy, fd, version;
54 _GLFWjoystickLinux* js;
55
56 for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++)
57 {
58 if (!_glfw.linux_js.js[joy].present)
59 continue;
60
61 if (strcmp(_glfw.linux_js.js[joy].path, path) == 0)
62 return GLFW_FALSE;
63 }
64
65 for (joy = GLFW_JOYSTICK_1; joy <= GLFW_JOYSTICK_LAST; joy++)
66 {
67 if (!_glfw.linux_js.js[joy].present)
68 break;
69 }
70
71 if (joy > GLFW_JOYSTICK_LAST)
72 return GLFW_FALSE;
73
74 fd = open(path, O_RDONLY | O_NONBLOCK);
75 if (fd == -1)
76 return GLFW_FALSE;
77
78 // Verify that the joystick driver version is at least 1.0
79 ioctl(fd, JSIOCGVERSION, &version);
80 if (version < 0x010000)
81 {
82 // It's an old 0.x interface (we don't support it)
83 close(fd);
84 return GLFW_FALSE;
85 }
86
87 if (ioctl(fd, JSIOCGNAME(sizeof(name)), name) < 0)
88 strncpy(name, "Unknown", sizeof(name));
89
90 js = _glfw.linux_js.js + joy;
91 js->present = GLFW_TRUE;
92 js->name = strdup(name);
93 js->path = strdup(path);
94 js->fd = fd;
95
96 ioctl(fd, JSIOCGAXES, &axisCount);
97 js->axisCount = (int) axisCount;
98 js->axes = calloc(axisCount, sizeof(float));
99
100 ioctl(fd, JSIOCGBUTTONS, &buttonCount);
101 js->buttonCount = (int) buttonCount;
102 js->buttons = calloc(buttonCount, 1);
103
104 _glfwInputJoystickChange(joy, GLFW_CONNECTED);
105 return GLFW_TRUE;
106}
107#endif // __linux__
108
109// Polls for and processes events the specified joystick
110//
111static GLFWbool pollJoystickEvents(_GLFWjoystickLinux* js)
112{
113#if defined(__linux__)
114 _glfwPollJoystickEvents();
115
116 if (!js->present)
117 return GLFW_FALSE;
118
119 // Read all queued events (non-blocking)
120 for (;;)
121 {
122 struct js_event e;
123
124 errno = 0;
125 if (read(js->fd, &e, sizeof(e)) < 0)
126 {
127 // Reset the joystick slot if the device was disconnected
128 if (errno == ENODEV)
129 {
130 free(js->axes);
131 free(js->buttons);
132 free(js->name);
133 free(js->path);
134
135 memset(js, 0, sizeof(_GLFWjoystickLinux));
136
137 _glfwInputJoystickChange(js - _glfw.linux_js.js,
138 GLFW_DISCONNECTED);
139 }
140
141 break;
142 }
143
144 // Clear the initial-state bit
145 e.type &= ~JS_EVENT_INIT;
146
147 if (e.type == JS_EVENT_AXIS)
148 js->axes[e.number] = (float) e.value / 32767.0f;
149 else if (e.type == JS_EVENT_BUTTON)
150 js->buttons[e.number] = e.value ? GLFW_PRESS : GLFW_RELEASE;
151 }
152#endif // __linux__
153 return js->present;
154}
155
156// Lexically compare joysticks, used by quicksort
157//
158#if defined(__linux__)
159static int compareJoysticks(const void* fp, const void* sp)
160{
161 const _GLFWjoystickLinux* fj = fp;
162 const _GLFWjoystickLinux* sj = sp;
163 return strcmp(fj->path, sj->path);
164}
165#endif // __linux__
166
167
168//////////////////////////////////////////////////////////////////////////
169////// GLFW internal API //////
170//////////////////////////////////////////////////////////////////////////
171
172// Initialize joystick interface
173//
174GLFWbool _glfwInitJoysticksLinux(void)
175{
176#if defined(__linux__)
177 DIR* dir;
178 int count = 0;
179 const char* dirname = "/dev/input";
180
181 _glfw.linux_js.inotify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
182 if (_glfw.linux_js.inotify == -1)
183 {
184 _glfwInputError(GLFW_PLATFORM_ERROR,
185 "Linux: Failed to initialize inotify: %s",
186 strerror(errno));
187 return GLFW_FALSE;
188 }
189
190 // HACK: Register for IN_ATTRIB as well to get notified when udev is done
191 // This works well in practice but the true way is libudev
192
193 _glfw.linux_js.watch = inotify_add_watch(_glfw.linux_js.inotify,
194 dirname,
195 IN_CREATE | IN_ATTRIB);
196 if (_glfw.linux_js.watch == -1)
197 {
198 _glfwInputError(GLFW_PLATFORM_ERROR,
199 "Linux: Failed to watch for joystick connections in %s: %s",
200 dirname,
201 strerror(errno));
202 // Continue without device connection notifications
203 }
204
205 if (regcomp(&_glfw.linux_js.regex, "^js[0-9]\\+$", 0) != 0)
206 {
207 _glfwInputError(GLFW_PLATFORM_ERROR, "Linux: Failed to compile regex");
208 return GLFW_FALSE;
209 }
210
211 dir = opendir(dirname);
212 if (dir)
213 {
214 struct dirent* entry;
215
216 while ((entry = readdir(dir)))
217 {
218 char path[20];
219 regmatch_t match;
220
221 if (regexec(&_glfw.linux_js.regex, entry->d_name, 1, &match, 0) != 0)
222 continue;
223
224 snprintf(path, sizeof(path), "%s/%s", dirname, entry->d_name);
225 if (openJoystickDevice(path))
226 count++;
227 }
228
229 closedir(dir);
230 }
231 else
232 {
233 _glfwInputError(GLFW_PLATFORM_ERROR,
234 "Linux: Failed to open joystick device directory %s: %s",
235 dirname,
236 strerror(errno));
237 // Continue with no joysticks detected
238 }
239
240 qsort(_glfw.linux_js.js, count, sizeof(_GLFWjoystickLinux), compareJoysticks);
241#endif // __linux__
242
243 return GLFW_TRUE;
244}
245
246// Close all opened joystick handles
247//
248void _glfwTerminateJoysticksLinux(void)
249{
250#if defined(__linux__)
251 int i;
252
253 for (i = 0; i <= GLFW_JOYSTICK_LAST; i++)
254 {
255 if (_glfw.linux_js.js[i].present)
256 {
257 close(_glfw.linux_js.js[i].fd);
258 free(_glfw.linux_js.js[i].axes);
259 free(_glfw.linux_js.js[i].buttons);
260 free(_glfw.linux_js.js[i].name);
261 free(_glfw.linux_js.js[i].path);
262 }
263 }
264
265 regfree(&_glfw.linux_js.regex);
266
267 if (_glfw.linux_js.inotify > 0)
268 {
269 if (_glfw.linux_js.watch > 0)
270 inotify_rm_watch(_glfw.linux_js.inotify, _glfw.linux_js.watch);
271
272 close(_glfw.linux_js.inotify);
273 }
274#endif // __linux__
275}
276
277void _glfwPollJoystickEvents(void)
278{
279#if defined(__linux__)
280 ssize_t offset = 0;
281 char buffer[16384];
282
283 const ssize_t size = read(_glfw.linux_js.inotify, buffer, sizeof(buffer));
284
285 while (size > offset)
286 {
287 regmatch_t match;
288 const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
289
290 if (regexec(&_glfw.linux_js.regex, e->name, 1, &match, 0) == 0)
291 {
292 char path[20];
293 snprintf(path, sizeof(path), "/dev/input/%s", e->name);
294 openJoystickDevice(path);
295 }
296
297 offset += sizeof(struct inotify_event) + e->len;
298 }
299#endif
300}
301
302
303//////////////////////////////////////////////////////////////////////////
304////// GLFW platform API //////
305//////////////////////////////////////////////////////////////////////////
306
307int _glfwPlatformJoystickPresent(int joy)
308{
309 _GLFWjoystickLinux* js = _glfw.linux_js.js + joy;
310 return pollJoystickEvents(js);
311}
312
313const float* _glfwPlatformGetJoystickAxes(int joy, int* count)
314{
315 _GLFWjoystickLinux* js = _glfw.linux_js.js + joy;
316 if (!pollJoystickEvents(js))
317 return NULL;
318
319 *count = js->axisCount;
320 return js->axes;
321}
322
323const unsigned char* _glfwPlatformGetJoystickButtons(int joy, int* count)
324{
325 _GLFWjoystickLinux* js = _glfw.linux_js.js + joy;
326 if (!pollJoystickEvents(js))
327 return NULL;
328
329 *count = js->buttonCount;
330 return js->buttons;
331}
332
333const char* _glfwPlatformGetJoystickName(int joy)
334{
335 _GLFWjoystickLinux* js = _glfw.linux_js.js + joy;
336 if (!pollJoystickEvents(js))
337 return NULL;
338
339 return js->name;
340}
341
342