Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3
 * It is copyright by its individual contributors, as recorded in the
4
 * project's Git history.  See COPYING.txt at the top level for license
5
 * terms and a link to the Git history.
6
 */
7
/*
8
 *
9
 * SDL joystick support
10
 *
11
 */
12
 
13
#include <memory>
14
#include <vector>
15
#include <tuple>
16
#include <type_traits>
17
#include "joy.h"
18
#include "key.h"
19
#include "dxxerror.h"
20
#include "timer.h"
21
#include "console.h"
22
#include "event.h"
23
#include "u_mem.h"
24
#include "playsave.h"
25
#include "kconfig.h"
26
#include "compiler-range_for.h"
27
 
28
#if DXX_MAX_JOYSTICKS
29
#include "compiler-cf_assert.h"
30
#include "d_enumerate.h"
31
#include "d_range.h"
32
#include "partial_range.h"
33
#include <utility>
34
 
35
namespace dcx {
36
 
37
namespace {
38
 
39
int num_joysticks = 0;
40
 
41
std::vector<unsigned> joy_key_map;
42
 
43
/* This struct is a "virtual" joystick, which includes all the axes
44
 * and buttons of every joystick found.
45
 */
46
static struct joyinfo {
47
#if DXX_MAX_BUTTONS_PER_JOYSTICK
48
        std::array<uint8_t, JOY_MAX_BUTTONS> button_state;
49
#endif
50
} Joystick;
51
 
52
struct d_event_joystickbutton : d_event
53
{
54
        const unsigned button;
55
        constexpr d_event_joystickbutton(const event_type t, const unsigned b) :
56
                d_event(t), button(b)
57
        {
58
        }
59
};
60
 
61
struct d_event_joystick_moved : d_event, d_event_joystick_axis_value
62
{
63
        DXX_INHERIT_CONSTRUCTORS(d_event_joystick_moved, d_event);
64
};
65
 
66
class SDL_Joystick_deleter
67
{
68
public:
69
        void operator()(SDL_Joystick *j) const
70
        {
71
                SDL_JoystickClose(j);
72
        }
73
};
74
 
75
#ifndef DXX_USE_SIZE_SORTED_TUPLE
76
#define DXX_USE_SIZE_SORTED_TUPLE       (__cplusplus > 201103L)
77
#endif
78
 
79
#if DXX_USE_SIZE_SORTED_TUPLE
80
template <typename T, std::size_t... Is, std::size_t... Js>
81
auto d_split_tuple(T &&t, std::index_sequence<Is...>, std::index_sequence<Js...>) ->
82
        std::pair<
83
                std::tuple<typename std::tuple_element<Is, T>::type...>,
84
                std::tuple<typename std::tuple_element<sizeof...(Is) + Js, T>::type...>
85
        >;
86
 
87
template <typename>
88
class d_size_sorted;
89
 
90
/* Given an input tuple T, define a public member `type` with the same
91
 * members as T, but sorted such that sizeof(Ti) <= sizeof(Tj) for all i
92
 * <= j.
93
 */
94
template <typename... Ts>
95
class d_size_sorted<std::tuple<Ts...>>
96
{
97
        using split_tuple = decltype(d_split_tuple(
98
                std::declval<std::tuple<Ts...>>(),
99
                std::make_index_sequence<sizeof...(Ts) / 2>(),
100
                std::make_index_sequence<(1 + sizeof...(Ts)) / 2>()
101
        ));
102
        using first_type = typename split_tuple::first_type;
103
        using second_type = typename split_tuple::second_type;
104
public:
105
        using type = typename std::conditional<(sizeof(first_type) < sizeof(second_type)),
106
                decltype(std::tuple_cat(std::declval<first_type>(), std::declval<second_type>())),
107
                decltype(std::tuple_cat(std::declval<second_type>(), std::declval<first_type>()))
108
        >::type;
109
};
110
 
111
template <typename T1>
112
class d_size_sorted<std::tuple<T1>>
113
{
114
public:
115
        using type = std::tuple<T1>;
116
};
117
#endif
118
 
119
template <typename>
120
class ignore_empty {};
121
 
122
template <typename T>
123
using maybe_empty_array = typename std::conditional<T().empty(), ignore_empty<T>, T>::type;
124
 
125
/* This struct is an array, with one entry for each physical joystick
126
 * found.
127
 */
128
class d_physical_joystick
129
{
130
#define for_each_tuple_item(HANDLE_VERB,VERB)   \
131
        HANDLE_VERB(handle)     \
132
        VERB(hat_map)   \
133
        VERB(button_map)        \
134
        VERB(axis_map)  \
135
        VERB(axis_value)        \
136
        VERB(axis_button_map)   \
137
 
138
#if DXX_USE_SIZE_SORTED_TUPLE
139
        template <typename... Ts>
140
                using tuple_type = typename d_size_sorted<std::tuple<Ts...>>::type;
141
#define define_handle_getter(N) \
142
        define_getter(N, tuple_member_type_##N)
143
#define define_array_getter(N)  \
144
        define_getter(N, maybe_empty_array<tuple_member_type_##N>)
145
#else
146
        template <typename... Ts>
147
                using tuple_type = std::tuple<Ts...>;
148
        enum
149
        {
150
#define define_enum(V)  tuple_item_##V,
151
                for_each_tuple_item(define_enum, define_enum)
152
#undef define_enum
153
        };
154
        /* std::get<index> does not handle the maybe_empty_array case, so
155
         * reuse the same getter for both handle and array.
156
         */
157
#define define_handle_getter(N) \
158
        define_getter(N, tuple_item_##N)
159
#define define_array_getter     define_handle_getter
160
#endif
161
#define define_getter(N,V)      \
162
        auto &N()       \
163
        {       \
164
                return std::get<V>(t);  \
165
        }
166
        using tuple_member_type_handle = std::unique_ptr<SDL_Joystick, SDL_Joystick_deleter>;
167
        //Note: Descent expects hats to be buttons, so these are indices into Joystick.buttons
168
        struct tuple_member_type_hat_map : std::array<unsigned, DXX_MAX_HATS_PER_JOYSTICK> {};
169
        struct tuple_member_type_button_map : std::array<unsigned, DXX_MAX_BUTTONS_PER_JOYSTICK> {};
170
        struct tuple_member_type_axis_map : std::array<unsigned, DXX_MAX_AXES_PER_JOYSTICK> {};
171
        struct tuple_member_type_axis_value : std::array<int, DXX_MAX_AXES_PER_JOYSTICK> {};
172
        struct tuple_member_type_axis_button_map : std::array<unsigned, DXX_MAX_AXES_PER_JOYSTICK> {};
173
        tuple_type<
174
                tuple_member_type_handle,
175
                maybe_empty_array<tuple_member_type_hat_map>,
176
                maybe_empty_array<tuple_member_type_button_map>,
177
                maybe_empty_array<tuple_member_type_axis_map>,
178
                maybe_empty_array<tuple_member_type_axis_value>,
179
                maybe_empty_array<tuple_member_type_axis_button_map>
180
        > t;
181
public:
182
        for_each_tuple_item(define_handle_getter, define_array_getter);
183
#undef define_handle_getter
184
#undef define_array_getter
185
#undef define_getter
186
#undef for_each_tuple_item
187
};
188
 
189
}
190
 
191
static std::array<d_physical_joystick, DXX_MAX_JOYSTICKS> SDL_Joysticks;
192
 
193
#if DXX_MAX_BUTTONS_PER_JOYSTICK
194
window_event_result joy_button_handler(const SDL_JoyButtonEvent *const jbe)
195
{
196
        const unsigned button = SDL_Joysticks[jbe->which].button_map()[jbe->button];
197
 
198
        Joystick.button_state[button] = jbe->state;
199
 
200
        const d_event_joystickbutton event{
201
                (jbe->type == SDL_JOYBUTTONDOWN) ? EVENT_JOYSTICK_BUTTON_DOWN : EVENT_JOYSTICK_BUTTON_UP,
202
                button
203
        };
204
        con_printf(CON_DEBUG, "Sending event %s, button %d", (jbe->type == SDL_JOYBUTTONDOWN) ? "EVENT_JOYSTICK_BUTTON_DOWN" : "EVENT_JOYSTICK_JOYSTICK_UP", event.button);
205
        return event_send(event);
206
}
207
#endif
208
 
209
#if DXX_MAX_HATS_PER_JOYSTICK
210
window_event_result joy_hat_handler(const SDL_JoyHatEvent *const jhe)
211
{
212
        int hat = SDL_Joysticks[jhe->which].hat_map()[jhe->hat];
213
        window_event_result highest_result(window_event_result::ignored);
214
        //Save last state of the hat-button
215
 
216
        //get current state of the hat-button
217
        const auto jhe_value = jhe->value;
218
        /* Every value must have exactly one bit set, and the union must
219
         * cover the lower four bits.  If any of these assertions fail, the
220
         * loop will not work right.
221
         */
222
#define assert_hat_one_bit(M)   \
223
        static_assert(!((SDL_HAT_##M) & ((SDL_HAT_##M) - 1)), "unexpected " #M " mask");
224
        assert_hat_one_bit(UP);
225
        assert_hat_one_bit(RIGHT);
226
        assert_hat_one_bit(DOWN);
227
        assert_hat_one_bit(LEFT);
228
#undef assert_hat_one_bit
229
        static_assert((SDL_HAT_UP | SDL_HAT_RIGHT | SDL_HAT_DOWN | SDL_HAT_LEFT) == 0xf, "unexpected hat mask");
230
 
231
        //determine if a hat-button up or down event based on state and last_state
232
        range_for (const unsigned i, xrange(4u))
233
        {
234
                const auto current_button_state = !!(jhe_value & (1 << i));
235
                auto &saved_button_state = Joystick.button_state[hat + i];
236
                if (saved_button_state == current_button_state)
237
                        // Same state as before
238
                        continue;
239
                saved_button_state = current_button_state;
240
                const d_event_joystickbutton event{current_button_state ? EVENT_JOYSTICK_BUTTON_DOWN : EVENT_JOYSTICK_BUTTON_UP, hat + i};
241
                if (current_button_state) //last_state up, current state down
242
                {
243
                        con_printf(CON_DEBUG, "Sending event EVENT_JOYSTICK_BUTTON_DOWN, button %d", event.button);
244
                }
245
                else    //last_state down, current state up
246
                {
247
                        con_printf(CON_DEBUG, "Sending event EVENT_JOYSTICK_BUTTON_UP, button %d", event.button);
248
                }
249
                highest_result = std::max(event_send(event), highest_result);
250
        }
251
 
252
        return highest_result;
253
}
254
#endif
255
 
256
#if DXX_MAX_AXES_PER_JOYSTICK
257
#if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
258
static window_event_result send_axis_button_event(unsigned button, event_type e)
259
{
260
        Joystick.button_state[button] = (e == EVENT_JOYSTICK_BUTTON_UP) ? 0 : 1;
261
        const d_event_joystickbutton event{ e, button };
262
        con_printf(CON_DEBUG, "Sending event %s, button %d", (e == EVENT_JOYSTICK_BUTTON_UP) ? "EVENT_JOYSTICK_BUTTON_UP" : "EVENT_JOYSTICK_BUTTON_DOWN", event.button);
263
        return event_send(event);
264
}
265
 
266
window_event_result joy_axisbutton_handler(const SDL_JoyAxisEvent *const jae)
267
{
268
        auto &js = SDL_Joysticks[jae->which];
269
        auto axis_value = js.axis_value()[jae->axis];
270
        auto button = js.axis_button_map()[jae->axis];
271
        window_event_result highest_result(window_event_result::ignored);
272
 
273
        // We have to hardcode a deadzone here. It's not mapped into the settings.
274
        // We could add another deadzone slider called "axis button deadzone".
275
        // I think it's safe to assume a 30% deadzone on analog button presses for now.
276
        const decltype(axis_value) deadzone = 38;
277
        auto prev_value = apply_deadzone(axis_value, deadzone);
278
        auto new_value = apply_deadzone(jae->value/256, deadzone);
279
 
280
        if (prev_value <= 0 && new_value >= 0) // positive pressed
281
        {
282
                if (prev_value < 0) // Do previous direction release first if the case
283
                        highest_result = std::max(send_axis_button_event(button + 1, EVENT_JOYSTICK_BUTTON_UP), highest_result);
284
                if (new_value > 0)
285
                        highest_result = std::max(send_axis_button_event(button, EVENT_JOYSTICK_BUTTON_DOWN), highest_result);
286
        }
287
        else if (prev_value >= 0 && new_value <= 0) // negative pressed
288
        {
289
                if (prev_value > 0) // Do previous direction release first if the case
290
                        highest_result = std::max(send_axis_button_event(button, EVENT_JOYSTICK_BUTTON_UP), highest_result);
291
                if (new_value < 0)
292
                        highest_result = std::max(send_axis_button_event(button + 1, EVENT_JOYSTICK_BUTTON_DOWN), highest_result);
293
        }
294
 
295
        return highest_result;
296
}
297
#endif
298
 
299
window_event_result joy_axis_handler(const SDL_JoyAxisEvent *const jae)
300
{
301
        auto &js = SDL_Joysticks[jae->which];
302
        const auto axis = js.axis_map()[jae->axis];
303
        auto &axis_value = js.axis_value()[jae->axis];
304
        // inaccurate stick is inaccurate. SDL might send SDL_JoyAxisEvent even if the value is the same as before.
305
        if (axis_value == jae->value/256)
306
                return window_event_result::ignored;
307
 
308
        d_event_joystick_moved event{EVENT_JOYSTICK_MOVED};
309
        event.value = axis_value = jae->value/256;
310
        event.axis = axis;
311
        con_printf(CON_DEBUG, "Sending event EVENT_JOYSTICK_MOVED, axis: %d, value: %d",event.axis, event.value);
312
 
313
        return event_send(event);
314
}
315
#endif
316
 
317
 
318
/* ----------------------------------------------- */
319
 
320
static unsigned check_warn_joy_support_limit(const unsigned n, const char *const desc, const unsigned MAX)
321
{
322
        if (n <= MAX)
323
        {
324
                con_printf(CON_NORMAL, "sdl-joystick: %d %ss", n, desc);
325
                return n;
326
        }
327
        Warning("sdl-joystick: found %d %ss, only %d supported.\n", n, desc, MAX);
328
        return MAX;
329
}
330
 
331
void joy_init()
332
{
333
        if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
334
                con_printf(CON_NORMAL, "sdl-joystick: initialisation failed: %s.",SDL_GetError());
335
                return;
336
        }
337
 
338
        Joystick = {};
339
#if DXX_MAX_AXES_PER_JOYSTICK
340
        joyaxis_text.clear();
341
#endif
342
        joybutton_text.clear();
343
        joy_key_map.clear();
344
 
345
        const auto n = check_warn_joy_support_limit(SDL_NumJoysticks(), "joystick", DXX_MAX_JOYSTICKS);
346
        cf_assert(n <= DXX_MAX_JOYSTICKS);
347
        unsigned joystick_n_buttons = 0, joystick_n_axes = 0;
348
        range_for (const unsigned i, xrange(n))
349
        {
350
                auto &joystick = SDL_Joysticks[num_joysticks];
351
                const auto handle = SDL_JoystickOpen(i);
352
                joystick.handle().reset(handle);
353
#if SDL_MAJOR_VERSION == 1
354
                con_printf(CON_NORMAL, "sdl-joystick %d: %s", i, SDL_JoystickName(i));
355
#else
356
                con_printf(CON_NORMAL, "sdl-joystick %d: %s", i, SDL_JoystickName(handle));
357
#endif
358
                if (handle)
359
                {
360
#if DXX_MAX_AXES_PER_JOYSTICK
361
                        const auto n_axes = check_warn_joy_support_limit(SDL_JoystickNumAxes(handle), "axe", DXX_MAX_AXES_PER_JOYSTICK);
362
 
363
                        joyaxis_text.resize(joyaxis_text.size() + n_axes);
364
                        range_for (auto &&e, enumerate(partial_range(joystick.axis_map(), n_axes), 1))
365
                        {
366
                                cf_assert(e.idx <= DXX_MAX_AXES_PER_JOYSTICK);
367
                                auto &text = joyaxis_text[joystick_n_axes];
368
                                e.value = joystick_n_axes++;
369
                                snprintf(&text[0], sizeof(text), "J%d A%u", i + 1, e.idx);
370
                        }
371
#else
372
            const auto n_axes = 0;
373
#endif
374
 
375
                        const auto n_buttons = check_warn_joy_support_limit(SDL_JoystickNumButtons(handle), "button", DXX_MAX_BUTTONS_PER_JOYSTICK);
376
                        const auto n_hats = check_warn_joy_support_limit(SDL_JoystickNumHats(handle), "hat", DXX_MAX_HATS_PER_JOYSTICK);
377
 
378
                        const auto n_virtual_buttons = n_buttons + (4 * n_hats) + (2 * n_axes);
379
                        joybutton_text.resize(joybutton_text.size() + n_virtual_buttons);
380
                        joy_key_map.resize(joy_key_map.size() + n_virtual_buttons, 0);
381
#if DXX_MAX_BUTTONS_PER_JOYSTICK
382
                        range_for (auto &&e, enumerate(partial_range(joystick.button_map(), n_buttons), 1))
383
                        {
384
                                switch (e.idx) {
385
                                        case 1: joy_key_map[joystick_n_buttons] = KEY_ENTER; break;
386
                                        case 2: joy_key_map[joystick_n_buttons] = KEY_ESC; break;
387
                                        case 3: joy_key_map[joystick_n_buttons] = KEY_SPACEBAR; break;
388
                                        case 4: joy_key_map[joystick_n_buttons] = KEY_DELETE; break;
389
                                        default: break;
390
                                }
391
                                auto &text = joybutton_text[joystick_n_buttons];
392
                                e.value = joystick_n_buttons++;
393
                                cf_assert(e.idx <= DXX_MAX_BUTTONS_PER_JOYSTICK);
394
                                snprintf(&text[0], sizeof(text), "J%u B%u", i + 1, e.idx);
395
                        }
396
#endif
397
#if DXX_MAX_HATS_PER_JOYSTICK
398
                        range_for (auto &&e, enumerate(partial_range(joystick.hat_map(), n_hats), 1))
399
                        {
400
                                e.value = joystick_n_buttons;
401
                                cf_assert(e.idx <= DXX_MAX_HATS_PER_JOYSTICK);
402
                                //a hat counts as four buttons
403
                                joy_key_map[joystick_n_buttons] = KEY_UP;
404
                                snprintf(&joybutton_text[joystick_n_buttons++][0], sizeof(joybutton_text[0]), "J%u H%u%c", i + 1, e.idx, 0202);
405
                                joy_key_map[joystick_n_buttons] = KEY_RIGHT;
406
                                snprintf(&joybutton_text[joystick_n_buttons++][0], sizeof(joybutton_text[0]), "J%u H%u%c", i + 1, e.idx, 0177);
407
                                joy_key_map[joystick_n_buttons] = KEY_DOWN;
408
                                snprintf(&joybutton_text[joystick_n_buttons++][0], sizeof(joybutton_text[0]), "J%u H%u%c", i + 1, e.idx, 0200);
409
                                joy_key_map[joystick_n_buttons] = KEY_LEFT;
410
                                snprintf(&joybutton_text[joystick_n_buttons++][0], sizeof(joybutton_text[0]), "J%u H%u%c", i + 1, e.idx, 0201);
411
                        }
412
#endif
413
#if DXX_MAX_AXES_PER_JOYSTICK
414
                        range_for (auto &&e, enumerate(partial_range(joystick.axis_button_map(), n_axes), 1))
415
                        {
416
                                e.value = joystick_n_buttons;
417
                                cf_assert(e.idx <= DXX_MAX_AXES_PER_JOYSTICK);
418
                                //an axis count as 2 buttons. negative - and positive +
419
                                joy_key_map[joystick_n_buttons] = (e.idx == 1) ? KEY_RIGHT : (e.idx == 2) ? KEY_DOWN : 0;
420
                                snprintf(&joybutton_text[joystick_n_buttons++][0], sizeof(joybutton_text[0]), "J%u -A%u", i + 1, e.idx);
421
                                joy_key_map[joystick_n_buttons] = (e.idx == 1) ? KEY_LEFT : (e.idx == 2) ? KEY_UP : 0;
422
                                snprintf(&joybutton_text[joystick_n_buttons++][0], sizeof(joybutton_text[0]), "J%u +A%u", i + 1, e.idx);
423
                        }
424
#endif
425
 
426
                        num_joysticks++;
427
                }
428
                else
429
                        con_puts(CON_NORMAL, "sdl-joystick: initialization failed!");
430
 
431
                con_printf(CON_NORMAL, "sdl-joystick: %d axes (total)", joystick_n_axes);
432
                con_printf(CON_NORMAL, "sdl-joystick: %d buttons (total)", joystick_n_buttons);
433
        }
434
}
435
 
436
void joy_close()
437
{
438
        range_for (auto &j, SDL_Joysticks)
439
                j.handle().reset();
440
#if DXX_MAX_AXES_PER_JOYSTICK
441
        joyaxis_text.clear();
442
#endif
443
        joybutton_text.clear();
444
}
445
 
446
const d_event_joystick_axis_value &event_joystick_get_axis(const d_event &event)
447
{
448
        auto &e = static_cast<const d_event_joystick_moved &>(event);
449
        Assert(e.type == EVENT_JOYSTICK_MOVED);
450
        return e;
451
}
452
 
453
void joy_flush()
454
{
455
        if (!num_joysticks)
456
                return;
457
 
458
        static_assert(SDL_RELEASED == uint8_t(), "SDL_RELEASED not 0.");
459
#if DXX_MAX_BUTTONS_PER_JOYSTICK
460
        Joystick.button_state = {};
461
#endif
462
#if DXX_MAX_AXES_PER_JOYSTICK
463
        range_for (auto &j, SDL_Joysticks)
464
                j.axis_value() = {};
465
#endif
466
}
467
 
468
int event_joystick_get_button(const d_event &event)
469
{
470
        auto &e = static_cast<const d_event_joystickbutton &>(event);
471
        Assert(e.type == EVENT_JOYSTICK_BUTTON_DOWN || e.type == EVENT_JOYSTICK_BUTTON_UP);
472
        return e.button;
473
}
474
 
475
int apply_deadzone(int value, int deadzone)
476
{
477
        if (value > deadzone)
478
                return ((value - deadzone) * 128) / (128 - deadzone);
479
        else if (value < -deadzone)
480
                return ((value + deadzone) * 128) / (128 - deadzone);
481
        else
482
                return 0;
483
}
484
 
485
bool joy_translate_menu_key(const d_event &event) {
486
        if (event.type != EVENT_JOYSTICK_BUTTON_DOWN)
487
                return false;
488
#if DXX_MAX_JOYSTICKS
489
        auto &e = static_cast<const d_event_joystickbutton &>(event);
490
        assert(e.button < joy_key_map.size());
491
        auto key = joy_key_map[e.button];
492
        if (key)
493
        {
494
                event_keycommand_send(key);
495
                return true;
496
        }
497
        return false;
498
#endif
499
}
500
 
501
}
502
#endif