/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2016-2019 - Brad Parker * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #include #include #include #include #include #include #include #include "../input_driver.h" #include "../input_keymaps.h" #include "../../verbosity.h" #include "../../gfx/video_driver.h" #define MAX_TEST_STEPS 200 #define INPUT_TEST_COMMAND_PRESS_KEY 1 #define INPUT_TEST_COMMAND_RELEASE_KEY 2 #define INPUT_TEST_COMMAND_SET_SENSOR_ACC_X 10 #define INPUT_TEST_COMMAND_SET_SENSOR_ACC_Y 11 #define INPUT_TEST_COMMAND_SET_SENSOR_ACC_Z 12 #define INPUT_TEST_COMMAND_SET_SENSOR_GYR_X 13 #define INPUT_TEST_COMMAND_SET_SENSOR_GYR_Y 14 #define INPUT_TEST_COMMAND_SET_SENSOR_GYR_Z 15 #define INPUT_TEST_COMMAND_SET_SENSOR_LUX 16 /* TODO/FIXME - static globals */ static uint16_t test_key_state[DEFAULT_MAX_PADS+1][RETROK_LAST]; typedef struct { float x; float y; float z; } sensor_t; typedef struct test_input { sensor_t accelerometer_state; /* float alignment */ sensor_t gyroscope_state; /* float alignment */ float lux_sensor_state; } test_input_t; static test_input_t test_input_values; typedef struct { unsigned frame; unsigned action; unsigned param_num; char param_str[NAME_MAX_LENGTH]; bool handled; } input_test_step_t; static input_test_step_t input_test_steps[MAX_TEST_STEPS]; static unsigned current_frame = 0; static unsigned next_teststep_frame = 0; static unsigned current_test_step = 0; static unsigned last_test_step = MAX_TEST_STEPS + 1; static uint32_t input_state_validated = 0; static uint32_t combo_state_validated = 0; static bool dump_state_blocked = false; /************************************/ /* JSON Helpers for test input file */ /************************************/ typedef struct { unsigned *current_entry_uint_val; char **current_entry_str_val; unsigned frame; unsigned action; unsigned param_num; char *param_str; } KTifJSONContext; static bool KTifJSONObjectEndHandler(void* context) { KTifJSONContext *pCtx = (KTifJSONContext*)context; /* Too long input is handled elsewhere, it should not lead to parse error */ if (current_test_step >= MAX_TEST_STEPS) return true; /* Copy values read from JSON file + fill defaults */ if (pCtx->frame == 0xffff) input_test_steps[current_test_step].frame = input_test_steps[current_test_step-1].frame + 60; else input_test_steps[current_test_step].frame = pCtx->frame; input_test_steps[current_test_step].action = pCtx->action; input_test_steps[current_test_step].param_num = pCtx->param_num; input_test_steps[current_test_step].handled = false; if (!string_is_empty(pCtx->param_str)) strlcpy( input_test_steps[current_test_step].param_str, pCtx->param_str, sizeof(input_test_steps[current_test_step].param_str)); else input_test_steps[current_test_step].param_str[0] = '\0'; current_test_step++; last_test_step = current_test_step; pCtx->frame = 0xffff; return true; } static bool KTifJSONObjectMemberHandler(void* context, const char *pValue, size_t length) { KTifJSONContext *pCtx = (KTifJSONContext*)context; /* something went wrong */ if (pCtx->current_entry_str_val) return false; if (length) { if (string_is_equal(pValue, "frame")) pCtx->current_entry_uint_val = &pCtx->frame; else if (string_is_equal(pValue, "action")) pCtx->current_entry_uint_val = &pCtx->action; else if (string_is_equal(pValue, "param_num")) pCtx->current_entry_uint_val = &pCtx->param_num; else if (string_is_equal(pValue, "param_str")) pCtx->current_entry_str_val = &pCtx->param_str; /* ignore unknown members */ } return true; } static bool KTifJSONNumberHandler(void* context, const char *pValue, size_t length) { KTifJSONContext *pCtx = (KTifJSONContext*)context; if (pCtx->current_entry_uint_val && length && !string_is_empty(pValue)) *pCtx->current_entry_uint_val = string_to_unsigned(pValue); /* ignore unknown members */ pCtx->current_entry_uint_val = NULL; return true; } static bool KTifJSONStringHandler(void* context, const char *pValue, size_t length) { KTifJSONContext *pCtx = (KTifJSONContext*)context; if (pCtx->current_entry_str_val && length && !string_is_empty(pValue)) { if (*pCtx->current_entry_str_val) free(*pCtx->current_entry_str_val); *pCtx->current_entry_str_val = strdup(pValue); } /* ignore unknown members */ pCtx->current_entry_str_val = NULL; return true; } /* Parses test input file referenced by file_path. * Does nothing if test input file does not exist. */ static bool input_test_file_read(const char* file_path) { bool success = false; KTifJSONContext context = {0}; RFILE *file = NULL; rjson_t* parser; /* Sanity check */ if ( string_is_empty(file_path) || !path_is_valid(file_path) ) { RARCH_DBG("[Test input driver]: No test input file supplied.\n"); return false; } /* Attempt to open test input file */ file = filestream_open( file_path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (!file) { RARCH_ERR("[Test input driver]: Failed to open test input file: \"%s\".\n", file_path); return false; } /* Initialise JSON parser */ if (!(parser = rjson_open_rfile(file))) { RARCH_ERR("[Test input driver]: Failed to create JSON parser.\n"); goto end; } /* Configure parser */ rjson_set_options(parser, RJSON_OPTION_ALLOW_UTF8BOM); /* Read file */ if (rjson_parse(parser, &context, KTifJSONObjectMemberHandler, KTifJSONStringHandler, KTifJSONNumberHandler, NULL, KTifJSONObjectEndHandler, NULL, NULL, /* object/array handlers */ NULL, NULL) /* unused boolean/null handlers */ != RJSON_DONE) { if (rjson_get_source_context_len(parser)) { RARCH_ERR( "[Test input driver]: Error parsing chunk of test input file: %s\n---snip---\n%.*s\n---snip---\n", file_path, rjson_get_source_context_len(parser), rjson_get_source_context_buf(parser)); } RARCH_WARN( "[Test input driver]: Error parsing test input file: %s\n", file_path); RARCH_ERR( "[Test input driver]: Error: Invalid JSON at line %d, column %d - %s.\n", (int)rjson_get_source_line(parser), (int)rjson_get_source_column(parser), (*rjson_get_error(parser) ? rjson_get_error(parser) : "format error")); } /* Free parser */ rjson_free(parser); success = true; end: /* Clean up leftover strings */ if (context.param_str) free(context.param_str); /* Close log file */ filestream_close(file); if (last_test_step >= MAX_TEST_STEPS) { RARCH_WARN("[Test input driver]: too long test input json, maximum size: %d\n",MAX_TEST_STEPS); } for (current_test_step = 0; current_test_step < last_test_step; current_test_step++) { RARCH_DBG( "[Test input driver]: test step %02d read from file: frame %d, action %x, num %x, str %s\n", current_test_step, input_test_steps[current_test_step].frame, input_test_steps[current_test_step].action, input_test_steps[current_test_step].param_num, input_test_steps[current_test_step].param_str); } current_test_step = 0; return success; } /********************************/ /* Test input file handling end */ /********************************/ /*uint16_t *test_keyboard_state_get(unsigned port) { return test_key_state[port]; }*/ static void test_keyboard_free(void) { unsigned i, j; for (i = 0; i < DEFAULT_MAX_PADS; i++) for (j = 0; j < RETROK_LAST; j++) test_key_state[i][j] = 0; } static int16_t test_input_state( void *data, const input_device_driver_t *joypad, const input_device_driver_t *sec_joypad, rarch_joypad_info_t *joypad_info, const retro_keybind_set *binds, bool keyboard_mapping_blocked, unsigned port, unsigned device, unsigned idx, unsigned id) { if (port <= 0) { switch (device) { /* TODO: something clear here */ case RETRO_DEVICE_JOYPAD: if (id == RETRO_DEVICE_ID_JOYPAD_MASK) { unsigned i; int16_t ret = 0; if (!keyboard_mapping_blocked && id < RARCH_BIND_LIST_END) { for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) { if (binds[port][i].valid) { if ( (binds[port][i].key && binds[port][i].key < RETROK_LAST) && test_key_state[DEFAULT_MAX_PADS][binds[port][id].key]) ret |= (1 << i); } } } return ret; } if (id < RARCH_BIND_LIST_END) { if (binds[port][id].valid) { if ( (binds[port][id].key && binds[port][id].key < RETROK_LAST) && test_key_state[DEFAULT_MAX_PADS][binds[port][id].key] && (id == RARCH_GAME_FOCUS_TOGGLE || !keyboard_mapping_blocked) ) return 1; } } break; case RETRO_DEVICE_KEYBOARD: if (id && id < RETROK_LAST) return (test_key_state[DEFAULT_MAX_PADS][id]); break; } } return 0; } static void test_input_free_input(void *data) { test_keyboard_free(); } static void* test_input_init(const char *joypad_driver) { settings_t *settings = config_get_ptr(); unsigned i; RARCH_DBG("[Test input driver]: start\n"); input_test_file_read(settings->paths.test_input_file_general); if (last_test_step > MAX_TEST_STEPS) last_test_step = 0; /* No need for keyboard mapping look-up table */ /* input_keymaps_init_keyboard_lut(rarch_key_map_test);*/ return (void*)-1; } static float test_input_unsigned_to_float_acc(unsigned i) { if (i>200) return 0.0f; return ((signed int)(100-i))/100.0f*9.81f; } static float test_input_unsigned_to_float_gyro(unsigned i) { if (i>200) return 0.0f; return ((signed int)(100-i))/100.0f; } static float test_input_unsigned_to_float_lux(unsigned i) { if (i>200 || i<100) return 0.0f; return (float)pow(10,(((i-100)/100.0f)*4)); } static void test_input_poll(void *data) { video_driver_state_t *video_st = video_state_get_ptr(); uint64_t curr_frame = video_st->frame_count; unsigned i; for (i=0; i input_test_steps[i].frame) { if( input_test_steps[i].action == INPUT_TEST_COMMAND_PRESS_KEY) { if(input_test_steps[i].param_num < RETROK_LAST) { test_key_state[DEFAULT_MAX_PADS][input_test_steps[i].param_num] = 1; input_keyboard_event(true, input_test_steps[i].param_num, 0, 0, RETRO_DEVICE_KEYBOARD); } input_test_steps[i].handled = true; RARCH_DBG( "[Test input driver]: Pressing keyboard button %d at frame %d\n", input_test_steps[i].param_num, curr_frame); } else if( input_test_steps[i].action == INPUT_TEST_COMMAND_RELEASE_KEY) { if(input_test_steps[i].param_num < RETROK_LAST) { test_key_state[DEFAULT_MAX_PADS][input_test_steps[i].param_num] = 0; input_keyboard_event(false, input_test_steps[i].param_num, 0, 0, RETRO_DEVICE_KEYBOARD); } input_test_steps[i].handled = true; RARCH_DBG( "[Test input driver]: Releasing keyboard button %d at frame %d\n", input_test_steps[i].param_num, curr_frame); } else if(input_test_steps[i].action == INPUT_TEST_COMMAND_SET_SENSOR_ACC_X || input_test_steps[i].action == INPUT_TEST_COMMAND_SET_SENSOR_ACC_Y || input_test_steps[i].action == INPUT_TEST_COMMAND_SET_SENSOR_ACC_Z) { float setval = test_input_unsigned_to_float_acc(input_test_steps[i].param_num); switch (input_test_steps[i].action) { case INPUT_TEST_COMMAND_SET_SENSOR_ACC_X: test_input_values.accelerometer_state.x = setval; break; case INPUT_TEST_COMMAND_SET_SENSOR_ACC_Y: test_input_values.accelerometer_state.y = setval; break; case INPUT_TEST_COMMAND_SET_SENSOR_ACC_Z: test_input_values.accelerometer_state.z = setval; break; } input_test_steps[i].handled = true; RARCH_DBG( "[Test input driver]: Setting accelerometer axis %d to %f at frame %d\n", input_test_steps[i].action - INPUT_TEST_COMMAND_SET_SENSOR_ACC_X, setval, curr_frame); } else if(input_test_steps[i].action == INPUT_TEST_COMMAND_SET_SENSOR_GYR_X || input_test_steps[i].action == INPUT_TEST_COMMAND_SET_SENSOR_GYR_Y || input_test_steps[i].action == INPUT_TEST_COMMAND_SET_SENSOR_GYR_Z) { float setval = test_input_unsigned_to_float_gyro(input_test_steps[i].param_num); switch (input_test_steps[i].action) { case INPUT_TEST_COMMAND_SET_SENSOR_GYR_X: test_input_values.gyroscope_state.x = setval; break; case INPUT_TEST_COMMAND_SET_SENSOR_GYR_Y: test_input_values.gyroscope_state.y = setval; break; case INPUT_TEST_COMMAND_SET_SENSOR_GYR_Z: test_input_values.gyroscope_state.z = setval; break; } input_test_steps[i].handled = true; RARCH_DBG( "[Test input driver]: Setting gyroscope axis %d to %f at frame %d\n", input_test_steps[i].action - INPUT_TEST_COMMAND_SET_SENSOR_GYR_X, setval, curr_frame); } else if(input_test_steps[i].action == INPUT_TEST_COMMAND_SET_SENSOR_LUX) { float setval = test_input_unsigned_to_float_lux(input_test_steps[i].param_num); test_input_values.lux_sensor_state = setval; input_test_steps[i].handled = true; RARCH_DBG( "[Test input driver]: Setting lux sensor to %f at frame %d\n", setval, curr_frame); } else { input_test_steps[i].handled = true; RARCH_WARN( "[Test input driver]: Unrecognized action %d in step %d, skipping\n", input_test_steps[i].action,i); } } } } static bool test_input_set_sensor_state(void *data, unsigned port, enum retro_sensor_action action, unsigned event_rate) { RARCH_DBG("[Test input driver]: Setting sensor action %d rate %d\n",action, event_rate); return true; } static float test_input_get_sensor_input(void *data, unsigned port, unsigned id) { switch (id) { case RETRO_SENSOR_ACCELEROMETER_X: return test_input_values.accelerometer_state.x; case RETRO_SENSOR_ACCELEROMETER_Y: return test_input_values.accelerometer_state.y; case RETRO_SENSOR_ACCELEROMETER_Z: return test_input_values.accelerometer_state.z; case RETRO_SENSOR_GYROSCOPE_X: return test_input_values.gyroscope_state.x; case RETRO_SENSOR_GYROSCOPE_Y: return test_input_values.gyroscope_state.y; case RETRO_SENSOR_GYROSCOPE_Z: return test_input_values.gyroscope_state.z; case RETRO_SENSOR_ILLUMINANCE: return test_input_values.lux_sensor_state; } return 0.0f; } static uint64_t test_input_get_capabilities(void *data) { return (1 << RETRO_DEVICE_JOYPAD) | (1 << RETRO_DEVICE_ANALOG) | (1 << RETRO_DEVICE_KEYBOARD) /* | (1 << RETRO_DEVICE_MOUSE) | (1 << RETRO_DEVICE_POINTER) | (1 << RETRO_DEVICE_LIGHTGUN)*/; } input_driver_t input_test = { test_input_init, test_input_poll, test_input_state, test_input_free_input, test_input_set_sensor_state, test_input_get_sensor_input, test_input_get_capabilities, "test", NULL, /* grab_mouse */ NULL, NULL };