diff --git a/Makefile.common b/Makefile.common index 17c5e401853d..a550c791462a 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2445,6 +2445,7 @@ endif ifeq ($(HAVE_TEST_DRIVERS), 1) DEFINES += -DHAVE_TEST_DRIVERS OBJ += input/drivers_joypad/test_joypad.o + OBJ += input/drivers/test_input.o endif diff --git a/configuration.c b/configuration.c index 2371165abf6d..4f51d4863e04 100644 --- a/configuration.c +++ b/configuration.c @@ -1669,7 +1669,8 @@ static struct config_path_setting *populate_settings_path( #endif #ifdef HAVE_TEST_DRIVERS - SETTING_PATH("test_input_file_joypad", settings->paths.test_input_file_joypad, false, NULL, true); + SETTING_PATH("test_input_file_joypad", settings->paths.test_input_file_joypad, false, NULL, true); + SETTING_PATH("test_input_file_general", settings->paths.test_input_file_general, false, NULL, true); #endif SETTING_ARRAY("log_dir", settings->paths.log_dir, true, NULL, true); diff --git a/configuration.h b/configuration.h index 0d67a825fff5..e65fe8e0f3df 100644 --- a/configuration.h +++ b/configuration.h @@ -577,6 +577,7 @@ typedef struct settings #endif #ifdef HAVE_TEST_DRIVERS char test_input_file_joypad[PATH_MAX_LENGTH]; + char test_input_file_general[PATH_MAX_LENGTH]; #endif char log_dir[PATH_MAX_LENGTH]; char app_icon[PATH_MAX_LENGTH]; diff --git a/griffin/griffin.c b/griffin/griffin.c index e1bb908f5888..d1986ea63801 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -733,6 +733,7 @@ INPUT #ifdef HAVE_TEST_DRIVERS #include "../input/drivers_joypad/test_joypad.c" +#include "../input/drivers/test_input.c" #endif /*============================================================ diff --git a/input/drivers/test_input.c b/input/drivers/test_input.c new file mode 100644 index 000000000000..cd45862007c4 --- /dev/null +++ b/input/drivers/test_input.c @@ -0,0 +1,431 @@ +/* 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 "../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 + +/* TODO/FIXME - static globals */ +static uint16_t test_key_state[DEFAULT_MAX_PADS+1][RETROK_LAST]; + + +typedef struct +{ + unsigned frame; + unsigned action; + unsigned param_num; + char param_str[255]; + 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 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 + { + 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 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, + NULL, + NULL, + test_input_get_capabilities, + "test", + NULL, /* grab_mouse */ + NULL, + NULL +}; diff --git a/input/drivers_joypad/test_joypad.c b/input/drivers_joypad/test_joypad.c index ac9a87a9f4c4..9c7dcf1fcfa1 100644 --- a/input/drivers_joypad/test_joypad.c +++ b/input/drivers_joypad/test_joypad.c @@ -194,7 +194,7 @@ static bool input_test_file_read(const char* file_path) || !path_is_valid(file_path) ) { - RARCH_DBG("[Test input driver]: No test input file supplied.\n"); + RARCH_DBG("[Test joypad driver]: No test input file supplied.\n"); return false; } @@ -206,7 +206,7 @@ static bool input_test_file_read(const char* file_path) if (!file) { - RARCH_ERR("[Test input driver]: Failed to open test input file: \"%s\".\n", + RARCH_ERR("[Test joypad driver]: Failed to open test input file: \"%s\".\n", file_path); return false; } @@ -214,7 +214,7 @@ static bool input_test_file_read(const char* file_path) /* Initialise JSON parser */ if (!(parser = rjson_open_rfile(file))) { - RARCH_ERR("[Test input driver]: Failed to create JSON parser.\n"); + RARCH_ERR("[Test joypad driver]: Failed to create JSON parser.\n"); goto end; } @@ -233,16 +233,16 @@ static bool input_test_file_read(const char* file_path) 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", + "[Test joypad 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", + "[Test joypad 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", + "[Test joypad 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")); @@ -262,12 +262,12 @@ static bool input_test_file_read(const char* file_path) if (last_test_step >= MAX_TEST_STEPS) { - RARCH_WARN("[Test input driver]: too long test input json, maximum size: %d\n",MAX_TEST_STEPS); + RARCH_WARN("[Test joypad 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", + "[Test joypad 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, @@ -433,7 +433,7 @@ static void test_joypad_poll(void) test_joypads[targetpad].button_state |= input_test_steps[i].param_num; input_test_steps[i].handled = true; RARCH_DBG( - "[Test input driver]: Pressing device %d buttons %x, new state %x.\n", + "[Test joypad driver]: Pressing device %d buttons %x, new state %x.\n", targetpad,input_test_steps[i].param_num,test_joypads[targetpad].button_state); } else if( input_test_steps[i].action >= JOYPAD_TEST_COMMAND_BUTTON_RELEASE_FIRST && @@ -443,7 +443,7 @@ static void test_joypad_poll(void) test_joypads[targetpad].button_state &= ~input_test_steps[i].param_num; input_test_steps[i].handled = true; RARCH_DBG( - "[Test input driver]: Releasing device %d buttons %x, new state %x.\n", + "[Test joypad driver]: Releasing device %d buttons %x, new state %x.\n", targetpad,input_test_steps[i].param_num,test_joypads[targetpad].button_state); } else if( input_test_steps[i].action >= JOYPAD_TEST_COMMAND_BUTTON_AXIS_FIRST && @@ -457,19 +457,19 @@ static void test_joypad_poll(void) test_joypads[targetpad].axis_state[targetaxis] = (int16_t) input_test_steps[i].param_num; else RARCH_WARN( - "[Test input driver]: Decoded axis outside target range: action %d pad %d axis %d.\n", + "[Test joypad driver]: Decoded axis outside target range: action %d pad %d axis %d.\n", input_test_steps[i].action, targetpad, targetaxis); input_test_steps[i].handled = true; RARCH_DBG( - "[Test input driver]: Setting axis device %d axis %d value %d.\n", + "[Test joypad driver]: Setting axis device %d axis %d value %d.\n", targetpad, targetaxis, (int16_t)input_test_steps[i].param_num); } else { input_test_steps[i].handled = true; RARCH_WARN( - "[Test input driver]: Unrecognized action %d in step %d, skipping\n", + "[Test joypad driver]: Unrecognized action %d in step %d, skipping\n", input_test_steps[i].action,i); } diff --git a/input/input_driver.c b/input/input_driver.c index 33fafc37d728..76a2c278cd3f 100644 --- a/input/input_driver.c +++ b/input/input_driver.c @@ -363,6 +363,9 @@ input_driver_t *input_drivers[] = { #endif #ifdef DJGPP &input_dos, +#endif +#ifdef HAVE_TEST_DRIVERS + &input_test, #endif &input_null, NULL, @@ -4418,11 +4421,24 @@ bool video_driver_init_input( void *new_data = NULL; input_driver_t **input = &input_driver_st.current_driver; if (*input) +#if HAVE_TEST_DRIVERS + if (strcmp(settings->arrays.input_driver,"test") != 0) + /* Test driver not in use, keep selected driver */ + return true; + else if (string_is_empty(settings->paths.test_input_file_general)) + { + RARCH_LOG("[Input]: Test input driver selected, but no input file provided - falling back.\n"); + return true; + } + else + RARCH_LOG("[Video]: Graphics driver initialized an input driver, but ignoring it as test input driver is in use.\n"); +#else return true; - - /* Video driver didn't provide an input driver, - * so we use configured one. */ - RARCH_LOG("[Video]: Graphics driver did not initialize an input driver." +#endif + else + /* Video driver didn't provide an input driver, + * so we use configured one. */ + RARCH_LOG("[Video]: Graphics driver did not initialize an input driver." " Attempting to pick a suitable driver.\n"); if (tmp) diff --git a/input/input_driver.h b/input/input_driver.h index 3db9c79ebe65..50eb5d4ee692 100644 --- a/input/input_driver.h +++ b/input/input_driver.h @@ -1103,6 +1103,7 @@ extern input_driver_t input_rwebinput; extern input_driver_t input_dos; extern input_driver_t input_winraw; extern input_driver_t input_wayland; +extern input_driver_t input_test; extern input_device_driver_t dinput_joypad; extern input_device_driver_t linuxraw_joypad; diff --git a/tests-other/all_binds_empty.cfg b/tests-other/all_binds_empty.cfg index 82d867584fed..b672d927a123 100644 --- a/tests-other/all_binds_empty.cfg +++ b/tests-other/all_binds_empty.cfg @@ -5,6 +5,7 @@ input_exit_emulator = "escape" input_max_users = "10" +input_fps_toggle = "f3" input_ai_service = "nul" input_ai_service_axis = "nul" @@ -62,7 +63,7 @@ input_enable_hotkey_mbtn = "nul" input_exit_emulator_axis = "nul" input_exit_emulator_btn = "nul" input_exit_emulator_mbtn = "nul" -input_fps_toggle = "nul" +# input_fps_toggle = "nul" input_fps_toggle_axis = "nul" input_fps_toggle_btn = "nul" input_fps_toggle_mbtn = "nul" diff --git a/tests-other/test_input_keyboard.ratst b/tests-other/test_input_keyboard.ratst new file mode 100644 index 000000000000..fffbec732791 --- /dev/null +++ b/tests-other/test_input_keyboard.ratst @@ -0,0 +1,26 @@ +[{ + "action": 1, + "param_num": 284, + "frame": 330 +},{ + "action": 2, + "param_num": 284 +},{ + "action": 1, + "param_num": 284 +},{ + "action": 2, + "param_num": 284 +},{ + "action": 1, + "param_num": 27 +},{ + "action": 2, + "param_num": 27 +},{ + "action": 1, + "param_num": 27 +},{ + "action": 2, + "param_num": 27 +}]