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__)
openJoystickDevice(const char * path)49 static 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 //
pollJoystickEvents(_GLFWjoystickLinux * js)111 static 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 by name; used by qsort
157 //
158 #if defined(__linux__)
compareJoysticks(const void * fp,const void * sp)159 static 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 //
_glfwInitJoysticksLinux(void)174 GLFWbool _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 //
_glfwTerminateJoysticksLinux(void)248 void _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
_glfwPollJoystickEvents(void)277 void _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
_glfwPlatformJoystickPresent(int joy)307 int _glfwPlatformJoystickPresent(int joy)
308 {
309 _GLFWjoystickLinux* js = _glfw.linux_js.js + joy;
310 return pollJoystickEvents(js);
311 }
312
_glfwPlatformGetJoystickAxes(int joy,int * count)313 const 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
_glfwPlatformGetJoystickButtons(int joy,int * count)323 const 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
_glfwPlatformGetJoystickName(int joy)333 const 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