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

281 lines
8.3 KiB
C

#include "cell.h"
#include <SDL3/SDL.h>
// SDL_hid_device class
void SDL_hid_device_free(JSRuntime *rt, SDL_hid_device *dev) {
if (dev) SDL_hid_close(dev);
}
QJSCLASS(SDL_hid_device,)
// Bus type to string
static const char *bus_type_to_string(SDL_hid_bus_type type) {
switch (type) {
case SDL_HID_API_BUS_USB: return "usb";
case SDL_HID_API_BUS_BLUETOOTH: return "bluetooth";
case SDL_HID_API_BUS_I2C: return "i2c";
case SDL_HID_API_BUS_SPI: return "spi";
default: return "unknown";
}
}
// Helper to convert device info to JS object
static JSValue device_info_to_js(JSContext *js, SDL_hid_device_info *info) {
JSValue obj = JS_NewObject(js);
if (info->path)
JS_SetPropertyStr(js, obj, "path", JS_NewString(js, info->path));
JS_SetPropertyStr(js, obj, "vendor_id", JS_NewUint32(js, info->vendor_id));
JS_SetPropertyStr(js, obj, "product_id", JS_NewUint32(js, info->product_id));
JS_SetPropertyStr(js, obj, "release_number", JS_NewUint32(js, info->release_number));
JS_SetPropertyStr(js, obj, "usage_page", JS_NewUint32(js, info->usage_page));
JS_SetPropertyStr(js, obj, "usage", JS_NewUint32(js, info->usage));
JS_SetPropertyStr(js, obj, "interface_number", JS_NewInt32(js, info->interface_number));
JS_SetPropertyStr(js, obj, "interface_class", JS_NewInt32(js, info->interface_class));
JS_SetPropertyStr(js, obj, "interface_subclass", JS_NewInt32(js, info->interface_subclass));
JS_SetPropertyStr(js, obj, "interface_protocol", JS_NewInt32(js, info->interface_protocol));
JS_SetPropertyStr(js, obj, "bus_type", JS_NewString(js, bus_type_to_string(info->bus_type)));
// Convert wide strings to UTF-8
if (info->serial_number) {
char buf[256];
wcstombs(buf, info->serial_number, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
JS_SetPropertyStr(js, obj, "serial_number", JS_NewString(js, buf));
}
if (info->manufacturer_string) {
char buf[256];
wcstombs(buf, info->manufacturer_string, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
JS_SetPropertyStr(js, obj, "manufacturer", JS_NewString(js, buf));
}
if (info->product_string) {
char buf[256];
wcstombs(buf, info->product_string, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = 0;
JS_SetPropertyStr(js, obj, "product", JS_NewString(js, buf));
}
return obj;
}
// SDL_hid_init() -> int
JSC_CCALL(hidapi_init,
return JS_NewInt32(js, SDL_hid_init());
)
// SDL_hid_exit() -> int
JSC_CCALL(hidapi_exit,
return JS_NewInt32(js, SDL_hid_exit());
)
// SDL_hid_device_change_count() -> number
JSC_CCALL(hidapi_device_change_count,
return JS_NewUint32(js, SDL_hid_device_change_count());
)
// SDL_hid_enumerate(vendor_id, product_id) -> array of device info
JSC_CCALL(hidapi_enumerate,
uint32_t vendor_id = 0, product_id = 0;
if (argc > 0) JS_ToUint32(js, &vendor_id, argv[0]);
if (argc > 1) JS_ToUint32(js, &product_id, argv[1]);
SDL_hid_device_info *devs = SDL_hid_enumerate(vendor_id, product_id);
if (!devs) return JS_NewArray(js);
JSValue arr = JS_NewArray(js);
int i = 0;
for (SDL_hid_device_info *cur = devs; cur; cur = cur->next) {
JS_SetPropertyUint32(js, arr, i++, device_info_to_js(js, cur));
}
SDL_hid_free_enumeration(devs);
return arr;
)
// SDL_hid_open(vendor_id, product_id) -> device
JSC_CCALL(hidapi_open,
uint32_t vendor_id, product_id;
JS_ToUint32(js, &vendor_id, argv[0]);
JS_ToUint32(js, &product_id, argv[1]);
SDL_hid_device *dev = SDL_hid_open(vendor_id, product_id, NULL);
if (!dev) return JS_NULL;
return SDL_hid_device2js(js, dev);
)
// SDL_hid_open_path(path) -> device
JSC_SCALL(hidapi_open_path,
const char *path = JS_ToCString(js, argv[0]);
if (!path) return JS_EXCEPTION;
SDL_hid_device *dev = SDL_hid_open_path(path);
JS_FreeCString(js, path);
if (!dev) return JS_NULL;
return SDL_hid_device2js(js, dev);
)
// Device instance methods
JSC_CCALL(hidapi_write,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
size_t len;
void *data = js_get_blob_data(js, &len, argv[0]);
if (!data || data == (void*)-1) return JS_ThrowTypeError(js, "Expected ArrayBuffer");
return JS_NewInt32(js, SDL_hid_write(dev, data, len));
)
JSC_CCALL(hidapi_read,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
int length = 64;
if (argc > 0) JS_ToInt32(js, &length, argv[0]);
unsigned char *buf = malloc(length);
if (!buf) return JS_ThrowOutOfMemory(js);
int result = SDL_hid_read(dev, buf, length);
if (result < 0) {
free(buf);
return JS_NULL;
}
ret = js_new_blob_stoned_copy(js, buf, result);
free(buf);
return ret;
)
JSC_CCALL(hidapi_read_timeout,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
int length = 64, timeout = -1;
if (argc > 0) JS_ToInt32(js, &length, argv[0]);
if (argc > 1) JS_ToInt32(js, &timeout, argv[1]);
unsigned char *buf = malloc(length);
if (!buf) return JS_ThrowOutOfMemory(js);
int result = SDL_hid_read_timeout(dev, buf, length, timeout);
if (result < 0) {
free(buf);
return JS_NULL;
}
ret = js_new_blob_stoned_copy(js, buf, result);
free(buf);
return ret;
)
JSC_CCALL(hidapi_set_nonblocking,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
int nonblock = JS_ToBool(js, argv[0]);
return JS_NewInt32(js, SDL_hid_set_nonblocking(dev, nonblock));
)
JSC_CCALL(hidapi_send_feature_report,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
size_t len;
void *data = js_get_blob_data(js, &len, argv[0]);
if (!data || data == (void*)-1) return JS_ThrowTypeError(js, "Expected ArrayBuffer");
return JS_NewInt32(js, SDL_hid_send_feature_report(dev, data, len));
)
JSC_CCALL(hidapi_get_feature_report,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
int length = 64;
if (argc > 0) JS_ToInt32(js, &length, argv[0]);
unsigned char *buf = malloc(length);
if (!buf) return JS_ThrowOutOfMemory(js);
// First byte is report number
buf[0] = 0;
if (argc > 1) {
int report_num;
JS_ToInt32(js, &report_num, argv[1]);
buf[0] = report_num;
}
int result = SDL_hid_get_feature_report(dev, buf, length);
if (result < 0) {
free(buf);
return JS_NULL;
}
ret = js_new_blob_stoned_copy(js, buf, result);
free(buf);
return ret;
)
JSC_CCALL(hidapi_get_input_report,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
int length = 64;
if (argc > 0) JS_ToInt32(js, &length, argv[0]);
unsigned char *buf = malloc(length);
if (!buf) return JS_ThrowOutOfMemory(js);
buf[0] = 0;
if (argc > 1) {
int report_num;
JS_ToInt32(js, &report_num, argv[1]);
buf[0] = report_num;
}
int result = SDL_hid_get_input_report(dev, buf, length);
if (result < 0) {
free(buf);
return JS_NULL;
}
ret = js_new_blob_stoned_copy(js, buf, result);
free(buf);
return ret;
)
JSC_CCALL(hidapi_get_device_info,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
SDL_hid_device_info *info = SDL_hid_get_device_info(dev);
if (!info) return JS_NULL;
return device_info_to_js(js, info);
)
JSC_CCALL(hidapi_close,
SDL_hid_device *dev = js2SDL_hid_device(js, self);
SDL_hid_close(dev);
return JS_NULL;
)
static const JSCFunctionListEntry js_SDL_hid_device_funcs[] = {
MIST_FUNC_DEF(hidapi, write, 1),
MIST_FUNC_DEF(hidapi, read, 1),
MIST_FUNC_DEF(hidapi, read_timeout, 2),
MIST_FUNC_DEF(hidapi, set_nonblocking, 1),
MIST_FUNC_DEF(hidapi, send_feature_report, 1),
MIST_FUNC_DEF(hidapi, get_feature_report, 2),
MIST_FUNC_DEF(hidapi, get_input_report, 2),
MIST_FUNC_DEF(hidapi, get_device_info, 0),
MIST_FUNC_DEF(hidapi, close, 0),
};
static const JSCFunctionListEntry js_hidapi_funcs[] = {
MIST_FUNC_DEF(hidapi, init, 0),
MIST_FUNC_DEF(hidapi, exit, 0),
MIST_FUNC_DEF(hidapi, device_change_count, 0),
MIST_FUNC_DEF(hidapi, enumerate, 2),
MIST_FUNC_DEF(hidapi, open, 2),
MIST_FUNC_DEF(hidapi, open_path, 1),
};
CELL_USE_INIT(
QJSCLASSPREP_FUNCS(SDL_hid_device);
JSValue ret = JS_NewObject(js);
JS_SetPropertyFunctionList(js, ret, js_hidapi_funcs, countof(js_hidapi_funcs));
// Export bus type constants
JS_SetPropertyStr(js, ret, "BUS_UNKNOWN", JS_NewInt32(js, SDL_HID_API_BUS_UNKNOWN));
JS_SetPropertyStr(js, ret, "BUS_USB", JS_NewInt32(js, SDL_HID_API_BUS_USB));
JS_SetPropertyStr(js, ret, "BUS_BLUETOOTH", JS_NewInt32(js, SDL_HID_API_BUS_BLUETOOTH));
JS_SetPropertyStr(js, ret, "BUS_I2C", JS_NewInt32(js, SDL_HID_API_BUS_I2C));
JS_SetPropertyStr(js, ret, "BUS_SPI", JS_NewInt32(js, SDL_HID_API_BUS_SPI));
return ret;
)