]> icculus.org git repositories - taylor/freespace2.git/blob - src/io/joy-linux.cpp
Add optional Linux joystick implementation from Karl Robillard.
[taylor/freespace2.git] / src / io / joy-linux.cpp
1 /*
2  * Native Linux joystick for FreeSpace 2.
3  *
4  * Author: Karl Robillard <wickedsmoke@users.sf.net>
5  */
6
7
8 #include "pstypes.h"
9 #include "joy.h"
10 #include "fix.h"
11 #include "key.h"
12 #include "timer.h"
13 #include "osregistry.h"
14 #include "controlsconfig.h"
15 #include "joy_ff.h"
16 #include "osapi.h"
17 #include <linux/joystick.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <unistd.h>
21
22
23 #define DEFAULT_JOYSTICK    0
24
25
26 int Joy_sensitivity = 9;
27 int Dead_zone_size  = 10;   // percentage of range that is dead zone
28 Joy_info joystick;
29
30
31 static int _joyfd = -1;
32 static int _joy_axis_value[JOY_NUM_AXES];
33
34
35 typedef struct joy_button_info {
36         int     state;              // 1 when the button is down.
37         int     down_count;
38         uint    down_time;          // Accumulated milliseconds down.
39     uint    last_down_check;    // Timestamp in milliseconds of last check.
40         uint    down_post;          // Time of last check or down transition.
41 } joy_button_info;
42
43 static joy_button_info _joy_buttons[JOY_TOTAL_BUTTONS];
44
45
46 #define VALID_BTN(b)    (_joyfd > -1) && (b >= 0) && (b < JOY_TOTAL_BUTTONS)
47
48
49 // --------------------------------------------------------------
50 //  joy_down()
51 //
52 //  Return the state of button number 'btn'
53 //
54 //  input:      btn =>      button number to check
55 //
56 //  returns:    0   =>      not pressed
57 //              1   =>      pressed
58 //
59 int joy_down(int btn)
60 {
61     if( VALID_BTN(btn) )
62     {
63         return _joy_buttons[btn].state;
64     }
65     return 0;
66 }
67
68
69 // --------------------------------------------------------------
70 //  joy_down_count()
71 //
72 //  Return the number of times the joystick button has gone down since
73 //  joy_down_count() was last called
74 //
75 //  input:      btn         =>      button number to check
76 //              reset_count =>      (default 1): if true reset down_count
77 //
78 //  returns:    number of times button 'btn' has gone down since last call
79 //
80 int joy_down_count(int btn, int reset_count)
81 {
82     if( VALID_BTN(btn) )
83     {
84         int tmp = _joy_buttons[btn].down_count;
85         if( reset_count )
86             _joy_buttons[btn].down_count = 0;
87         return tmp;
88     }
89     return 0;
90 }
91
92
93 // --------------------------------------------------------------
94 //  joy_down_time()
95 //
96 //  Return a number between 0 and 1.  This number represents the percentage
97 //  time that the joystick button has been down since it was last checked
98 //
99 //  input:      btn =>      button number to check
100 //
101 //  returns:    value between 0 and 1
102 //
103 float joy_down_time(int btn)
104 {
105     if( VALID_BTN(btn) )
106     {
107         float rval;
108         uint  now;
109         joy_button_info* bi;
110
111         now = timer_get_milliseconds();
112
113         bi = &_joy_buttons[btn];
114
115         if( bi->down_post )
116         {
117             bi->down_time += now - bi->down_post;
118             bi->down_post = now;
119         }
120
121         if (now > bi->last_down_check)
122             rval = i2fl(bi->down_time) / (now - bi->last_down_check);
123         else
124             rval = 0.0f;
125
126         bi->down_time = 0;
127         bi->last_down_check = now;
128
129         if (rval < 0)
130             rval = 0.0f;
131         if (rval > 1)
132             rval = 1.0f;
133
134         return rval;
135     }
136     return 0.0f;
137 }
138
139
140 // --------------------------------------------------------------
141 //  joy_flush()
142 //
143 // Clear the state of the joystick.
144 //
145 void joy_flush()
146 {
147     if (_joyfd > -1)
148     {
149         int i;
150         joy_button_info *bi;
151         uint now = timer_get_milliseconds();
152
153         for ( i = 0; i < JOY_TOTAL_BUTTONS; i++)
154         {
155             bi = &_joy_buttons[i];
156             bi->state       = 0;
157             bi->down_count  = 0;
158             bi->down_time   = 0;
159             bi->down_post   = 0;
160             bi->last_down_check = now;
161         }
162     }
163 }
164
165
166 int joy_get_unscaled_reading(int raw, int axn)
167 {
168         int rng;
169
170         // Make sure it's calibrated properly.
171         if (joystick.axis_center[axn] - joystick.axis_min[axn] < 5)
172                 return 0;
173
174         if (joystick.axis_max[axn] - joystick.axis_center[axn] < 5)
175                 return 0;
176
177         rng = joystick.axis_max[axn] - joystick.axis_min[axn];
178         raw -= joystick.axis_min[axn];  // adjust for linear range starting at 0
179         
180         // cap at limits
181         if (raw < 0)
182                 raw = 0;
183         if (raw > rng)
184                 raw = rng;
185
186         return (int) ((unsigned int) raw * (unsigned int) F1_0 / (unsigned int) rng);  // convert to 0 - F1_0 range.
187 }
188
189
190 // --------------------------------------------------------------
191 //      joy_get_scaled_reading()
192 //
193 //      input:  raw =>  the raw value for an axis position
194 //          axn =>  axis number, numbered starting at 0
195 //
196 // return:  joy_get_scaled_reading will return a value that represents
197 //          the joystick pos from -1 to +1 for the specified axis number
198 //          'axn', and the raw value 'raw'
199 //
200 int joy_get_scaled_reading(int raw, int axn)
201 {
202         int x, d, dead_zone, rng;
203         float percent, sensitivity_percent, non_sensitivity_percent;
204
205         // Make sure it's calibrated properly.
206         if (joystick.axis_center[axn] - joystick.axis_min[axn] < 5)
207                 return 0;
208
209         if (joystick.axis_max[axn] - joystick.axis_center[axn] < 5)
210                 return 0;
211
212         raw -= joystick.axis_center[axn];
213
214         dead_zone = (joystick.axis_max[axn] - joystick.axis_min[axn]) * Dead_zone_size / 100;
215
216         if (raw < -dead_zone) {
217                 rng = joystick.axis_center[axn] - joystick.axis_min[axn] - dead_zone;
218                 d = -raw - dead_zone;
219
220         } else if (raw > dead_zone) {
221                 rng = joystick.axis_max[axn] - joystick.axis_center[axn] - dead_zone;
222                 d = raw - dead_zone;
223
224         } else
225                 return 0;
226
227         if (d > rng)
228                 d = rng;
229
230         Assert(Joy_sensitivity >= 0 && Joy_sensitivity <= 9);
231
232         // compute percentages as a range between 0 and 1
233         sensitivity_percent = (float) Joy_sensitivity / 9.0f;
234         non_sensitivity_percent = (float) (9 - Joy_sensitivity) / 9.0f;
235
236         // find percent of max axis is at
237         percent = (float) d / (float) rng;
238
239         // work sensitivity on axis value
240         percent = (percent * sensitivity_percent + percent * percent * percent * percent * percent * non_sensitivity_percent);
241
242         x = (int) ((float) F1_0 * percent);
243
244         //nprintf(("AI", "d=%6i, sens=%3i, percent=%6.3f, val=%6i, ratio=%6.3f\n", d, Joy_sensitivity, percent, (raw<0) ? -x : x, (float) d/x));
245
246         if (raw < 0)
247                 return -x;
248
249         return x;
250 }
251
252
253 // --------------------------------------------------------------
254 //  joy_get_pos()
255 //
256 //  input:  x   =>   OUTPUT PARAMETER: x-axis position of stick (-1 to 1)
257 //          y   =>   OUTPUT PARAMETER: y-axis position of stick (-1 to 1)
258 //          z   =>   OUTPUT PARAMETER: z-axis (throttle) position of stick (-1 to 1)
259 //          r   =>   OUTPUT PARAMETER: rudder position of stick (-1 to 1)
260 //
261 //  return:   success    => 1
262 //            failure    => 0
263 //
264 int joy_get_pos(int *x, int *y, int *z, int *rx)
265 {
266         if (x) *x = 0;
267         if (y) *y = 0;
268         if (z) *z = 0;
269         if (rx) *rx = 0;
270
271         if (_joyfd == -1)
272         return 0;
273
274         //      joy_get_scaled_reading will return a value represents the joystick pos from -1 to +1
275         if (x && joystick.axis_valid[0])
276                 *x = joy_get_scaled_reading(_joy_axis_value[0], 0);
277         if (y && joystick.axis_valid[1]) 
278                 *y = joy_get_scaled_reading(_joy_axis_value[1], 1);
279         if (z && joystick.axis_valid[2])
280                 *z = joy_get_unscaled_reading(_joy_axis_value[2], 2);
281         if (rx && joystick.axis_valid[3])
282                 *rx = joy_get_scaled_reading(_joy_axis_value[3], 3);
283
284         return 1;
285 }
286
287
288 /*
289    Convert Linux axis value to FreeSpace button.
290 */
291 static void convertAxisToHat( int value, int btnA, int btnB )
292 {
293     if( value < -20000 )
294     {
295         _joy_buttons[ btnA ].state = 1;
296         _joy_buttons[ btnB ].state = 0;
297     }
298     else if( value > 20000 )
299     {
300         _joy_buttons[ btnA ].state = 0;
301         _joy_buttons[ btnB ].state = 1;
302     }
303     else
304     {
305         _joy_buttons[ btnA ].state = 0;
306         _joy_buttons[ btnB ].state = 0;
307     }
308 }
309
310
311 void joy_read()
312 {
313     struct js_event je;
314
315     if( _joyfd == -1 )
316         return;
317
318     while( read(_joyfd, &je, sizeof(struct js_event)) > 0 )
319     {
320         if( je.type & JS_EVENT_BUTTON )
321         {
322             // je.value will be 0 or 1.
323
324             if( je.number < JOY_NUM_AXES )
325             {
326                 joy_button_info* bi = &_joy_buttons[ je.number ];
327                 uint now = timer_get_milliseconds();
328
329                 bi->state = je.value;
330
331                 if (bi->state)
332                 {
333                     // Went from up to down.
334                     bi->down_count++;
335                     bi->down_post = now;
336                 }
337                 else
338                 {
339                     // Went from down to up.
340                     if( bi->down_post )
341                     {
342                         bi->down_time += now - bi->down_post;
343                         bi->down_post = 0;
344                     }
345                 }
346             }
347         }
348         else if( je.type & JS_EVENT_AXIS )
349         {
350             // je.value will be -32767 to +32767.
351
352             if( je.number < JOY_NUM_AXES )
353             {
354                 _joy_axis_value[ je.number ] = je.value + 32768;
355
356                 // Joystick hat is typically on axis 4/5
357                 if( je.number == 4 )
358                 {
359                     convertAxisToHat( je.value, JOY_HATLEFT, JOY_HATRIGHT );
360                 }
361                 else if( je.number == 5 )
362                 {
363                     convertAxisToHat( je.value, JOY_HATFORWARD, JOY_HATBACK );
364                 }
365             }
366         }
367     }
368
369     if( errno != EAGAIN )
370         perror( "joy_read" );
371 }
372
373
374 static char _joy_device[] = "/dev/js0";
375
376 static bool openJoyDevice( int n )
377 {
378     _joy_device[7] = '0' + n;
379     _joyfd = open( _joy_device, O_RDONLY | O_NONBLOCK );
380     return (_joyfd > -1) ? true : false;
381 }
382
383
384 // --------------------------------------------------------------
385 //  joy_init()
386 //
387 // Initialize the joystick system.  This is called once at game startup.
388 //
389 int joy_init()
390 {
391         int i;
392     char joyname[80];
393     int current;
394     int buttons, axes;
395
396         if (_joyfd > -1)
397                 return 0;
398
399         current = os_config_read_uint (NULL, "CurrentJoystick", DEFAULT_JOYSTICK);
400
401     if( ! openJoyDevice( current ) )
402     {
403         if( current != DEFAULT_JOYSTICK )
404             mprintf(("Unable to open joystick %s\n", _joy_device ));
405
406         for( i = 0; i < 4; ++i )
407         {
408             if( i == current )
409                 continue;
410             if( openJoyDevice( i ) )
411             {
412                 current = i;
413                 break;
414             }
415         }
416
417         if( _joyfd < 0 )
418         {
419             mprintf(("No joysticks found\n"));
420             return 0;
421         }
422     }
423     
424     ioctl( _joyfd, JSIOCGAXES, &axes );
425     ioctl( _joyfd, JSIOCGBUTTONS, &buttons );
426     ioctl( _joyfd, JSIOCGNAME(80), &joyname );
427
428     nprintf (("JOYSTICK", "Joystick #%d: %s\n", current, joyname));
429
430     for (i=0; i < JOY_NUM_AXES; i++)
431     {
432         joystick.axis_valid[i] = (i < axes) ? 1 : 0;
433         _joy_axis_value[i] = 32768;   // Center
434     }
435
436         joy_flush();
437
438         // Fake a calibration
439     for (i=0; i<4; i++) {
440         joystick.axis_center[i] = 32768;
441         joystick.axis_min[i] = 0;
442         joystick.axis_max[i] = 65536;
443     }
444
445
446     // Map throttle by default if joystick has at least 3 axes.
447     if( axes > 2 )
448     {
449         extern int Axis_map_to_defaults[5];
450         Axis_map_to_defaults[3] = JOY_Z_AXIS;
451     }
452
453         return 1;
454 }
455
456
457 // --------------------------------------------------------------
458 //  joy_close()
459 //
460 // Close the joystick system.  Should be called at game exit.
461 //
462 void joy_close()
463 {
464     if( _joyfd > -1 )
465     {
466         close( _joyfd );
467         _joyfd = -1;
468     }
469 }
470
471
472 void joy_set_cen()
473 {
474         joystick_read_raw_axis( 2, joystick.axis_center );
475 }
476
477 int joystick_read_raw_axis(int num_axes, int *axis)
478 {
479         int i;
480         
481         if (_joyfd == -1)
482                 return 0;
483         
484         for (i = 0; i < num_axes; i++)
485         axis[i] = _joy_axis_value[i];
486         
487         return 1;
488 }
489
490 void joy_ff_adjust_handling(int speed)
491 {
492 //      STUB_FUNCTION;
493 }
494
495 void joy_ff_afterburn_off()
496 {
497 //      STUB_FUNCTION;
498 }
499
500 void joy_ff_afterburn_on()
501 {
502 //      STUB_FUNCTION;
503 }
504
505 void joy_ff_deathroll()
506 {
507 //      STUB_FUNCTION;
508 }
509
510 void joy_ff_docked()
511 {
512 //      STUB_FUNCTION;
513 }
514
515 void joy_ff_explode()
516 {
517 //      STUB_FUNCTION;
518 }
519
520 void joy_ff_fly_by(int mag)
521 {
522 //      STUB_FUNCTION;
523 }
524
525 void joy_ff_mission_init(vector v)
526 {
527 //      STUB_FUNCTION;
528 }
529
530 void joy_ff_play_dir_effect(float x, float y)
531 {
532 //      STUB_FUNCTION;
533 }
534
535 void joy_ff_play_primary_shoot(int gain)
536 {
537 //      STUB_FUNCTION;
538 }
539
540 void joy_ff_play_reload_effect()
541 {
542 //      STUB_FUNCTION;
543 }
544
545 void joy_ff_play_secondary_shoot(int gain)
546 {
547 //      STUB_FUNCTION;
548 }
549
550 void joy_ff_play_vector_effect(vector *v, float scaler)
551 {
552 //      STUB_FUNCTION;
553 }
554
555 void joy_ff_stop_effects()
556 {
557         joy_ff_afterburn_off();
558 }
559
560
561 //EOF