diff --git a/in_sdl.c b/in_sdl.c index a84c781..518e8cf 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,97 @@ static int get_keystate(keybits_t *keystate, int sym) return !!(*ks_word & mask); } +static int translate_keystate(struct in_sdl_state *state, SDL_Event *event) +{ + const struct in_pdata *pdata = state->drv->pdata; + const struct mod_keymap *map; + SDL_Event new_event; + short key = (short)event->key.keysym.sym; + uint8_t type = event->type; + short mod_key = pdata->mod_key; + int i; + int ret = 1; + + if (!mod_key) + return 1; + + if (state->mod_state == MOD_NO && key != mod_key) + return 1; + + if (key == mod_key) { + 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(state->keystate, map->inkey)) { + state->mod_state = MOD_YES; + new_event.type = SDL_KEYUP; + new_event.key.state = SDL_RELEASED; + new_event.key.keysym.sym = map->inkey; + SDL_PushEvent(&new_event); + + new_event.type = SDL_KEYDOWN; + new_event.key.state = SDL_PRESSED; + new_event.key.keysym.sym = map->outkey; + SDL_PushEvent(&new_event); + } + } + + ret = -1; + } else if (state->mod_state == MOD_MAYBE) { + /* Released mod without combo, simulate down and up */ + SDL_PushEvent(event); + state->mod_state = MOD_NO; + event->type = SDL_KEYDOWN; + ret = 1; + } else if (state->mod_state == MOD_YES) { + /* Released mod, switch all mod keys to unmod */ + state->mod_state = MOD_NO; + + for (i = 0; i < pdata->modmap_size; i++) { + map = &pdata->mod_keymap[i]; + + if (get_keystate(state->keystate, map->outkey)) { + state->mod_state = MOD_NO; + new_event.type = SDL_KEYUP; + new_event.key.state = SDL_RELEASED; + new_event.key.keysym.sym = map->outkey; + SDL_PushEvent(&new_event); + + new_event.type = SDL_KEYDOWN; + new_event.key.state = SDL_PRESSED; + new_event.key.keysym.sym = map->inkey; + SDL_PushEvent(&new_event); + } + } + ret = -1; + } + } else { + for (i = 0; i < pdata->modmap_size; i++) { + map = &pdata->mod_keymap[i]; + + if (map->inkey == key) { + state->mod_state = MOD_YES; + + /* Releasing the original key? Allow it through, + * Otherwise, replace with modified key. */ + if (!(event->type == SDL_KEYUP && + get_keystate(state->keystate, map->inkey))) + event->key.keysym.sym = map->outkey; + + break; + } + } + ret = 1; + } + + return ret; +} + static int handle_event(struct in_sdl_state *state, SDL_Event *event, int *kc_out, int *down_out, int *emu_out) { @@ -267,6 +365,9 @@ static int handle_event(struct in_sdl_state *state, SDL_Event *event, if (event->type != SDL_KEYDOWN && event->type != SDL_KEYUP) return -1; + if (translate_keystate(state, event) < 0) + return -1; + emu = get_keystate(state->emu_keys, event->key.keysym.sym); update_keystate(state->keystate, event->key.keysym.sym, event->type == SDL_KEYDOWN); 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 */