Files
cell-sdl3/joystick.c
2025-12-11 10:39:24 -06:00

394 lines
12 KiB
C

#include "cell.h"
#include <SDL3/SDL.h>
// Joystick type enum to string
static const char *joystick_type_to_string(SDL_JoystickType type) {
switch (type) {
case SDL_JOYSTICK_TYPE_GAMEPAD: return "gamepad";
case SDL_JOYSTICK_TYPE_WHEEL: return "wheel";
case SDL_JOYSTICK_TYPE_ARCADE_STICK: return "arcade_stick";
case SDL_JOYSTICK_TYPE_FLIGHT_STICK: return "flight_stick";
case SDL_JOYSTICK_TYPE_DANCE_PAD: return "dance_pad";
case SDL_JOYSTICK_TYPE_GUITAR: return "guitar";
case SDL_JOYSTICK_TYPE_DRUM_KIT: return "drum_kit";
case SDL_JOYSTICK_TYPE_ARCADE_PAD: return "arcade_pad";
case SDL_JOYSTICK_TYPE_THROTTLE: return "throttle";
default: return "unknown";
}
}
// Connection state to string
static const char *connection_state_to_string(SDL_JoystickConnectionState state) {
switch (state) {
case SDL_JOYSTICK_CONNECTION_WIRED: return "wired";
case SDL_JOYSTICK_CONNECTION_WIRELESS: return "wireless";
case SDL_JOYSTICK_CONNECTION_UNKNOWN: return "unknown";
default: return "invalid";
}
}
// SDL_Joystick class
void SDL_Joystick_free(JSRuntime *rt, SDL_Joystick *joystick) {
if (joystick) SDL_CloseJoystick(joystick);
}
QJSCLASS(SDL_Joystick,)
// SDL_LockJoysticks()
JSC_CCALL(joystick_lock,
SDL_LockJoysticks();
return JS_NULL;
)
// SDL_UnlockJoysticks()
JSC_CCALL(joystick_unlock,
SDL_UnlockJoysticks();
return JS_NULL;
)
// SDL_HasJoystick() -> bool
JSC_CCALL(joystick_has,
return JS_NewBool(js, SDL_HasJoystick());
)
// SDL_GetJoysticks() -> array of joystick IDs
JSC_CCALL(joystick_get_joysticks,
int count = 0;
SDL_JoystickID *joysticks = SDL_GetJoysticks(&count);
if (!joysticks) return JS_NewArray(js);
JSValue arr = JS_NewArray(js);
for (int i = 0; i < count; i++) {
JS_SetPropertyUint32(js, arr, i, JS_NewUint32(js, joysticks[i]));
}
SDL_free(joysticks);
return arr;
)
// SDL_GetJoystickNameForID(id) -> string
JSC_CCALL(joystick_get_name_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
const char *name = SDL_GetJoystickNameForID(id);
return name ? JS_NewString(js, name) : JS_NULL;
)
// SDL_GetJoystickPathForID(id) -> string
JSC_CCALL(joystick_get_path_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
const char *path = SDL_GetJoystickPathForID(id);
return path ? JS_NewString(js, path) : JS_NULL;
)
// SDL_GetJoystickPlayerIndexForID(id) -> number
JSC_CCALL(joystick_get_player_index_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
return JS_NewInt32(js, SDL_GetJoystickPlayerIndexForID(id));
)
// SDL_GetJoystickGUIDForID(id) -> string
JSC_CCALL(joystick_get_guid_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
SDL_GUID guid = SDL_GetJoystickGUIDForID(id);
char buf[64];
SDL_GUIDToString(guid, buf, sizeof(buf));
return JS_NewString(js, buf);
)
// SDL_GetJoystickVendorForID(id) -> number
JSC_CCALL(joystick_get_vendor_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
return JS_NewUint32(js, SDL_GetJoystickVendorForID(id));
)
// SDL_GetJoystickProductForID(id) -> number
JSC_CCALL(joystick_get_product_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
return JS_NewUint32(js, SDL_GetJoystickProductForID(id));
)
// SDL_GetJoystickProductVersionForID(id) -> number
JSC_CCALL(joystick_get_product_version_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
return JS_NewUint32(js, SDL_GetJoystickProductVersionForID(id));
)
// SDL_GetJoystickTypeForID(id) -> string
JSC_CCALL(joystick_get_type_for_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
SDL_JoystickType type = SDL_GetJoystickTypeForID(id);
return JS_NewString(js, joystick_type_to_string(type));
)
// SDL_OpenJoystick(id) -> Joystick object
JSC_CCALL(joystick_open,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
SDL_Joystick *joystick = SDL_OpenJoystick(id);
if (!joystick) return JS_NULL;
return SDL_Joystick2js(js, joystick);
)
// SDL_GetJoystickFromID(id) -> Joystick object (does not take ownership)
JSC_CCALL(joystick_get_from_id,
uint32_t id;
JS_ToUint32(js, &id, argv[0]);
SDL_Joystick *joystick = SDL_GetJoystickFromID(id);
if (!joystick) return JS_NULL;
// Note: This returns an existing joystick, not a new one
// We should not free it when the JS object is garbage collected
return SDL_Joystick2js(js, joystick);
)
// SDL_GetJoystickFromPlayerIndex(player_index) -> Joystick object
JSC_CCALL(joystick_get_from_player_index,
int player_index;
JS_ToInt32(js, &player_index, argv[0]);
SDL_Joystick *joystick = SDL_GetJoystickFromPlayerIndex(player_index);
if (!joystick) return JS_NULL;
return SDL_Joystick2js(js, joystick);
)
// Joystick instance methods
JSC_CCALL(joystick_get_name,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
const char *name = SDL_GetJoystickName(joystick);
return name ? JS_NewString(js, name) : JS_NULL;
)
JSC_CCALL(joystick_get_path,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
const char *path = SDL_GetJoystickPath(joystick);
return path ? JS_NewString(js, path) : JS_NULL;
)
JSC_CCALL(joystick_get_player_index,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewInt32(js, SDL_GetJoystickPlayerIndex(joystick));
)
JSC_CCALL(joystick_set_player_index,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
int player_index;
JS_ToInt32(js, &player_index, argv[0]);
return JS_NewBool(js, SDL_SetJoystickPlayerIndex(joystick, player_index));
)
JSC_CCALL(joystick_get_guid,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
SDL_GUID guid = SDL_GetJoystickGUID(joystick);
char buf[64];
SDL_GUIDToString(guid, buf, sizeof(buf));
return JS_NewString(js, buf);
)
JSC_CCALL(joystick_get_vendor,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewUint32(js, SDL_GetJoystickVendor(joystick));
)
JSC_CCALL(joystick_get_product,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewUint32(js, SDL_GetJoystickProduct(joystick));
)
JSC_CCALL(joystick_get_product_version,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewUint32(js, SDL_GetJoystickProductVersion(joystick));
)
JSC_CCALL(joystick_get_serial,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
const char *serial = SDL_GetJoystickSerial(joystick);
return serial ? JS_NewString(js, serial) : JS_NULL;
)
JSC_CCALL(joystick_get_type,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
SDL_JoystickType type = SDL_GetJoystickType(joystick);
return JS_NewString(js, joystick_type_to_string(type));
)
JSC_CCALL(joystick_get_id,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewUint32(js, SDL_GetJoystickID(joystick));
)
JSC_CCALL(joystick_get_num_axes,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewInt32(js, SDL_GetNumJoystickAxes(joystick));
)
JSC_CCALL(joystick_get_num_balls,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewInt32(js, SDL_GetNumJoystickBalls(joystick));
)
JSC_CCALL(joystick_get_num_hats,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewInt32(js, SDL_GetNumJoystickHats(joystick));
)
JSC_CCALL(joystick_get_num_buttons,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
return JS_NewInt32(js, SDL_GetNumJoystickButtons(joystick));
)
JSC_CCALL(joystick_get_axis,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
int axis;
JS_ToInt32(js, &axis, argv[0]);
return JS_NewInt32(js, SDL_GetJoystickAxis(joystick, axis));
)
JSC_CCALL(joystick_get_ball,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
int ball;
JS_ToInt32(js, &ball, argv[0]);
int dx, dy;
if (!SDL_GetJoystickBall(joystick, ball, &dx, &dy)) return JS_NULL;
JSValue result = JS_NewObject(js);
JS_SetPropertyStr(js, result, "dx", JS_NewInt32(js, dx));
JS_SetPropertyStr(js, result, "dy", JS_NewInt32(js, dy));
return result;
)
JSC_CCALL(joystick_get_hat,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
int hat;
JS_ToInt32(js, &hat, argv[0]);
return JS_NewUint32(js, SDL_GetJoystickHat(joystick, hat));
)
JSC_CCALL(joystick_get_button,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
int button;
JS_ToInt32(js, &button, argv[0]);
return JS_NewBool(js, SDL_GetJoystickButton(joystick, button));
)
JSC_CCALL(joystick_rumble,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
uint32_t low_freq, high_freq, duration_ms;
JS_ToUint32(js, &low_freq, argv[0]);
JS_ToUint32(js, &high_freq, argv[1]);
JS_ToUint32(js, &duration_ms, argv[2]);
return JS_NewBool(js, SDL_RumbleJoystick(joystick, low_freq, high_freq, duration_ms));
)
JSC_CCALL(joystick_rumble_triggers,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
uint32_t left, right, duration_ms;
JS_ToUint32(js, &left, argv[0]);
JS_ToUint32(js, &right, argv[1]);
JS_ToUint32(js, &duration_ms, argv[2]);
return JS_NewBool(js, SDL_RumbleJoystickTriggers(joystick, left, right, duration_ms));
)
JSC_CCALL(joystick_set_led,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
int r, g, b;
JS_ToInt32(js, &r, argv[0]);
JS_ToInt32(js, &g, argv[1]);
JS_ToInt32(js, &b, argv[2]);
return JS_NewBool(js, SDL_SetJoystickLED(joystick, r, g, b));
)
JSC_CCALL(joystick_get_connection_state,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
SDL_JoystickConnectionState state = SDL_GetJoystickConnectionState(joystick);
return JS_NewString(js, connection_state_to_string(state));
)
JSC_CCALL(joystick_get_power_info,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
int percent;
SDL_PowerState state = SDL_GetJoystickPowerInfo(joystick, &percent);
JSValue result = JS_NewObject(js);
const char *state_str;
switch (state) {
case SDL_POWERSTATE_ON_BATTERY: state_str = "on_battery"; break;
case SDL_POWERSTATE_NO_BATTERY: state_str = "no_battery"; break;
case SDL_POWERSTATE_CHARGING: state_str = "charging"; break;
case SDL_POWERSTATE_CHARGED: state_str = "charged"; break;
default: state_str = "unknown"; break;
}
JS_SetPropertyStr(js, result, "state", JS_NewString(js, state_str));
JS_SetPropertyStr(js, result, "percent", JS_NewInt32(js, percent));
return result;
)
JSC_CCALL(joystick_close,
SDL_Joystick *joystick = js2SDL_Joystick(js, self);
SDL_CloseJoystick(joystick);
return JS_NULL;
)
static const JSCFunctionListEntry js_SDL_Joystick_funcs[] = {
MIST_FUNC_DEF(joystick, get_name, 0),
MIST_FUNC_DEF(joystick, get_path, 0),
MIST_FUNC_DEF(joystick, get_player_index, 0),
MIST_FUNC_DEF(joystick, set_player_index, 1),
MIST_FUNC_DEF(joystick, get_guid, 0),
MIST_FUNC_DEF(joystick, get_vendor, 0),
MIST_FUNC_DEF(joystick, get_product, 0),
MIST_FUNC_DEF(joystick, get_product_version, 0),
MIST_FUNC_DEF(joystick, get_serial, 0),
MIST_FUNC_DEF(joystick, get_type, 0),
MIST_FUNC_DEF(joystick, get_id, 0),
MIST_FUNC_DEF(joystick, get_num_axes, 0),
MIST_FUNC_DEF(joystick, get_num_balls, 0),
MIST_FUNC_DEF(joystick, get_num_hats, 0),
MIST_FUNC_DEF(joystick, get_num_buttons, 0),
MIST_FUNC_DEF(joystick, get_axis, 1),
MIST_FUNC_DEF(joystick, get_ball, 1),
MIST_FUNC_DEF(joystick, get_hat, 1),
MIST_FUNC_DEF(joystick, get_button, 1),
MIST_FUNC_DEF(joystick, rumble, 3),
MIST_FUNC_DEF(joystick, rumble_triggers, 3),
MIST_FUNC_DEF(joystick, set_led, 3),
MIST_FUNC_DEF(joystick, get_connection_state, 0),
MIST_FUNC_DEF(joystick, get_power_info, 0),
MIST_FUNC_DEF(joystick, close, 0),
};
static const JSCFunctionListEntry js_joystick_funcs[] = {
MIST_FUNC_DEF(joystick, lock, 0),
MIST_FUNC_DEF(joystick, unlock, 0),
MIST_FUNC_DEF(joystick, has, 0),
MIST_FUNC_DEF(joystick, get_joysticks, 0),
MIST_FUNC_DEF(joystick, get_name_for_id, 1),
MIST_FUNC_DEF(joystick, get_path_for_id, 1),
MIST_FUNC_DEF(joystick, get_player_index_for_id, 1),
MIST_FUNC_DEF(joystick, get_guid_for_id, 1),
MIST_FUNC_DEF(joystick, get_vendor_for_id, 1),
MIST_FUNC_DEF(joystick, get_product_for_id, 1),
MIST_FUNC_DEF(joystick, get_product_version_for_id, 1),
MIST_FUNC_DEF(joystick, get_type_for_id, 1),
MIST_FUNC_DEF(joystick, open, 1),
MIST_FUNC_DEF(joystick, get_from_id, 1),
MIST_FUNC_DEF(joystick, get_from_player_index, 1),
};
CELL_USE_INIT(
SDL_Init(SDL_INIT_JOYSTICK);
QJSCLASSPREP_FUNCS(SDL_Joystick);
JSValue ret = JS_NewObject(js);
JS_SetPropertyFunctionList(js, ret, js_joystick_funcs, countof(js_joystick_funcs));
// Export axis constants
JS_SetPropertyStr(js, ret, "AXIS_MAX", JS_NewInt32(js, SDL_JOYSTICK_AXIS_MAX));
JS_SetPropertyStr(js, ret, "AXIS_MIN", JS_NewInt32(js, SDL_JOYSTICK_AXIS_MIN));
return ret;
)