diff --git a/in_sdl.c b/in_sdl.c index a84c781..bab649a 100644 --- a/in_sdl.c +++ b/in_sdl.c @@ -19,11 +19,18 @@ typedef unsigned long keybits_t; #define KEYBITS_WORD_BITS (sizeof(keybits_t) * 8) +enum mod_state { + MOD_NO, + MOD_MAYBE, + MOD_YES +}; + struct in_sdl_state { const in_drv_t *drv; SDL_Joystick *joy; int joy_id; int axis_keydown[2]; + enum mod_state mod_state; keybits_t keystate[SDLK_LAST / KEYBITS_WORD_BITS + 1]; // emulator keys should always be processed immediately lest one is lost keybits_t emu_keys[SDLK_LAST / KEYBITS_WORD_BITS + 1]; @@ -259,6 +266,139 @@ static int get_keystate(keybits_t *keystate, int sym) return !!(*ks_word & mask); } +static inline void switch_key(SDL_Event *event, keybits_t *keystate, short upkey, short downkey) +{ + event->type = SDL_KEYUP; + event->key.state = SDL_RELEASED; + event->key.keysym.sym = upkey; + + update_keystate(keystate, upkey, 0); + SDL_PushEvent(event); + + event->type = SDL_KEYDOWN; + event->key.state = SDL_PRESSED; + event->key.keysym.sym = downkey; + + update_keystate(keystate, downkey, 1); + SDL_PushEvent(event); +} + +static void translate_combo_event(struct in_sdl_state *state, SDL_Event *event, keybits_t *keystate) +{ + const struct in_pdata *pdata = state->drv->pdata; + const struct mod_keymap *map; + short key = (short)event->key.keysym.sym; + uint8_t type = event->type; + short mod_key = pdata->mod_key; + int i; + + if (event->type != SDL_KEYDOWN && event->type != SDL_KEYUP) { + SDL_PushEvent(event); + return; + } + + if (state->mod_state == MOD_NO && key != mod_key) { + update_keystate(keystate, event->key.keysym.sym, event->type == SDL_KEYDOWN); + SDL_PushEvent(event); + return; + } + + if (key == mod_key) { + switch (state->mod_state) { + case MOD_NO: + if (type == SDL_KEYDOWN) { + /* Pressed mod, maybe a combo? Ignore the keypress + * until it's determined */ + state->mod_state = MOD_MAYBE; + + for (i = 0; i < pdata->modmap_size; i++) { + map = &pdata->mod_keymap[i]; + + if (get_keystate(keystate, map->inkey)) { + state->mod_state = MOD_YES; + switch_key(event, keystate, map->inkey, map->outkey); + } + } + } else { + SDL_PushEvent(event); + } + break; + case MOD_MAYBE: + if (type == SDL_KEYDOWN) { + SDL_PushEvent(event); + } else { + /* Released mod without combo, simulate down and up */ + state->mod_state = MOD_NO; + + event->type = SDL_KEYDOWN; + event->key.state = SDL_PRESSED; + SDL_PushEvent(event); + + event->type = SDL_KEYUP; + event->key.state = SDL_RELEASED; + SDL_PushEvent(event); + } + break; + case MOD_YES: + if (type == SDL_KEYDOWN) { + SDL_PushEvent(event); + } else { + /* Released mod, switch all mod keys to unmod and ignore mod press */ + state->mod_state = MOD_NO; + + for (i = 0; i < pdata->modmap_size; i++) { + map = &pdata->mod_keymap[i]; + + if (get_keystate(keystate, map->outkey)) { + switch_key(event, keystate, map->outkey, map->inkey); + } + } + } + break; + default: + SDL_PushEvent(event); + break; + } + } else { + for (i = 0; i < pdata->modmap_size; i++) { + map = &pdata->mod_keymap[i]; + + if (map->inkey == key) { + state->mod_state = MOD_YES; + + event->key.keysym.sym = map->outkey; + update_keystate(keystate, map->outkey, event->type == SDL_KEYDOWN); + SDL_PushEvent(event); + } + } + } +} + +static void translate_combo_events(struct in_sdl_state *state, Uint32 mask) +{ + const struct in_pdata *pdata = state->drv->pdata; + SDL_Event events[10]; /* Must be bigger than events size in collect_events */ + keybits_t keystate[SDLK_LAST / KEYBITS_WORD_BITS + 1]; + int count; + int has_events; + int i; + + if (!pdata->mod_key) + return; + + has_events = SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, mask); + + if (!has_events) + return; + + memcpy(keystate, state->keystate, sizeof(keystate)); + + count = SDL_PeepEvents(events, (sizeof(events) / sizeof(events[0])), SDL_GETEVENT, mask); + for (i = 0; i < count; i++) { + translate_combo_event(state, &events[i], keystate); + } +} + static int handle_event(struct in_sdl_state *state, SDL_Event *event, int *kc_out, int *down_out, int *emu_out) { @@ -363,6 +503,9 @@ static int collect_events(struct in_sdl_state *state, int *one_kc, int *one_down SDL_PumpEvents(); + if (!state->joy) + translate_combo_events(state, mask); + num_events = SDL_PeepEvents(NULL, 0, SDL_PEEKEVENT, mask); for (num_peeped_events = 0; num_peeped_events < num_events; num_peeped_events += count) { diff --git a/input.h b/input.h index 360b65b..895ad61 100644 --- a/input.h +++ b/input.h @@ -110,6 +110,11 @@ struct menu_keymap { short pbtn; }; +struct mod_keymap { + short inkey; + short outkey; +}; + struct in_pdata { const struct in_default_bind *defbinds; const struct menu_keymap *key_map; @@ -117,6 +122,9 @@ struct in_pdata { const struct menu_keymap *joy_map; size_t jmap_size; const char * const *key_names; + short mod_key; + const struct mod_keymap *mod_keymap; + size_t modmap_size; }; /* to be called by drivers */