in_sdl: improve joy support
authornotaz <notasas@gmail.com>
Fri, 16 May 2025 21:04:40 +0000 (00:04 +0300)
committeririxxxx <31696370+irixxxx@users.noreply.github.com>
Sat, 17 May 2025 07:26:14 +0000 (09:26 +0200)
- avoid event flood for analog->udlr mapping
- ability to disable this mapping
- basic hat support

in_sdl.c
input.h
linux/in_evdev.c

index 3fdc01a..15c8c22 100644 (file)
--- a/in_sdl.c
+++ b/in_sdl.c
 typedef unsigned long keybits_t;
 #define KEYBITS_WORD_BITS (sizeof(keybits_t) * 8)
 
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+#endif
+
 struct in_sdl_state {
        const in_drv_t *drv;
        SDL_Joystick *joy;
        int joy_id;
        int numaxes;
        int axis_keydown[2];
-       int redraw;
+       int hat_down;
+       unsigned int redraw:1;
+       unsigned int abs_to_udlr:1;
+       unsigned int hat_warned:1;
        SDL_Event revent;
        SDL_Event mevent; // last mouse event
        keybits_t keystate[SDLK_LAST / KEYBITS_WORD_BITS + 1];
@@ -169,6 +176,23 @@ static const char * const in_sdl_keys[SDLK_LAST] = {
        [SDLK_COMPOSE] = "compose",
 };
 
+static struct in_sdl_state *state_alloc(void)
+{
+       struct in_sdl_state *state;
+       size_t i;
+
+       state = calloc(1, sizeof(*state));
+       if (state == NULL) {
+               fprintf(stderr, "in_sdl: OOM\n");
+               return NULL;
+       }
+
+       state->abs_to_udlr = 1;
+       for (i = 0; i < ARRAY_SIZE(state->axis_keydown); i++)
+               state->axis_keydown[i] = -1;
+       return state;
+}
+
 static void in_sdl_probe(const in_drv_t *drv)
 {
        const struct in_pdata *pdata = drv->pdata;
@@ -181,11 +205,8 @@ static void in_sdl_probe(const in_drv_t *drv)
        if (pdata->key_names)
                key_names = pdata->key_names;
 
-       state = calloc(1, sizeof(*state));
-       if (state == NULL) {
-               fprintf(stderr, "in_sdl: OOM\n");
+       if (!(state = state_alloc()))
                return;
-       }
 
        state->drv = drv;
        in_register(IN_SDL_PREFIX "keys", -1, state, SDLK_LAST,
@@ -201,11 +222,8 @@ static void in_sdl_probe(const in_drv_t *drv)
                if (joy == NULL)
                        continue;
 
-               state = calloc(1, sizeof(*state));
-               if (state == NULL) {
-                       fprintf(stderr, "in_sdl: OOM\n");
+               if (!(state = state_alloc()))
                        break;
-               }
                state->joy = joy;
                state->joy_id = i;
                state->numaxes = SDL_JoystickNumAxes(joy);
@@ -289,42 +307,39 @@ static int handle_event(struct in_sdl_state *state, SDL_Event *event,
 static int handle_joy_event(struct in_sdl_state *state, SDL_Event *event,
        int *kc_out, int *down_out, int *emu_out)
 {
-       int kc = -1, down = 0, emu = 0, ret = 0;
+       static const int hat2key[4] = { SDLK_UP, SDLK_RIGHT, SDLK_DOWN, SDLK_LEFT };
+       int kc = -1, kc_prev = -1, down = 0, emu = 0, ret = 0, i, val, xor;
 
        /* TODO: remaining axis */
        switch (event->type) {
        case SDL_JOYAXISMOTION:
+               if (!state->abs_to_udlr)
+                       return -1;
                if (event->jaxis.which != state->joy_id)
                        return -2;
                if (event->jaxis.axis > 1)
                        break;
-               if (-16384 <= event->jaxis.value && event->jaxis.value <= 16384) {
-                       kc = state->axis_keydown[event->jaxis.axis];
-                       state->axis_keydown[event->jaxis.axis] = 0;
-                       ret = 1;
-               }
-               else if (event->jaxis.value < -16384) {
-                       kc = state->axis_keydown[event->jaxis.axis];
-                       if (kc) {
-                               emu |= get_keystate(state->emu_keys, kc);
-                               update_keystate(state->keystate, kc, 0);
-                       }
+               if (event->jaxis.value < -16384) {
                        kc = event->jaxis.axis ? SDLK_UP : SDLK_LEFT;
-                       state->axis_keydown[event->jaxis.axis] = kc;
                        down = 1;
-                       ret = 1;
                }
                else if (event->jaxis.value > 16384) {
-                       kc = state->axis_keydown[event->jaxis.axis];
-                       if (kc) {
-                               emu |= get_keystate(state->emu_keys, kc);
-                               update_keystate(state->keystate, kc, 0);
-                       }
                        kc = event->jaxis.axis ? SDLK_DOWN : SDLK_RIGHT;
-                       state->axis_keydown[event->jaxis.axis] = kc;
                        down = 1;
-                       ret = 1;
                }
+               kc_prev = state->axis_keydown[event->jaxis.axis];
+               state->axis_keydown[event->jaxis.axis] = kc;
+               if (kc == kc_prev)
+                       return -1; // no simulated key change
+               if (kc >= 0 && kc_prev >= 0) {
+                       // must release the old key first
+                       state->axis_keydown[event->jaxis.axis] = -1;
+                       kc = kc_prev;
+                       down = 0;
+               }
+               else if (kc_prev >= 0)
+                       kc = kc_prev;
+               ret = 1;
                break;
 
        case SDL_JOYBUTTONDOWN:
@@ -335,7 +350,29 @@ static int handle_joy_event(struct in_sdl_state *state, SDL_Event *event,
                down = event->jbutton.state == SDL_PRESSED;
                ret = 1;
                break;
+       case SDL_JOYHATMOTION:
+               if (event->jhat.which != state->joy_id)
+                       return -2;
+               if (event->jhat.hat != 0)
+                       return -1; // todo?
+               xor = event->jhat.value ^ state->hat_down;
+               val = state->hat_down = event->jhat.value;
+               for (i = 0; i < 4; i++, xor >>= 1, val >>= 1) {
+                       if (xor & 1) {
+                               down = val & 1;
+                               kc = hat2key[i];
+                               ret = 1;
+                               break;
+                       }
+               }
+               if ((!ret || (xor >> ret)) && !state->hat_warned) {
+                       // none, more than 1, or upper bits changed
+                       fprintf(stderr, "in_sdl: unexpected hat behavior\n");
+                       state->hat_warned = 1;
+               }
+               break;
        default:
+               //printf("joy ev %d\n", event->type);
                return -1;
        }
 
@@ -366,7 +403,7 @@ static int collect_events(struct in_sdl_state *state, int *one_kc, int *one_down
 
        SDL_PumpEvents();
 
-       maxcount = sizeof(events) / sizeof(events[0]);
+       maxcount = ARRAY_SIZE(events);
        if ((count = SDL_PeepEvents(events, maxcount, SDL_GETEVENT, mask)) > 0) {
                for (i = 0; i < count; i++) {
                        event = &events[i];
@@ -628,6 +665,31 @@ static int in_sdl_get_config(void *drv_data, enum in_cfg_opt what, int *val)
        case IN_CFG_ABS_AXIS_COUNT:
                *val = state->numaxes;
                break;
+       case IN_CFG_ANALOG_MAP_ULDR:
+               *val = state->abs_to_udlr;
+               break;
+       default:
+               return -1;
+       }
+
+       return 0;
+}
+
+static int in_sdl_set_config(void *drv_data, enum in_cfg_opt what, int val)
+{
+       struct in_sdl_state *state = drv_data;
+       size_t i;
+
+       switch (what) {
+       case IN_CFG_ANALOG_MAP_ULDR:
+               state->abs_to_udlr = val ? 1 : 0;
+               for (i = 0; !val && i < ARRAY_SIZE(state->axis_keydown); i++) {
+                       if (state->axis_keydown[i] < 0)
+                               continue;
+                       update_keystate(state->keystate, state->axis_keydown[i], 0);
+                       state->axis_keydown[i] = -1;
+               }
+               break;
        default:
                return -1;
        }
@@ -648,6 +710,7 @@ static const in_drv_t in_sdl_drv = {
        .menu_translate  = in_sdl_menu_translate,
        .clean_binds     = in_sdl_clean_binds,
        .get_config      = in_sdl_get_config,
+       .set_config      = in_sdl_set_config,
 };
 
 int in_sdl_init(const struct in_pdata *pdata, void (*handler)(void *event))
diff --git a/input.h b/input.h
index 4fcdbde..5ac73c9 100644 (file)
--- a/input.h
+++ b/input.h
@@ -61,6 +61,7 @@ enum in_cfg_opt {
        IN_CFG_ABS_DEAD_ZONE,   /* dead zone for analog-digital mapping */
        IN_CFG_ABS_AXIS_COUNT,  /* number of abs axes (ro) */
        IN_CFG_DEFAULT_DEV,
+       IN_CFG_ANALOG_MAP_ULDR,
 };
 
 enum {
index 49bfa37..b3fb825 100644 (file)
@@ -437,6 +437,8 @@ static int in_evdev_get_config(void *drv_data, enum in_cfg_opt what, int *val)
        case IN_CFG_ABS_AXIS_COUNT:
                *val = dev->abs_count;
                break;
+       case IN_CFG_ANALOG_MAP_ULDR:
+               *val = dev->abs_to_digital;
        default:
                return -1;
        }
@@ -463,6 +465,8 @@ static int in_evdev_set_config(void *drv_data, enum in_cfg_opt what, int val)
                else if (dev->abs_lzone >= tmp)
                        dev->abs_lzone = tmp - 1;
                break;
+       case IN_CFG_ANALOG_MAP_ULDR:
+               dev->abs_to_digital = val;
        default:
                return -1;
        }