Closes internal-consistency item 18. The convention was settled -- AGENTS.md records errctx winning 92 sites to 45 -- but the other 45 were still there, and four files favoured `e` throughout while six favoured errctx, sometimes within one file. Its own commit because it is a rename and nothing else. All 333 changed lines are a single identifier substitution: applying \be\b -> errctx to each removed line reproduces the added line exactly, and the counts match at 333 either way. `e` keeps its meaning where the convention actually wants it -- an incoming error context being inspected, as in akgl_get_json_with_default(e, ...) -- which is why that function was left alone. 25/25 pass, reindent --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
598 lines
21 KiB
C
598 lines
21 KiB
C
/**
|
|
* @file controller.c
|
|
* @brief Implements the controller subsystem.
|
|
*/
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <akerror.h>
|
|
#include <akgl/heap.h>
|
|
#include <akgl/registry.h>
|
|
#include <akgl/game.h>
|
|
#include <akgl/controller.h>
|
|
|
|
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(errctx);
|
|
|
|
FAIL_ZERO_RETURN(errctx, 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(errctx) {
|
|
} FINISH(errctx, true);
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
akerr_ErrorContext *akgl_controller_open_gamepads(void)
|
|
{
|
|
int count = 0;
|
|
int i = 0;
|
|
SDL_JoystickID *gamepads = NULL;
|
|
SDL_Gamepad *gamepad = NULL;
|
|
|
|
PREPARE_ERROR(errctx);
|
|
if ( SDL_HasGamepad() ) {
|
|
gamepads = SDL_GetGamepads(&count);
|
|
if ( count > 0 ) {
|
|
FAIL_ZERO_RETURN(errctx, gamepads, AKERR_NULLPOINTER, "%s", SDL_GetError());
|
|
for ( i = 0; i < count ; i++ ) {
|
|
gamepad = SDL_OpenGamepad(gamepads[i]);
|
|
FAIL_ZERO_RETURN(errctx, 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(errctx);
|
|
}
|
|
|
|
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_BREAK(errctx, control, AKERR_NULLPOINTER, "NULL Control");
|
|
FAIL_NONZERO_BREAK(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_BREAK(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_BREAK(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));
|
|
} CLEANUP {
|
|
} PROCESS(errctx) {
|
|
} FINISH(errctx, true);
|
|
// The SUCCEED_RETURN used to sit at the end of the ATTEMPT block, which
|
|
// returned past CLEANUP and left this function with no return statement at
|
|
// all on the path that falls out of FINISH.
|
|
SUCCEED_RETURN(errctx);
|
|
}
|
|
|
|
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);
|
|
}
|