/** * @file controller.c * @brief Implements the controller subsystem. */ #include #include #include #include #include #include akgl_ControlMap akgl_controlmaps[AKGL_MAX_CONTROL_MAPS]; /* * Keystrokes waiting for akgl_controller_poll_key() and * akgl_controller_poll_keystroke(). Filled by akgl_controller_handle_event() * before it consults the control maps, so a key that also drives an actor is * still delivered to a polling caller. * * head is the next slot to read, count is how many are waiting. Both index a * fixed array rather than a queue object, which is the whole point: a host that * never polls cannot make this grow. */ static akgl_Keystroke keybuffer[AKGL_CONTROLLER_KEY_BUFFER]; static int keybuffer_head = 0; static int keybuffer_count = 0; /* * Whether the newest entry is a key press that has not been given its composed * text yet. SDL reports one keystroke as two events -- the key going down, and * then the text it composed to, if it composed to anything -- so the text that * arrives next belongs to the press that arrived last. Without this flag a * press dropped by a full buffer would hand its text to whatever older press * happened to still be sitting at the end of the ring. */ static bool keybuffer_awaiting_text = false; /** * @brief Length in bytes of the UTF-8 sequence @p src begins with, or 0. * * 0 both for a byte that cannot start a sequence and for a sequence the string * ends in the middle of. SDL should hand over neither, and reading past a * terminator to find out otherwise is not a risk worth taking for input that * arrives from a keyboard driver. * * @param src UTF-8 text, NUL terminated. Required. */ static size_t utf8_sequence_length(const char *src) { unsigned char lead = (unsigned char)src[0]; size_t len = 0; size_t i = 0; if ( lead < 0x80 ) { return 1; } else if ( (lead & 0xe0) == 0xc0 ) { len = 2; } else if ( (lead & 0xf0) == 0xe0 ) { len = 3; } else if ( (lead & 0xf8) == 0xf0 ) { len = 4; } else { return 0; } for ( i = 1; i < len; i++ ) { // A NUL fails this too, which is what stops the scan at the end of a // truncated string instead of past it. if ( ((unsigned char)src[i] & 0xc0) != 0x80 ) { return 0; } } return len; } /** * @brief Copy as much of @p src as fits in @p dest without splitting a character. * * SDL hands over whatever the platform composed, which for an input method can * be several characters at once, and the ring entry holds one. Truncating on a * byte boundary would leave a partial UTF-8 sequence that nothing downstream * can render, so this stops at the last code point that fits whole. * * @param dest Receives the prefix, always NUL terminated. Required. * @param destsize Bytes available in @p dest, terminator included. * @param src UTF-8 text to copy. Required. */ static void copy_utf8_prefix(char *dest, size_t destsize, const char *src) { size_t used = 0; size_t len = 0; dest[0] = '\0'; while ( src[used] != '\0' ) { len = utf8_sequence_length(&src[used]); if ( len == 0 ) { // Not valid UTF-8 from here on. Keep what is already known good. break; } if ( (used + len) >= destsize ) { break; } memcpy(&dest[used], &src[used], len); used += len; } dest[used] = '\0'; } /** * @brief Record one key press, discarding it if the buffer is already full. * * Dropping the newest rather than overwriting the oldest is deliberate. A * caller reading a line of input wants the characters that were typed first; * overwriting would hand it the tail of what the user typed and silently lose * the head. * * The entry starts with no text. Whatever the press composes to arrives as a * separate event and is attached by keybuffer_attach_text(). */ static void keybuffer_push_key(SDL_Keycode key, SDL_Keymod mod) { int tail = 0; if ( keybuffer_count >= AKGL_CONTROLLER_KEY_BUFFER ) { keybuffer_awaiting_text = false; return; } tail = (keybuffer_head + keybuffer_count) % AKGL_CONTROLLER_KEY_BUFFER; keybuffer[tail].key = key; keybuffer[tail].mod = mod; keybuffer[tail].text[0] = '\0'; keybuffer_count += 1; keybuffer_awaiting_text = true; } /** * @brief Give composed text to the press it belongs to, or buffer it alone. * * An entry with no keycode is not a degenerate case to be avoided: an input * method or a dead key finishes a character with no key press of its own, and a * line editor wants that character. akgl_controller_poll_keystroke() reports it; * akgl_controller_poll_key(), which has no keycode to give, discards it. */ static void keybuffer_attach_text(const char *text) { int newest = 0; if ( (keybuffer_awaiting_text == true) && (keybuffer_count > 0) ) { newest = (keybuffer_head + keybuffer_count - 1) % AKGL_CONTROLLER_KEY_BUFFER; copy_utf8_prefix(keybuffer[newest].text, AKGL_CONTROLLER_KEYSTROKE_TEXT, text); keybuffer_awaiting_text = false; return; } if ( keybuffer_count >= AKGL_CONTROLLER_KEY_BUFFER ) { return; } newest = (keybuffer_head + keybuffer_count) % AKGL_CONTROLLER_KEY_BUFFER; keybuffer[newest].key = 0; keybuffer[newest].mod = 0; copy_utf8_prefix(keybuffer[newest].text, AKGL_CONTROLLER_KEYSTROKE_TEXT, text); keybuffer_count += 1; } /** * @brief Take the oldest entry, whatever it carries. * * @param dest Receives the entry. Required. * @return `true` when an entry was taken, `false` when the buffer was empty. */ static bool keybuffer_take(akgl_Keystroke *dest) { if ( keybuffer_count == 0 ) { return false; } memcpy(dest, &keybuffer[keybuffer_head], sizeof(akgl_Keystroke)); keybuffer_head = (keybuffer_head + 1) % AKGL_CONTROLLER_KEY_BUFFER; keybuffer_count -= 1; if ( keybuffer_count == 0 ) { // Nothing is left for a text event to attach itself to. keybuffer_awaiting_text = false; } return true; } akerr_ErrorContext *akgl_controller_list_keyboards(void) { int count; SDL_KeyboardID *keyboards = SDL_GetKeyboards(&count); PREPARE_ERROR(e); FAIL_ZERO_RETURN(e, keyboards, AKERR_NULLPOINTER, "%s", SDL_GetError()); // The array is SDL's to allocate and ours to free -- that is the contract on // every SDL_Get*s() enumeration. Released in CLEANUP so the loop can fail // without taking the array with it. ATTEMPT { for (int i = 0; i < count; i++) { const char *name = SDL_GetKeyboardNameForID(keyboards[i]); SDL_Log("Keyboard %d: ID %u, Name: %s\n", i, keyboards[i], name); } } CLEANUP { SDL_free(keyboards); keyboards = NULL; } PROCESS(e) { } FINISH(e, true); SUCCEED_RETURN(e); } akerr_ErrorContext *akgl_controller_open_gamepads(void) { int count = 0; int i = 0; SDL_JoystickID *gamepads = NULL; SDL_Gamepad *gamepad = NULL; PREPARE_ERROR(e); if ( SDL_HasGamepad() ) { gamepads = SDL_GetGamepads(&count); if ( count > 0 ) { FAIL_ZERO_RETURN(e, gamepads, AKERR_NULLPOINTER, "%s", SDL_GetError()); for ( i = 0; i < count ; i++ ) { gamepad = SDL_OpenGamepad(gamepads[i]); FAIL_ZERO_RETURN(e, gamepad, AKERR_NULLPOINTER, "%s", SDL_GetError()); SDL_Log("Gamepad %d is %s", i, SDL_GetGamepadNameForID(gamepads[i])); } SDL_free(gamepads); } else { SDL_Log("No gamepads enumerated"); } } else { SDL_Log("No gamepads connected"); } SUCCEED_RETURN(e); } akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *event) { int i = 0; int j = 0; int eventButtonComboMatch = 0; akgl_ControlMap *curmap = NULL; akgl_Control *curcontrol = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); // Before the control maps, not after: a key bound to an actor is still a // key an interpreter polling with akgl_controller_poll_key() wants to see, // and the scan below returns as soon as a binding claims the event. if ( event->type == SDL_EVENT_KEY_DOWN ) { keybuffer_push_key(event->key.key, event->key.mod); } else if ( event->type == SDL_EVENT_TEXT_INPUT ) { // The character SDL composed for the press recorded a moment ago. A // keycode cannot express it: the layout, the shift state, a compose key // and a dead key all have their say between the two events. keybuffer_attach_text(event->text.text); } ATTEMPT { for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) { curmap = &akgl_controlmaps[i]; if ( curmap->target == NULL ) { continue; } //SDL_Log("Control map %d maps to actor %s", i, curmap->target->name); //SDL_Log("event from keyboard %d", event->key.which); for ( j = 0; j < AKGL_MAX_CONTROLS; j++ ) { curcontrol = &curmap->controls[j]; //SDL_Log("button/key is processed by controlmap %d control %d", i, j); //SDL_Log("event %d -> control on %d off %d", event->type, curcontrol->event_on, curcontrol->event_off); // This controlmap processes this control eventButtonComboMatch = ( ((event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN || event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) && event->gbutton.which == curmap->jsid && event->gbutton.button == curcontrol->button) || ((event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP) && event->key.which == curmap->kbid && event->key.key == curcontrol->key) ); if ( event->type == 768 && event->key.which == 11 && event->key.key == 13 ) { SDL_Log("Event type=%d, keyboard=%d, key=%d", event->type, event->key.which, event->key.key); SDL_Log("ControlMap[%d].Controls[%d] keyboard=%d, key=%d", i, j, curmap->kbid, curcontrol->key); SDL_Log("event %d -> control on %d off %d", event->type, curcontrol->event_on, curcontrol->event_off); SDL_Log("eventButtonComboMatch for controlmap %d id %d = %d", i, j, eventButtonComboMatch); } if ( event->type == curcontrol->event_on && eventButtonComboMatch) { CATCH(errctx, curcontrol->handler_on(curmap->target, event)); goto _akgl_controller_handle_event_success; } else if ( event->type == curcontrol->event_off && eventButtonComboMatch ) { CATCH(errctx, curcontrol->handler_off(curmap->target, event)); goto _akgl_controller_handle_event_success; } } } _akgl_controller_handle_event_success: } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_handle_button_down(void *appstate, SDL_Event *event) { akgl_Actor *player = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); player = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL); FAIL_ZERO_RETURN(errctx, player, AKERR_NULLPOINTER, "Player actor does not exist"); if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN || event->key.key == SDLK_DOWN ) { SDL_Log("Processing dpad down : state %d", player->state); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_DOWN); if ( !player->movement_controls_face ) { AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_DOWN); } SDL_Log("New state : %d", player->state); } else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP || event->key.key == SDLK_UP ) { SDL_Log("Processing dpad up"); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_UP); if ( !player->movement_controls_face ) { AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_UP); } SDL_Log("New state : %d", player->state); } else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT || event->key.key == SDLK_LEFT ) { SDL_Log("Processing dpad left"); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_LEFT); if ( !player->movement_controls_face ) { AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_LEFT); } SDL_Log("New state : %d", player->state); } else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT || event->key.key == SDLK_RIGHT ) { SDL_Log("Processing dpad right"); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT); if ( !player->movement_controls_face ) { AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL); AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_RIGHT); } SDL_Log("New state : %d", player->state); } SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_handle_button_up(void *appstate, SDL_Event *event) { akgl_Actor *player = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); player = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, "player", NULL); FAIL_ZERO_RETURN(errctx, player, AKERR_NULLPOINTER, "Player actor does not exist"); if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN || event->key.key == SDLK_DOWN ) { SDL_Log("processing down release"); AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_DOWN); player->curSpriteFrameId = 0; SDL_Log("New state : %d", player->state); } else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP || event->key.key == SDLK_UP ) { SDL_Log("processing up release"); AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_UP); player->curSpriteFrameId = 0; SDL_Log("New state : %d", player->state); } else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT || event->key.key == SDLK_RIGHT) { SDL_Log("processing right release"); AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT); player->curSpriteFrameId = 0; SDL_Log("New state : %d", player->state); } else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT || event->key.key == SDLK_LEFT ) { SDL_Log("processing left release"); AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_LEFT); player->curSpriteFrameId = 0; SDL_Log("New state : %d", player->state); } SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_handle_added(void *appstate, SDL_Event *event) { SDL_JoystickID which; SDL_Gamepad *gamepad = NULL; char *mapping = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); which = event->gbutton.which; gamepad = SDL_GetGamepadFromID(which); if (!gamepad) { SDL_Log("Gamepad #%u add, but not opened: %s", (unsigned int) which, SDL_GetError()); gamepad = SDL_OpenGamepad(which); SDL_Log("Gamepad #%u opened: %s", (unsigned int) which, SDL_GetError()); mapping = SDL_GetGamepadMapping(gamepad); if ( mapping == NULL ) { SDL_Log("Gamepad #%u has no mapping!", (unsigned int) which); } else if ( mapping != NULL ) { SDL_Log("Gamepad #%u mapping : %s", (unsigned int) which, mapping); SDL_free(mapping); } } else { SDL_Log("Gamepad #%u ('%s') added", (unsigned int) which, SDL_GetGamepadName(gamepad)); } SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_handle_removed(void *appstate, SDL_Event *event) { SDL_JoystickID which; SDL_Gamepad *gamepad = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate"); FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event"); which = event->gbutton.which; gamepad = SDL_GetGamepadFromID(which); if (gamepad) { SDL_CloseGamepad(gamepad); /* the joystick was unplugged. */ } SDL_Log("Gamepad #%u removed", (unsigned int) which); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_pushmap(int controlmapid, akgl_Control *control) { int newmapid = 0; PREPARE_ERROR(errctx); ATTEMPT { FAIL_ZERO_RETURN(errctx, control, AKERR_NULLPOINTER, "NULL Control"); FAIL_NONZERO_RETURN(errctx, (controlmapid >= AKGL_MAX_CONTROL_MAPS), AKERR_OUTOFBOUNDS, "ID %d exceeds maximum %d", controlmapid, AKGL_MAX_CONTROL_MAPS); newmapid = akgl_controlmaps[controlmapid].nextMap; FAIL_ZERO_RETURN(errctx, (AKGL_MAX_CONTROLS - newmapid), AKERR_OUTOFBOUNDS, "Control map ID %d is full", controlmapid); memcpy((void *)&akgl_controlmaps[controlmapid].controls[newmapid], control, sizeof(akgl_Control)); akgl_controlmaps[controlmapid].nextMap = newmapid + 1; } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid) { akgl_ControlMap *controlmap; akgl_Control control; PREPARE_ERROR(errctx); ATTEMPT { // set up the control map FAIL_NONZERO_RETURN(errctx, (controlmapid >= AKGL_MAX_CONTROL_MAPS), AKERR_OUTOFBOUNDS, "ID %d exceeds maximum %d", controlmapid, AKGL_MAX_CONTROL_MAPS); memset((void *)&control, 0x00, sizeof(akgl_Control)); controlmap = &akgl_controlmaps[controlmapid]; controlmap->kbid = kbid; controlmap->jsid = jsid; controlmap->target = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, actorname, NULL); FAIL_ZERO_BREAK(errctx, controlmap->target, AKGL_ERR_REGISTRY, "Actor %s not found in registry", actorname); // ---- KEYBOARD CONTROLS ---- // Move down control.key = SDLK_DOWN; control.event_on = SDL_EVENT_KEY_DOWN; control.event_off = SDL_EVENT_KEY_UP; control.handler_on = &akgl_actor_cmhf_down_on; control.handler_off = &akgl_actor_cmhf_down_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); // Move up control.key = SDLK_UP; control.event_on = SDL_EVENT_KEY_DOWN; control.event_off = SDL_EVENT_KEY_UP; control.handler_on = &akgl_actor_cmhf_up_on; control.handler_off = &akgl_actor_cmhf_up_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); // Move left control.key = SDLK_LEFT; control.event_on = SDL_EVENT_KEY_DOWN; control.event_off = SDL_EVENT_KEY_UP; control.handler_on = &akgl_actor_cmhf_left_on; control.handler_off = &akgl_actor_cmhf_left_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); // Move right control.key = SDLK_RIGHT; control.event_on = SDL_EVENT_KEY_DOWN; control.event_off = SDL_EVENT_KEY_UP; control.handler_on = &akgl_actor_cmhf_right_on; control.handler_off = &akgl_actor_cmhf_right_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); control.key = 0; // ----- GAMEPAD CONTROLS // Move down control.button = SDL_GAMEPAD_BUTTON_DPAD_DOWN; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.handler_on = &akgl_actor_cmhf_down_on; control.handler_off = &akgl_actor_cmhf_down_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); // Move up control.button = SDL_GAMEPAD_BUTTON_DPAD_UP; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.handler_on = &akgl_actor_cmhf_up_on; control.handler_off = &akgl_actor_cmhf_up_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); // Move left control.button = SDL_GAMEPAD_BUTTON_DPAD_LEFT; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.handler_on = &akgl_actor_cmhf_left_on; control.handler_off = &akgl_actor_cmhf_left_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); // Move right control.button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT; control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN; control.event_off = SDL_EVENT_GAMEPAD_BUTTON_UP; control.handler_on = &akgl_actor_cmhf_right_on; control.handler_off = &akgl_actor_cmhf_right_off; CATCH(errctx, akgl_controller_pushmap(controlmapid, &control)); SUCCEED_RETURN(errctx); } CLEANUP { } PROCESS(errctx) { } FINISH(errctx, true); } akerr_ErrorContext *akgl_controller_poll_key(int *keycode, bool *available) { akgl_Keystroke keystroke; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, keycode, AKERR_NULLPOINTER, "NULL keycode destination"); FAIL_ZERO_RETURN(errctx, available, AKERR_NULLPOINTER, "NULL availability destination"); // An empty buffer is the ordinary case, not a failure: the caller is asking // whether a key is waiting, and the answer is no. *keycode = 0; *available = false; while ( keybuffer_take(&keystroke) == true ) { if ( keystroke.key == 0 ) { // Composed text with no key behind it. This form has no way to // report it, so it is dropped rather than handed back as key 0. continue; } *keycode = (int)keystroke.key; *available = true; break; } SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_poll_keystroke(akgl_Keystroke *dest, bool *available) { PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL keystroke destination"); FAIL_ZERO_RETURN(errctx, available, AKERR_NULLPOINTER, "NULL availability destination"); memset(dest, 0x00, sizeof(akgl_Keystroke)); *available = keybuffer_take(dest); SUCCEED_RETURN(errctx); } akerr_ErrorContext *akgl_controller_flush_keys(void) { PREPARE_ERROR(errctx); keybuffer_head = 0; keybuffer_count = 0; keybuffer_awaiting_text = false; SUCCEED_RETURN(errctx); }