/* 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 #include #include #include #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include "menu_driver.h" #include "menu_cbs.h" #include "menu_input.h" #include "menu_entries.h" #include "widgets/menu_dialog.h" #include "widgets/menu_input_bind_dialog.h" #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) #include "menu_shader.h" #endif #include "../config.def.h" #include "../content.h" #include "../core.h" #include "../configuration.h" #include "../dynamic.h" #include "../driver.h" #include "../retroarch.h" #include "../version.h" #include "../list_special.h" #include "../tasks/tasks_internal.h" #include "../verbosity.h" #include "../tasks/task_powerstate.h" #ifdef HAVE_NETWORKING #include "../core_updater_list.h" #endif #ifdef HAVE_ACCESSIBILITY #include "./accessibility.h" #endif #define SCROLL_INDEX_SIZE (2 * (26 + 2) + 1) #define POWERSTATE_CHECK_INTERVAL (30 * 1000000) #define DATETIME_CHECK_INTERVAL 1000000 typedef struct menu_ctx_load_image { void *data; enum menu_image_type type; } menu_ctx_load_image_t; struct menu_list { size_t menu_stack_size; size_t selection_buf_size; file_list_t **menu_stack; file_list_t **selection_buf; }; /* Storage container for current menu datetime * representation string */ static char menu_datetime_cache[255] = {0}; /* when enabled, on next iteration the 'Quick Menu' list will * be pushed onto the stack */ static bool menu_driver_pending_quick_menu = false; static bool menu_driver_prevent_populate = false; /* The menu driver owns the userdata */ static bool menu_driver_data_own = false; static menu_handle_t *menu_driver_data = NULL; static const menu_ctx_driver_t *menu_driver_ctx = NULL; static void *menu_userdata = NULL; /* Quick jumping indices with L/R. * Rebuilt when parsing directory. */ static size_t scroll_index_list[SCROLL_INDEX_SIZE]; static unsigned scroll_index_size = 0; static unsigned scroll_acceleration = 0; static size_t menu_driver_selection_ptr = 0; /* Timers */ static retro_time_t menu_driver_current_time_us = 0; static retro_time_t menu_driver_powerstate_last_time_us = 0; static retro_time_t menu_driver_datetime_last_time_us = 0; /* Flagged when menu entries need to be refreshed */ static bool menu_entries_need_refresh = false; static bool menu_entries_nonblocking_refresh = false; static size_t menu_entries_begin = 0; static rarch_setting_t *menu_entries_list_settings = NULL; static menu_list_t *menu_entries_list = NULL; static enum action_iterate_type action_iterate_type(const char *label) { if (string_is_equal(label, "info_screen")) return ITERATE_TYPE_INFO; if ( string_is_equal(label, "help") || string_is_equal(label, "help_controls") || string_is_equal(label, "help_what_is_a_core") || string_is_equal(label, "help_loading_content") || string_is_equal(label, "help_scanning_content") || string_is_equal(label, "help_change_virtual_gamepad") || string_is_equal(label, "help_audio_video_troubleshooting") || string_is_equal(label, "help_send_debug_info") || string_is_equal(label, "cheevos_description") ) return ITERATE_TYPE_HELP; if ( string_is_equal(label, "custom_bind") || string_is_equal(label, "custom_bind_all") || string_is_equal(label, "custom_bind_defaults") ) return ITERATE_TYPE_BIND; return ITERATE_TYPE_DEFAULT; } #ifdef HAVE_ACCESSIBILITY static void get_current_menu_value(char* retstr, size_t max) { const char* entry_label; menu_entry_t entry; menu_driver_selection_ptr = menu_navigation_get_selection(); menu_entry_init(&entry); menu_entry_get(&entry, 0, menu_navigation_get_selection(), NULL, true); menu_entry_get_value(&entry, &entry_label); strlcpy(retstr, entry_label, max); } static void get_current_menu_label(char* retstr, size_t max) { const char* entry_label; menu_entry_t entry; menu_driver_selection_ptr = menu_navigation_get_selection(); menu_entry_init(&entry); menu_entry_get(&entry, 0, menu_navigation_get_selection(), NULL, true); menu_entry_get_rich_label(&entry, &entry_label); strlcpy(retstr, entry_label, max); } static void get_current_menu_sublabel(char* retstr, size_t max) { const char* entry_sublabel; menu_entry_t entry; menu_driver_selection_ptr = menu_navigation_get_selection(); menu_entry_init(&entry); menu_entry_get(&entry, 0, menu_navigation_get_selection(), NULL, true); menu_entry_get_sublabel(&entry, &entry_sublabel); strlcpy(retstr, entry_sublabel, max); } #endif /** * menu_iterate: * @input : input sample for this frame * @old_input : input sample of the previous frame * @trigger_input : difference' input sample - difference * between 'input' and 'old_input' * * Runs RetroArch menu for one frame. * * Returns: 0 on success, -1 if we need to quit out of the loop. **/ static int generic_menu_iterate(void *data, void *userdata, enum menu_action action, retro_time_t current_time) { #ifdef HAVE_ACCESSIBILITY static enum action_iterate_type last_iterate_type = ITERATE_TYPE_DEFAULT; #endif enum action_iterate_type iterate_type; unsigned file_type = 0; int ret = 0; const char *label = NULL; menu_handle_t *menu = (menu_handle_t*)data; if (!menu) return 0; menu_entries_get_last_stack(NULL, &label, &file_type, NULL, NULL); menu->menu_state_msg[0] = '\0'; iterate_type = action_iterate_type(label); menu_driver_set_binding_state(iterate_type == ITERATE_TYPE_BIND); if ( action != MENU_ACTION_NOOP || menu_entries_ctl(MENU_ENTRIES_CTL_NEEDS_REFRESH, NULL) || gfx_display_get_update_pending()) { BIT64_SET(menu->state, MENU_STATE_RENDER_FRAMEBUFFER); } switch (iterate_type) { case ITERATE_TYPE_HELP: ret = menu_dialog_iterate( menu->menu_state_msg, sizeof(menu->menu_state_msg), current_time); #ifdef HAVE_ACCESSIBILITY if (iterate_type != last_iterate_type && is_accessibility_enabled()) accessibility_speak_priority(menu->menu_state_msg, 10); #endif BIT64_SET(menu->state, MENU_STATE_RENDER_MESSAGEBOX); BIT64_SET(menu->state, MENU_STATE_POST_ITERATE); { bool pop_stack = false; if ( ret == 1 || action == MENU_ACTION_OK || action == MENU_ACTION_CANCEL ) pop_stack = true; if (pop_stack) BIT64_SET(menu->state, MENU_STATE_POP_STACK); } break; case ITERATE_TYPE_BIND: { menu_input_ctx_bind_t bind; bind.s = menu->menu_state_msg; bind.len = sizeof(menu->menu_state_msg); if (menu_input_key_bind_iterate(&bind, current_time)) { size_t selection = menu_navigation_get_selection(); menu_entries_pop_stack(&selection, 0, 0); menu_navigation_set_selection(selection); } else BIT64_SET(menu->state, MENU_STATE_RENDER_MESSAGEBOX); } break; case ITERATE_TYPE_INFO: { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); size_t selection = menu_navigation_get_selection(); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*) file_list_get_actiondata_at_offset(selection_buf, selection) : NULL; if (cbs && cbs->enum_idx != MSG_UNKNOWN) { ret = menu_hash_get_help_enum(cbs->enum_idx, menu->menu_state_msg, sizeof(menu->menu_state_msg)); #ifdef HAVE_ACCESSIBILITY if (iterate_type != last_iterate_type && is_accessibility_enabled()) { if (string_is_equal(menu->menu_state_msg, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE))) { char current_sublabel[255]; get_current_menu_sublabel(current_sublabel, sizeof(current_sublabel)); if (string_is_equal(current_sublabel, "")) accessibility_speak_priority(menu->menu_state_msg, 10); else accessibility_speak_priority(current_sublabel, 10); } else accessibility_speak_priority(menu->menu_state_msg, 10); } #endif } else { unsigned type = 0; enum msg_hash_enums enum_idx = MSG_UNKNOWN; size_t selection = menu_navigation_get_selection(); menu_entries_get_at_offset(selection_buf, selection, NULL, NULL, &type, NULL, NULL); switch (type) { case FILE_TYPE_FONT: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_FONT; break; case FILE_TYPE_RDB: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_RDB; break; case FILE_TYPE_OVERLAY: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_OVERLAY; break; #ifdef HAVE_VIDEO_LAYOUT case FILE_TYPE_VIDEO_LAYOUT: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_VIDEO_LAYOUT; break; #endif case FILE_TYPE_CHEAT: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_CHEAT; break; case FILE_TYPE_SHADER_PRESET: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_SHADER_PRESET; break; case FILE_TYPE_SHADER: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_SHADER; break; case FILE_TYPE_REMAP: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_REMAP; break; case FILE_TYPE_RECORD_CONFIG: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_RECORD_CONFIG; break; case FILE_TYPE_CURSOR: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_CURSOR; break; case FILE_TYPE_CONFIG: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_CONFIG; break; case FILE_TYPE_CARCHIVE: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_COMPRESSED_ARCHIVE; break; case FILE_TYPE_DIRECTORY: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY; break; case FILE_TYPE_VIDEOFILTER: /* TODO/FIXME */ case FILE_TYPE_AUDIOFILTER: /* TODO/FIXME */ case FILE_TYPE_SHADER_SLANG: /* TODO/FIXME */ case FILE_TYPE_SHADER_GLSL: /* TODO/FIXME */ case FILE_TYPE_SHADER_HLSL: /* TODO/FIXME */ case FILE_TYPE_SHADER_CG: /* TODO/FIXME */ case FILE_TYPE_SHADER_PRESET_GLSLP: /* TODO/FIXME */ case FILE_TYPE_SHADER_PRESET_HLSLP: /* TODO/FIXME */ case FILE_TYPE_SHADER_PRESET_CGP: /* TODO/FIXME */ case FILE_TYPE_SHADER_PRESET_SLANGP: /* TODO/FIXME */ case FILE_TYPE_PLAIN: enum_idx = MENU_ENUM_LABEL_FILE_BROWSER_PLAIN_FILE; break; default: break; } if (enum_idx != MSG_UNKNOWN) ret = menu_hash_get_help_enum(enum_idx, menu->menu_state_msg, sizeof(menu->menu_state_msg)); } } BIT64_SET(menu->state, MENU_STATE_RENDER_MESSAGEBOX); BIT64_SET(menu->state, MENU_STATE_POST_ITERATE); if (action == MENU_ACTION_OK || action == MENU_ACTION_CANCEL) { BIT64_SET(menu->state, MENU_STATE_POP_STACK); } break; case ITERATE_TYPE_DEFAULT: { menu_entry_t entry; size_t selection = menu_navigation_get_selection(); /* FIXME: Crappy hack, needed for mouse controls * to not be completely broken in case we press back. * * We need to fix this entire mess, mouse controls * should not rely on a hack like this in order to work. */ selection = MAX(MIN(selection, (menu_entries_get_size() - 1)), 0); menu_entry_init(&entry); /* Note: If menu_entry_action() is modified, * will have to verify that these parameters * remain unused... */ entry.rich_label_enabled = false; entry.value_enabled = false; entry.sublabel_enabled = false; menu_entry_get(&entry, 0, selection, NULL, false); ret = menu_entry_action(&entry, selection, (enum menu_action)action); if (ret) goto end; BIT64_SET(menu->state, MENU_STATE_POST_ITERATE); /* Have to defer it so we let settings refresh. */ menu_dialog_push(); } break; } #ifdef HAVE_ACCESSIBILITY if ((last_iterate_type == ITERATE_TYPE_HELP || last_iterate_type == ITERATE_TYPE_INFO) && last_iterate_type != iterate_type && is_accessibility_enabled()) accessibility_speak_priority("Closed dialog.", 10); last_iterate_type = iterate_type; #endif BIT64_SET(menu->state, MENU_STATE_BLIT); if (BIT64_GET(menu->state, MENU_STATE_POP_STACK)) { size_t selection = menu_navigation_get_selection(); size_t new_selection_ptr = selection; menu_entries_pop_stack(&new_selection_ptr, 0, 0); menu_navigation_set_selection(selection); } if (BIT64_GET(menu->state, MENU_STATE_POST_ITERATE)) menu_input_post_iterate(&ret, action); end: if (ret) return -1; return 0; } int generic_menu_entry_action( void *userdata, menu_entry_t *entry, size_t i, enum menu_action action) { int ret = 0; file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; switch (action) { case MENU_ACTION_UP: if (cbs && cbs->action_up) ret = cbs->action_up(entry->type, entry->label); break; case MENU_ACTION_DOWN: if (cbs && cbs->action_down) ret = cbs->action_down(entry->type, entry->label); break; case MENU_ACTION_SCROLL_UP: menu_driver_ctl(MENU_NAVIGATION_CTL_DESCEND_ALPHABET, NULL); break; case MENU_ACTION_SCROLL_DOWN: menu_driver_ctl(MENU_NAVIGATION_CTL_ASCEND_ALPHABET, NULL); break; case MENU_ACTION_CANCEL: if (cbs && cbs->action_cancel) ret = cbs->action_cancel(entry->path, entry->label, entry->type, i); break; case MENU_ACTION_OK: if (cbs && cbs->action_ok) ret = cbs->action_ok(entry->path, entry->label, entry->type, i, entry->entry_idx); break; case MENU_ACTION_START: if (cbs && cbs->action_start) ret = cbs->action_start(entry->path, entry->label, entry->type, i, entry->entry_idx); break; case MENU_ACTION_LEFT: if (cbs && cbs->action_left) ret = cbs->action_left(entry->type, entry->label, false); break; case MENU_ACTION_RIGHT: if (cbs && cbs->action_right) ret = cbs->action_right(entry->type, entry->label, false); break; case MENU_ACTION_INFO: if (cbs && cbs->action_info) ret = cbs->action_info(entry->type, entry->label); break; case MENU_ACTION_SELECT: if (cbs && cbs->action_select) ret = cbs->action_select(entry->path, entry->label, entry->type, i, entry->entry_idx); break; case MENU_ACTION_SEARCH: menu_input_dialog_start_search(); break; case MENU_ACTION_SCAN: if (cbs && cbs->action_scan) ret = cbs->action_scan(entry->path, entry->label, entry->type, i); break; default: break; } cbs = selection_buf ? (menu_file_list_cbs_t*) selection_buf->list[i].actiondata : NULL; if (cbs && cbs->action_refresh) { if (menu_entries_ctl(MENU_ENTRIES_CTL_NEEDS_REFRESH, NULL)) { bool refresh = false; file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); cbs->action_refresh(selection_buf, menu_stack); menu_entries_ctl(MENU_ENTRIES_CTL_UNSET_REFRESH, &refresh); } } #ifdef HAVE_ACCESSIBILITY if ( action != 0 && is_accessibility_enabled() && !is_input_keyboard_display_on()) { char current_label[255]; char current_value[255]; char title_name[255]; char speak_string[512]; strlcpy(title_name, "", sizeof(title_name)); strlcpy(current_label, "", sizeof(current_label)); get_current_menu_value(current_value, sizeof(current_value)); switch (action) { case MENU_ACTION_INFO: break; case MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE: menu_entries_get_title(title_name, sizeof(title_name)); break; case MENU_ACTION_ACCESSIBILITY_SPEAK_LABEL: get_current_menu_label(current_label, sizeof(current_label)); break; case MENU_ACTION_ACCESSIBILITY_SPEAK_TITLE_LABEL: menu_entries_get_title(title_name, sizeof(title_name)); get_current_menu_label(current_label, sizeof(current_label)); break; case MENU_ACTION_OK: case MENU_ACTION_LEFT: case MENU_ACTION_RIGHT: case MENU_ACTION_CANCEL: menu_entries_get_title(title_name, sizeof(title_name)); get_current_menu_label(current_label, sizeof(current_label)); break; case MENU_ACTION_UP: case MENU_ACTION_DOWN: case MENU_ACTION_SCROLL_UP: case MENU_ACTION_SCROLL_DOWN: get_current_menu_label(current_label, sizeof(current_label)); break; case MENU_ACTION_START: if (!string_is_equal(current_value, "...")) { menu_entries_get_title(title_name, sizeof(title_name)); get_current_menu_label(current_label, sizeof(current_label)); } break; case MENU_ACTION_SELECT: case MENU_ACTION_SEARCH: get_current_menu_label(current_label, sizeof(current_label)); break; case MENU_ACTION_SCAN: default: break; } strlcpy(speak_string, "", sizeof(speak_string)); if (!string_is_equal(title_name, "")) { strlcpy(speak_string, title_name, sizeof(speak_string)); strlcat(speak_string, " ", sizeof(speak_string)); } strlcat(speak_string, current_label, sizeof(speak_string)); if (!string_is_equal(current_value, "...")) { strlcat(speak_string, " ", sizeof(speak_string)); strlcat(speak_string, current_value, sizeof(speak_string)); } if (!string_is_equal(speak_string, "")) accessibility_speak_priority(speak_string, 10); } #endif return ret; } static void *null_menu_init(void **userdata, bool video_is_threaded) { menu_handle_t *menu = (menu_handle_t*)calloc(1, sizeof(*menu)); if (!menu) return NULL; return menu; } static int null_menu_iterate(void *data, void *userdata, enum menu_action action) { return 1; } static menu_ctx_driver_t menu_ctx_null = { NULL, /* set_texture */ NULL, /* render_messagebox */ null_menu_iterate, NULL, /* render */ NULL, /* frame */ null_menu_init, NULL, /* free */ NULL, /* context_reset */ NULL, /* context_destroy */ NULL, /* populate_entries */ NULL, /* toggle */ NULL, /* navigation_clear */ NULL, /* navigation_decrement */ NULL, /* navigation_increment */ NULL, /* navigation_set */ NULL, /* navigation_set_last */ NULL, /* navigation_descend_alphabet */ NULL, /* navigation_ascend_alphabet */ NULL, /* lists_init */ NULL, /* list_insert */ NULL, /* list_prepend */ NULL, /* list_delete */ NULL, /* list_clear */ NULL, /* list_cache */ NULL, /* list_push */ NULL, /* list_get_selection */ NULL, /* list_get_size */ NULL, /* list_get_entry */ NULL, /* list_set_selection */ NULL, /* bind_init */ NULL, /* load_image */ "null", NULL, /* environ */ NULL, /* update_thumbnail_path */ NULL, /* update_thumbnail_image */ NULL, /* refresh_thumbnail_image */ NULL, /* set_thumbnail_system */ NULL, /* get_thumbnail_system */ NULL, /* set_thumbnail_content */ NULL, /* osk_ptr_at_pos */ NULL, /* update_savestate_thumbnail_path */ NULL, /* update_savestate_thumbnail_image */ NULL, /* pointer_down */ NULL, /* pointer_up */ NULL, /* get_load_content_animation_data */ NULL /* entry_action */ }; /* Menu drivers */ static const menu_ctx_driver_t *menu_ctx_drivers[] = { #if defined(HAVE_MATERIALUI) &menu_ctx_mui, #endif #if defined(HAVE_OZONE) &menu_ctx_ozone, #endif #if defined(HAVE_RGUI) &menu_ctx_rgui, #endif #if defined(HAVE_STRIPES) &menu_ctx_stripes, #endif #if defined(HAVE_XMB) &menu_ctx_xmb, #endif &menu_ctx_null, NULL }; menu_handle_t *menu_driver_get_ptr(void) { return menu_driver_data; } size_t menu_navigation_get_selection(void) { return menu_driver_selection_ptr; } void menu_navigation_set_selection(size_t val) { menu_driver_selection_ptr = val; } #define menu_list_get(list, idx) ((list) ? ((list)->menu_stack[(idx)]) : NULL) #define menu_list_get_selection(list, idx) ((list) ? ((list)->selection_buf[(idx)]) : NULL) #define menu_list_get_stack_size(list, idx) ((list)->menu_stack[(idx)]->size) #define menu_entries_get_selection_buf_ptr_internal(idx) ((menu_entries_list) ? menu_list_get_selection(menu_entries_list, (unsigned)idx) : NULL) /* Menu entry interface - * * This provides an abstraction of the currently displayed * menu. * * It is organized into an event-based system where the UI companion * calls this functions and RetroArch responds by changing the global * state (including arranging for these functions to return different * values). * * Its only interaction back to the UI is to arrange for * notify_list_loaded on the UI companion. */ enum menu_entry_type menu_entry_get_type(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = NULL; rarch_setting_t *setting = NULL; /* FIXME/TODO - XXX Really a special kind of ST_ACTION, * but this should be changed */ if (menu_setting_ctl(MENU_SETTING_CTL_IS_OF_PATH_TYPE, (void*)setting)) return MENU_ENTRY_PATH; cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; setting = cbs ? cbs->setting : NULL; if (setting) { switch (setting_get_type(setting)) { case ST_BOOL: return MENU_ENTRY_BOOL; case ST_BIND: return MENU_ENTRY_BIND; case ST_INT: return MENU_ENTRY_INT; case ST_UINT: return MENU_ENTRY_UINT; case ST_SIZE: return MENU_ENTRY_SIZE; case ST_FLOAT: return MENU_ENTRY_FLOAT; case ST_PATH: return MENU_ENTRY_PATH; case ST_DIR: return MENU_ENTRY_DIR; case ST_STRING_OPTIONS: return MENU_ENTRY_ENUM; case ST_STRING: return MENU_ENTRY_STRING; case ST_HEX: return MENU_ENTRY_HEX; default: break; } } return MENU_ENTRY_ACTION; } void menu_entry_init(menu_entry_t *entry) { entry->path[0] = '\0'; entry->label[0] = '\0'; entry->sublabel[0] = '\0'; entry->rich_label[0] = '\0'; entry->value[0] = '\0'; entry->password_value[0] = '\0'; entry->enum_idx = MSG_UNKNOWN; entry->entry_idx = 0; entry->idx = 0; entry->type = 0; entry->spacing = 0; entry->path_enabled = true; entry->label_enabled = true; entry->rich_label_enabled = true; entry->value_enabled = true; entry->sublabel_enabled = true; } void menu_entry_get_path(menu_entry_t *entry, const char **path) { if (!entry || !path) return; *path = entry->path; } void menu_entry_get_rich_label(menu_entry_t *entry, const char **rich_label) { if (!entry || !rich_label) return; if (!string_is_empty(entry->rich_label)) *rich_label = entry->rich_label; else *rich_label = entry->path; } void menu_entry_get_sublabel(menu_entry_t *entry, const char **sublabel) { if (!entry || !sublabel) return; *sublabel = entry->sublabel; } void menu_entry_get_label(menu_entry_t *entry, const char **label) { if (!entry || !label) return; *label = entry->label; } unsigned menu_entry_get_spacing(menu_entry_t *entry) { if (entry) return entry->spacing; return 0; } unsigned menu_entry_get_type_new(menu_entry_t *entry) { if (!entry) return 0; return entry->type; } uint32_t menu_entry_get_bool_value(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; bool *ptr = setting ? (bool*)setting->value.target.boolean : NULL; if (!ptr) return 0; return *ptr; } struct string_list *menu_entry_enum_values(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; const char *values = setting->values; if (!values) return NULL; return string_split(values, "|"); } void menu_entry_enum_set_value_with_string(uint32_t i, const char *s) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; setting_set_with_string_representation(setting, s); } int32_t menu_entry_bind_index(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; if (setting) return setting->index - 1; return 0; } void menu_entry_bind_key_set(uint32_t i, int32_t value) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; struct retro_keybind *keybind = setting ? (struct retro_keybind*)setting->value.target.keybind : NULL; if (keybind) keybind->key = (enum retro_key)value; } void menu_entry_bind_joykey_set(uint32_t i, int32_t value) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; struct retro_keybind *keybind = setting ? (struct retro_keybind*)setting->value.target.keybind : NULL; if (keybind) keybind->joykey = value; } void menu_entry_bind_joyaxis_set(uint32_t i, int32_t value) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; struct retro_keybind *keybind = setting ? (struct retro_keybind*)setting->value.target.keybind : NULL; if (keybind) keybind->joyaxis = value; } void menu_entry_pathdir_selected(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; if (menu_setting_ctl(MENU_SETTING_CTL_IS_OF_PATH_TYPE, (void*)setting)) menu_setting_ctl(MENU_SETTING_CTL_ACTION_RIGHT, setting); } bool menu_entry_pathdir_allow_empty(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; uint64_t flags = setting->flags; return flags & SD_FLAG_ALLOW_EMPTY; } uint32_t menu_entry_pathdir_for_directory(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; uint64_t flags = setting->flags; return flags & SD_FLAG_PATH_DIR; } void menu_entry_pathdir_extensions(uint32_t i, char *s, size_t len) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; const char *values = setting->values; if (!values) return; strlcpy(s, values, len); } void menu_entry_reset(uint32_t i) { menu_entry_t entry; menu_entry_init(&entry); menu_entry_get(&entry, 0, i, NULL, true); menu_entry_action(&entry, (size_t)i, MENU_ACTION_START); } void menu_entry_get_value(menu_entry_t *entry, const char **value) { if (!entry || !value) return; if (menu_entry_is_password(entry)) *value = entry->password_value; else *value = entry->value; } void menu_entry_set_value(uint32_t i, const char *s) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; setting_set_with_string_representation(setting, s); } bool menu_entry_is_password(menu_entry_t *entry) { return entry->enum_idx == MENU_ENUM_LABEL_CHEEVOS_PASSWORD; } uint32_t menu_entry_num_has_range(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; uint64_t flags = setting->flags; return (flags & SD_FLAG_HAS_RANGE); } float menu_entry_num_min(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; double min = setting->min; return (float)min; } float menu_entry_num_max(uint32_t i) { file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(0); menu_file_list_cbs_t *cbs = selection_buf ? (menu_file_list_cbs_t*)selection_buf->list[i].actiondata : NULL; rarch_setting_t *setting = cbs ? cbs->setting : NULL; double max = setting->max; return (float)max; } void menu_entry_get(menu_entry_t *entry, size_t stack_idx, size_t i, void *userdata, bool use_representation) { char newpath[255]; const char *path = NULL; const char *entry_label = NULL; menu_file_list_cbs_t *cbs = NULL; file_list_t *selection_buf = menu_entries_get_selection_buf_ptr_internal(stack_idx); file_list_t *list = (userdata) ? (file_list_t*)userdata : selection_buf; bool path_enabled = entry->path_enabled; newpath[0] = '\0'; if (!list) return; path = list->list[i].path; entry_label = list->list[i].label; entry->type = list->list[i].type; entry->entry_idx = list->list[i].entry_idx; cbs = (menu_file_list_cbs_t*)list->list[i].actiondata; entry->idx = (unsigned)i; if (entry->label_enabled && !string_is_empty(entry_label)) strlcpy(entry->label, entry_label, sizeof(entry->label)); if (cbs) { const char *label = NULL; entry->enum_idx = cbs->enum_idx; entry->checked = cbs->checked; menu_entries_get_last_stack(NULL, &label, NULL, NULL, NULL); if (entry->rich_label_enabled && cbs->action_label) { cbs->action_label(list, entry->type, (unsigned)i, label, path, entry->rich_label, sizeof(entry->rich_label)); if (string_is_empty(entry->rich_label)) path_enabled = true; } if ((path_enabled || entry->value_enabled) && cbs->action_get_value && use_representation) { cbs->action_get_value(list, &entry->spacing, entry->type, (unsigned)i, label, entry->value, entry->value_enabled ? sizeof(entry->value) : 0, path, newpath, path_enabled ? sizeof(newpath) : 0); if (!string_is_empty(entry->value)) { if (menu_entry_is_password(entry)) { size_t size, i; size = strlcpy(entry->password_value, entry->value, sizeof(entry->password_value)); for (i = 0; i < size; i++) entry->password_value[i] = '*'; } } } if (entry->sublabel_enabled) { if (!string_is_empty(cbs->action_sublabel_cache)) strlcpy(entry->sublabel, cbs->action_sublabel_cache, sizeof(entry->sublabel)); else if (cbs->action_sublabel) { char tmp[MENU_SUBLABEL_MAX_LENGTH]; tmp[0] = '\0'; if (cbs->action_sublabel(list, entry->type, (unsigned)i, label, path, tmp, sizeof(tmp)) > 0) { /* If this function callback returns true, * we know that the value won't change - so we * can cache it instead. */ strlcpy(cbs->action_sublabel_cache, tmp, sizeof(cbs->action_sublabel_cache)); } strlcpy(entry->sublabel, tmp, sizeof(entry->sublabel)); } } } if (path_enabled) { if (!string_is_empty(path) && !use_representation) strlcpy(newpath, path, sizeof(newpath)); else if (cbs && cbs->setting && cbs->setting->enum_value_idx != MSG_UNKNOWN && !cbs->setting->dont_use_enum_idx_representation) strlcpy(newpath, msg_hash_to_str(cbs->setting->enum_value_idx), sizeof(newpath)); if (!string_is_empty(newpath)) strlcpy(entry->path, newpath, sizeof(entry->path)); } } bool menu_entry_is_currently_selected(unsigned id) { return id == menu_driver_selection_ptr; } /* Performs whatever actions are associated with menu entry 'i'. * * This is the most important function because it does all the work * associated with clicking on things in the UI. * * This includes loading cores and updating the * currently displayed menu. */ int menu_entry_select(uint32_t i) { menu_entry_t entry; menu_driver_selection_ptr = i; menu_entry_init(&entry); menu_entry_get(&entry, 0, i, NULL, false); return menu_entry_action(&entry, (size_t)i, MENU_ACTION_SELECT); } int menu_entry_action( menu_entry_t *entry, size_t i, enum menu_action action) { if (menu_driver_ctx && menu_driver_ctx->entry_action) return menu_driver_ctx->entry_action( menu_userdata, entry, i, action); return -1; } static void menu_list_free_list(file_list_t *list) { unsigned i; for (i = 0; i < list->size; i++) { menu_ctx_list_t list_info; list_info.list = list; list_info.idx = i; list_info.list_size = list->size; menu_driver_ctl(RARCH_MENU_CTL_LIST_FREE, &list_info); } file_list_free(list); } static void menu_list_free(menu_list_t *menu_list) { if (!menu_list) return; if (menu_list->menu_stack) { unsigned i; for (i = 0; i < menu_list->menu_stack_size; i++) { if (!menu_list->menu_stack[i]) continue; menu_list_free_list(menu_list->menu_stack[i]); menu_list->menu_stack[i] = NULL; } free(menu_list->menu_stack); } if (menu_list->selection_buf) { unsigned i; for (i = 0; i < menu_list->selection_buf_size; i++) { if (!menu_list->selection_buf[i]) continue; menu_list_free_list(menu_list->selection_buf[i]); menu_list->selection_buf[i] = NULL; } free(menu_list->selection_buf); } free(menu_list); } static menu_list_t *menu_list_new(void) { unsigned i; menu_list_t *list = (menu_list_t*)malloc(sizeof(*list)); if (!list) return NULL; list->menu_stack_size = 1; list->selection_buf_size = 1; list->selection_buf = NULL; list->menu_stack = (file_list_t**) calloc(list->menu_stack_size, sizeof(*list->menu_stack)); if (!list->menu_stack) goto error; list->selection_buf = (file_list_t**) calloc(list->selection_buf_size, sizeof(*list->selection_buf)); if (!list->selection_buf) goto error; for (i = 0; i < list->menu_stack_size; i++) list->menu_stack[i] = (file_list_t*) calloc(1, sizeof(*list->menu_stack[i])); for (i = 0; i < list->selection_buf_size; i++) list->selection_buf[i] = (file_list_t*) calloc(1, sizeof(*list->selection_buf[i])); return list; error: menu_list_free(list); return NULL; } static int menu_list_flush_stack_type(const char *needle, const char *label, unsigned type, unsigned final_type) { return needle ? !string_is_equal(needle, label) : (type != final_type); } static bool menu_list_pop_stack(menu_list_t *list, size_t idx, size_t *directory_ptr, bool animate) { menu_ctx_list_t list_info; bool refresh = false; file_list_t *menu_list = menu_list_get(list, (unsigned)idx); if (menu_list_get_stack_size(list, idx) <= 1) return false; list_info.type = MENU_LIST_PLAIN; list_info.action = 0; if (animate) menu_driver_list_cache(&list_info); if (menu_list->size != 0) { menu_ctx_list_t list_info; list_info.list = menu_list; list_info.idx = menu_list->size - 1; list_info.list_size = menu_list->size - 1; menu_driver_ctl(RARCH_MENU_CTL_LIST_FREE, &list_info); } file_list_pop(menu_list, directory_ptr); menu_driver_list_set_selection(menu_list); if (animate) menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); return true; } static void menu_list_flush_stack(menu_list_t *list, size_t idx, const char *needle, unsigned final_type) { bool refresh = false; const char *path = NULL; const char *label = NULL; unsigned type = 0; size_t entry_idx = 0; file_list_t *menu_list = menu_list_get(list, (unsigned)idx); menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); file_list_get_last(menu_list, &path, &label, &type, &entry_idx); while (menu_list_flush_stack_type( needle, label, type, final_type) != 0) { size_t new_selection_ptr = menu_driver_selection_ptr; if (!menu_list_pop_stack(list, idx, &new_selection_ptr, 1)) break; menu_driver_selection_ptr = new_selection_ptr; menu_list = menu_list_get(list, (unsigned)idx); file_list_get_last(menu_list, &path, &label, &type, &entry_idx); } } void menu_entries_get_at_offset(const file_list_t *list, size_t idx, const char **path, const char **label, unsigned *file_type, size_t *entry_idx, const char **alt) { file_list_get_at_offset(list, idx, path, label, file_type, entry_idx); if (list && alt) *alt = list->list[idx].alt ? list->list[idx].alt : list->list[idx].path; } /** * menu_entries_elem_get_first_char: * @list : File list handle. * @offset : Offset index of element. * * Gets the first character of an element in the * file list. * * Returns: first character of element in file list. **/ static int menu_entries_elem_get_first_char( file_list_t *list, unsigned offset) { int ret = 0; const char *path = NULL; if (list) if ((path = list->list[offset].alt ? list->list[offset].alt : list->list[offset].path)) ret = tolower((int)*path); /* "Normalize" non-alphabetical entries so they * are lumped together for purposes of jumping. */ if (ret < 'a') return ('a' - 1); else if (ret > 'z') return ('z' + 1); return ret; } static void menu_navigation_add_scroll_index(size_t sel) { scroll_index_list[scroll_index_size] = sel; if (!((scroll_index_size + 1) >= SCROLL_INDEX_SIZE)) scroll_index_size++; } static void menu_entries_build_scroll_indices(file_list_t *list) { int current; bool current_is_dir = false; unsigned type = 0; size_t i = 0; scroll_index_size = 0; menu_navigation_add_scroll_index(0); current = menu_entries_elem_get_first_char(list, 0); type = list->list[0].type; if (type == FILE_TYPE_DIRECTORY) current_is_dir = true; for (i = 1; i < list->size; i++) { int first = menu_entries_elem_get_first_char(list, (unsigned)i); bool is_dir = false; unsigned idx = (unsigned)i; type = list->list[idx].type; if (type == FILE_TYPE_DIRECTORY) is_dir = true; if ((current_is_dir && !is_dir) || (first > current)) menu_navigation_add_scroll_index(i); current = first; current_is_dir = is_dir; } menu_navigation_add_scroll_index(list->size - 1); } /** * Before a refresh, we could have deleted a * file on disk, causing selection_ptr to * suddendly be out of range. * * Ensure it doesn't overflow. **/ static bool menu_entries_refresh(file_list_t *list) { size_t list_size; size_t selection = menu_driver_selection_ptr; if (list->size) menu_entries_build_scroll_indices(list); list_size = menu_entries_get_size(); if ((selection >= list_size) && list_size) { size_t idx = list_size - 1; menu_driver_selection_ptr = idx; menu_driver_navigation_set(true); } else if (!list_size) { bool pending_push = true; menu_driver_ctl(MENU_NAVIGATION_CTL_CLEAR, &pending_push); } return true; } menu_file_list_cbs_t *menu_entries_get_last_stack_actiondata(void) { if (menu_entries_list) { const file_list_t *list = menu_list_get(menu_entries_list, 0); return (menu_file_list_cbs_t*)list->list[list->size - 1].actiondata; } return NULL; } /* Sets title to what the name of the current menu should be. */ int menu_entries_get_title(char *s, size_t len) { unsigned menu_type = 0; const char *path = NULL; const char *label = NULL; const file_list_t *list = menu_entries_list ? menu_list_get(menu_entries_list, 0) : NULL; menu_file_list_cbs_t *cbs = list ? (menu_file_list_cbs_t*)list->list[list->size - 1].actiondata : NULL; if (!cbs) return -1; if (cbs && cbs->action_get_title) { int ret; if (!string_is_empty(cbs->action_title_cache)) { strlcpy(s, cbs->action_title_cache, len); return 0; } menu_entries_get_last_stack(&path, &label, &menu_type, NULL, NULL); ret = cbs->action_get_title(path, label, menu_type, s, len); if (ret == 1) strlcpy(cbs->action_title_cache, s, sizeof(cbs->action_title_cache)); return ret; } return 0; } /* Sets 's' to the name of the current core * (shown at the top of the UI). */ int menu_entries_get_core_title(char *s, size_t len) { struct retro_system_info *system = runloop_get_libretro_system_info(); const char *core_name = (system && !string_is_empty(system->library_name)) ? system->library_name : msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORE); const char *core_version = (system && system->library_version) ? system->library_version : ""; #if _MSC_VER == 1200 strlcpy(s, PACKAGE_VERSION " msvc6" " - ", len); #elif _MSC_VER == 1300 strlcpy(s, PACKAGE_VERSION " msvc2002" " - ", len); #elif _MSC_VER == 1310 strlcpy(s, PACKAGE_VERSION " msvc2003" " - ", len); #elif _MSC_VER == 1400 strlcpy(s, PACKAGE_VERSION " msvc2005" " - ", len); #elif _MSC_VER == 1500 strlcpy(s, PACKAGE_VERSION " msvc2008" " - ", len); #elif _MSC_VER == 1600 strlcpy(s, PACKAGE_VERSION " msvc2010" " - ", len); #elif _MSC_VER == 1700 strlcpy(s, PACKAGE_VERSION " msvc2012" " - ", len); #elif _MSC_VER == 1800 strlcpy(s, PACKAGE_VERSION " msvc2013" " - ", len); #elif _MSC_VER == 1900 strlcpy(s, PACKAGE_VERSION " msvc2015" " - ", len); #elif _MSC_VER >= 1910 && _MSC_VER < 2000 strlcpy(s, PACKAGE_VERSION " msvc2017" " - ", len); #else strlcpy(s, PACKAGE_VERSION " - ", len); #endif strlcat(s, core_name, len); if (!string_is_empty(core_version)) { strlcat(s, " (", len); strlcat(s, core_version, len); strlcat(s, ")", len); } return 0; } file_list_t *menu_entries_get_menu_stack_ptr(size_t idx) { menu_list_t *menu_list = menu_entries_list; if (!menu_list) return NULL; return menu_list_get(menu_list, (unsigned)idx); } file_list_t *menu_entries_get_selection_buf_ptr(size_t idx) { menu_list_t *menu_list = menu_entries_list; if (!menu_list) return NULL; return menu_list_get_selection(menu_list, (unsigned)idx); } static void menu_entries_list_deinit(void) { if (menu_entries_list) menu_list_free(menu_entries_list); menu_entries_list = NULL; } static void menu_entries_settings_deinit(void) { menu_setting_free(menu_entries_list_settings); if (menu_entries_list_settings) free(menu_entries_list_settings); menu_entries_list_settings = NULL; } static bool menu_entries_init(void) { if (!(menu_entries_list = (menu_list_t*)menu_list_new())) goto error; menu_setting_ctl(MENU_SETTING_CTL_NEW, &menu_entries_list_settings); if (!menu_entries_list_settings) goto error; return true; error: menu_entries_settings_deinit(); menu_entries_list_deinit(); return false; } void menu_entries_set_checked(file_list_t *list, size_t entry_idx, bool checked) { menu_file_list_cbs_t *cbs = (menu_file_list_cbs_t*)list->list[entry_idx].actiondata; if (cbs) cbs->checked = checked; } void menu_entries_append(file_list_t *list, const char *path, const char *label, unsigned type, size_t directory_ptr, size_t entry_idx) { menu_ctx_list_t list_info; size_t idx; const char *menu_path = NULL; menu_file_list_cbs_t *cbs = NULL; if (!list || !label) return; file_list_append(list, path, label, type, directory_ptr, entry_idx); menu_entries_get_last_stack(&menu_path, NULL, NULL, NULL, NULL); idx = list->size - 1; list_info.list = list; list_info.path = path; list_info.fullpath = NULL; if (!string_is_empty(menu_path)) list_info.fullpath = strdup(menu_path); list_info.label = label; list_info.idx = idx; list_info.entry_type = type; menu_driver_list_insert(&list_info); if (list_info.fullpath) free(list_info.fullpath); file_list_free_actiondata(list, idx); cbs = (menu_file_list_cbs_t*) calloc(1, sizeof(menu_file_list_cbs_t)); if (!cbs) return; file_list_set_actiondata(list, idx, cbs); cbs->enum_idx = MSG_UNKNOWN; cbs->setting = menu_setting_find(label); menu_cbs_init(list, cbs, path, label, type, idx); } bool menu_entries_append_enum(file_list_t *list, const char *path, const char *label, enum msg_hash_enums enum_idx, unsigned type, size_t directory_ptr, size_t entry_idx) { menu_ctx_list_t list_info; size_t idx; const char *menu_path = NULL; menu_file_list_cbs_t *cbs = NULL; const char *menu_ident = menu_driver_ident(); if (!list || !label) return false; file_list_append(list, path, label, type, directory_ptr, entry_idx); menu_entries_get_last_stack(&menu_path, NULL, NULL, NULL, NULL); idx = list->size - 1; list_info.fullpath = NULL; if (!string_is_empty(menu_path)) list_info.fullpath = strdup(menu_path); list_info.list = list; list_info.path = path; list_info.label = label; list_info.idx = idx; list_info.entry_type = type; menu_driver_list_insert(&list_info); if (list_info.fullpath) free(list_info.fullpath); file_list_free_actiondata(list, idx); cbs = (menu_file_list_cbs_t*) calloc(1, sizeof(menu_file_list_cbs_t)); file_list_set_actiondata(list, idx, cbs); cbs->enum_idx = enum_idx; if ( enum_idx != MENU_ENUM_LABEL_PLAYLIST_ENTRY && enum_idx != MENU_ENUM_LABEL_PLAYLIST_COLLECTION_ENTRY && enum_idx != MENU_ENUM_LABEL_RDB_ENTRY) cbs->setting = menu_setting_find_enum(enum_idx); if (!string_is_equal(menu_ident, "null")) menu_cbs_init(list, cbs, path, label, type, idx); return true; } void menu_entries_prepend(file_list_t *list, const char *path, const char *label, enum msg_hash_enums enum_idx, unsigned type, size_t directory_ptr, size_t entry_idx) { menu_ctx_list_t list_info; size_t idx; const char *menu_path = NULL; menu_file_list_cbs_t *cbs = NULL; if (!list || !label) return; file_list_prepend(list, path, label, type, directory_ptr, entry_idx); menu_entries_get_last_stack(&menu_path, NULL, NULL, NULL, NULL); idx = 0; list_info.fullpath = NULL; if (!string_is_empty(menu_path)) list_info.fullpath = strdup(menu_path); list_info.list = list; list_info.path = path; list_info.label = label; list_info.idx = idx; list_info.entry_type = type; menu_driver_list_insert(&list_info); if (list_info.fullpath) free(list_info.fullpath); file_list_free_actiondata(list, idx); cbs = (menu_file_list_cbs_t*) calloc(1, sizeof(menu_file_list_cbs_t)); if (!cbs) return; file_list_set_actiondata(list, idx, cbs); cbs->enum_idx = enum_idx; cbs->setting = menu_setting_find_enum(cbs->enum_idx); menu_cbs_init(list, cbs, path, label, type, idx); } void menu_entries_get_last_stack(const char **path, const char **label, unsigned *file_type, enum msg_hash_enums *enum_idx, size_t *entry_idx) { file_list_t *list = NULL; if (!menu_entries_list) return; list = menu_list_get(menu_entries_list, 0); file_list_get_last(list, path, label, file_type, entry_idx); if (enum_idx) { menu_file_list_cbs_t *cbs = (menu_file_list_cbs_t*) list->list[list->size - 1].actiondata; if (cbs) *enum_idx = cbs->enum_idx; } } void menu_entries_flush_stack(const char *needle, unsigned final_type) { menu_list_t *menu_list = menu_entries_list; if (menu_list) menu_list_flush_stack(menu_list, 0, needle, final_type); } void menu_entries_pop_stack(size_t *ptr, size_t idx, bool animate) { menu_list_t *menu_list = menu_entries_list; if (menu_list) menu_list_pop_stack(menu_list, idx, ptr, animate); } size_t menu_entries_get_stack_size(size_t idx) { menu_list_t *menu_list = menu_entries_list; if (!menu_list) return 0; return menu_list_get_stack_size(menu_list, idx); } size_t menu_entries_get_size(void) { const file_list_t *list = NULL; menu_list_t *menu_list = menu_entries_list; if (!menu_list) return 0; list = menu_list_get_selection(menu_list, 0); return list->size; } bool menu_entries_ctl(enum menu_entries_ctl_state state, void *data) { switch (state) { case MENU_ENTRIES_CTL_NEEDS_REFRESH: if (menu_entries_nonblocking_refresh) return false; if (!menu_entries_need_refresh) return false; break; case MENU_ENTRIES_CTL_LIST_GET: { menu_list_t **list = (menu_list_t**)data; if (!list) return false; *list = menu_entries_list; } return true; case MENU_ENTRIES_CTL_SETTINGS_GET: { rarch_setting_t **settings = (rarch_setting_t**)data; if (!settings) return false; *settings = menu_entries_list_settings; } break; case MENU_ENTRIES_CTL_SET_REFRESH: { bool *nonblocking = (bool*)data; if (*nonblocking) menu_entries_nonblocking_refresh = true; else menu_entries_need_refresh = true; } break; case MENU_ENTRIES_CTL_UNSET_REFRESH: { bool *nonblocking = (bool*)data; if (*nonblocking) menu_entries_nonblocking_refresh = false; else menu_entries_need_refresh = false; } break; case MENU_ENTRIES_CTL_SET_START: { size_t *idx = (size_t*)data; if (idx) menu_entries_begin = *idx; } case MENU_ENTRIES_CTL_START_GET: { size_t *idx = (size_t*)data; if (!idx) return 0; *idx = menu_entries_begin; } break; case MENU_ENTRIES_CTL_REFRESH: if (!data) return false; return menu_entries_refresh((file_list_t*)data); case MENU_ENTRIES_CTL_CLEAR: { unsigned i; file_list_t *list = (file_list_t*)data; if (!list) return false; menu_driver_list_clear(list); for (i = 0; i < list->size; i++) file_list_free_actiondata(list, i); file_list_clear(list); } break; case MENU_ENTRIES_CTL_SHOW_BACK: /* Returns true if a Back button should be shown * (i.e. we are at least * one level deep in the menu hierarchy). */ if (!menu_entries_list) return false; return (menu_list_get_stack_size(menu_entries_list, 0) > 1); case MENU_ENTRIES_CTL_NONE: default: break; } return true; } static bool menu_driver_load_image(menu_ctx_load_image_t *load_image_info) { if (menu_driver_ctx && menu_driver_ctx->load_image) return menu_driver_ctx->load_image(menu_userdata, load_image_info->data, load_image_info->type); return false; } void menu_display_handle_thumbnail_upload(retro_task_t *task, void *task_data, void *user_data, const char *err) { menu_ctx_load_image_t load_image_info; struct texture_image *img = (struct texture_image*)task_data; load_image_info.data = img; load_image_info.type = MENU_IMAGE_THUMBNAIL; menu_driver_load_image(&load_image_info); image_texture_free(img); free(img); free(user_data); } void menu_display_handle_left_thumbnail_upload(retro_task_t *task, void *task_data, void *user_data, const char *err) { menu_ctx_load_image_t load_image_info; struct texture_image *img = (struct texture_image*)task_data; load_image_info.data = img; load_image_info.type = MENU_IMAGE_LEFT_THUMBNAIL; menu_driver_load_image(&load_image_info); image_texture_free(img); free(img); free(user_data); } void menu_display_handle_savestate_thumbnail_upload(retro_task_t *task, void *task_data, void *user_data, const char *err) { menu_ctx_load_image_t load_image_info; struct texture_image *img = (struct texture_image*)task_data; load_image_info.data = img; load_image_info.type = MENU_IMAGE_SAVESTATE_THUMBNAIL; menu_driver_load_image(&load_image_info); image_texture_free(img); free(img); free(user_data); } /* Function that gets called when we want to load in a * new menu wallpaper. */ void menu_display_handle_wallpaper_upload(retro_task_t *task, void *task_data, void *user_data, const char *err) { menu_ctx_load_image_t load_image_info; struct texture_image *img = (struct texture_image*)task_data; load_image_info.data = img; load_image_info.type = MENU_IMAGE_WALLPAPER; menu_driver_load_image(&load_image_info); image_texture_free(img); free(img); free(user_data); } /** * menu_driver_find_handle: * @idx : index of driver to get handle to. * * Returns: handle to menu driver at index. Can be NULL * if nothing found. **/ const void *menu_driver_find_handle(int idx) { const void *drv = menu_ctx_drivers[idx]; if (!drv) return NULL; return drv; } /** * menu_driver_find_ident: * @idx : index of driver to get handle to. * * Returns: Human-readable identifier of menu driver at index. * Can be NULL if nothing found. **/ const char *menu_driver_find_ident(int idx) { const menu_ctx_driver_t *drv = menu_ctx_drivers[idx]; if (!drv) return NULL; return drv->ident; } /** * config_get_menu_driver_options: * * Get an enumerated list of all menu driver names, * separated by '|'. * * Returns: string listing of all menu driver names, * separated by '|'. **/ const char *config_get_menu_driver_options(void) { return char_list_new_special(STRING_LIST_MENU_DRIVERS, NULL); } #ifdef HAVE_COMPRESSION /* This function gets called at first startup on Android/iOS * when we need to extract the APK contents/zip file. This * file contains assets which then get extracted to the * user's asset directories. */ static void bundle_decompressed(retro_task_t *task, void *task_data, void *user_data, const char *err) { settings_t *settings = config_get_ptr(); decompress_task_data_t *dec = (decompress_task_data_t*)task_data; if (dec && !err) command_event(CMD_EVENT_REINIT, NULL); if (err) RARCH_ERR("%s", err); if (dec) { /* delete bundle? */ free(dec->source_file); free(dec); } configuration_set_uint(settings, settings->uints.bundle_assets_extract_last_version, settings->uints.bundle_assets_extract_version_current); configuration_set_bool(settings, settings->bools.bundle_finished, true); command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL); } #endif /** * menu_init: * @data : Menu context handle. * * Create and initialize menu handle. * * Returns: menu handle on success, otherwise NULL. **/ static bool menu_init(menu_handle_t *menu_data) { settings_t *settings = config_get_ptr(); #ifdef HAVE_CONFIGFILE bool menu_show_start_screen = settings->bools.menu_show_start_screen; bool config_save_on_exit = settings->bools.config_save_on_exit; #endif /* Ensure that menu pointer input is correctly * initialised */ menu_input_reset(); if (!menu_entries_init()) return false; #ifdef HAVE_CONFIGFILE if (menu_show_start_screen) { /* We don't want the welcome dialog screen to show up * again after the first startup, so we save to config * file immediately. */ menu_dialog_push_pending(true, MENU_DIALOG_WELCOME); configuration_set_bool(settings, settings->bools.menu_show_start_screen, false); #if !(defined(PS2) && defined(DEBUG)) /* TODO: PS2 IMPROVEMENT */ if (config_save_on_exit) command_event(CMD_EVENT_MENU_SAVE_CURRENT_CONFIG, NULL); #endif } #endif if ( settings->bools.bundle_assets_extract_enable && !string_is_empty(settings->arrays.bundle_assets_src) && !string_is_empty(settings->arrays.bundle_assets_dst) #ifndef IOS /* TODO/FIXME - we should make this more generic so that * this platform-specific ifdef is no longer needed */ && (settings->uints.bundle_assets_extract_version_current != settings->uints.bundle_assets_extract_last_version) #endif ) { if (menu_dialog_push_pending(true, MENU_DIALOG_HELP_EXTRACT)) { #ifdef HAVE_COMPRESSION task_push_decompress( settings->arrays.bundle_assets_src, settings->arrays.bundle_assets_dst, NULL, settings->arrays.bundle_assets_dst_subdir, NULL, bundle_decompressed, NULL, NULL, false); #endif } } #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) menu_shader_manager_init(); #endif gfx_display_init(); return true; } const char *menu_driver_ident(void) { if (!menu_driver_is_alive()) return NULL; if (!menu_driver_ctx || !menu_driver_ctx->ident) return NULL; return menu_driver_ctx->ident; } void menu_driver_frame(video_frame_info_t *video_info) { bool menu_is_alive = video_info->menu_is_alive; if (menu_is_alive && menu_driver_ctx->frame) menu_driver_ctx->frame(menu_userdata, video_info); } bool menu_driver_get_load_content_animation_data(uintptr_t *icon, char **playlist_name) { return menu_driver_ctx && menu_driver_ctx->get_load_content_animation_data && menu_driver_ctx->get_load_content_animation_data(menu_userdata, icon, playlist_name); } /* Time format strings with AM-PM designation require special * handling due to platform dependence */ static void strftime_am_pm(char* ptr, size_t maxsize, const char* format, const struct tm* timeptr) { char *local = NULL; #if defined(__linux__) && !defined(ANDROID) strftime(ptr, maxsize, format, timeptr); #else strftime(ptr, maxsize, format, timeptr); local = local_to_utf8_string_alloc(ptr); if (!string_is_empty(local)) strlcpy(ptr, local, maxsize); if (local) { free(local); local = NULL; } #endif } /* Display the date and time - time_mode will influence how * the time representation will look like. * */ void menu_display_timedate(gfx_display_ctx_datetime_t *datetime) { if (!datetime) return; /* Trigger an update, if required */ if (menu_driver_current_time_us - menu_driver_datetime_last_time_us >= DATETIME_CHECK_INTERVAL) { time_t time_; const struct tm *tm_; menu_driver_datetime_last_time_us = menu_driver_current_time_us; /* Get current time */ time(&time_); setlocale(LC_TIME, ""); tm_ = localtime(&time_); /* Format string representation */ switch (datetime->time_mode) { case MENU_TIMEDATE_STYLE_YMD_HMS: /* YYYY-MM-DD HH:MM:SS */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%Y-%m-%d %H:%M:%S", tm_); break; case MENU_TIMEDATE_STYLE_YMD_HM: /* YYYY-MM-DD HH:MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%Y-%m-%d %H:%M", tm_); break; case MENU_TIMEDATE_STYLE_YMD: /* YYYY-MM-DD */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%Y-%m-%d", tm_); break; case MENU_TIMEDATE_STYLE_YM: /* YYYY-MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%Y-%m", tm_); break; case MENU_TIMEDATE_STYLE_MDYYYY_HMS: /* MM-DD-YYYY HH:MM:SS */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%m-%d-%Y %H:%M:%S", tm_); break; case MENU_TIMEDATE_STYLE_MDYYYY_HM: /* MM-DD-YYYY HH:MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%m-%d-%Y %H:%M", tm_); break; case MENU_TIMEDATE_STYLE_MD_HM: /* MM/DD HH:MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%m/%d %H:%M", tm_); break; case MENU_TIMEDATE_STYLE_MDYYYY: /* MM-DD-YYYY */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%m-%d-%Y", tm_); break; case MENU_TIMEDATE_STYLE_MD: /* MM-DD */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%m-%d", tm_); break; case MENU_TIMEDATE_STYLE_DDMMYYYY_HMS: /* DD/MM/YYYY HH:MM:SS */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m/%Y %H:%M:%S", tm_); break; case MENU_TIMEDATE_STYLE_DDMMYYYY_HM: /* DD/MM/YYYY HH:MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m/%Y %H:%M", tm_); break; case MENU_TIMEDATE_STYLE_DDMM_HM: /* DD/MM HH:MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m %H:%M", tm_); break; case MENU_TIMEDATE_STYLE_DDMMYYYY: /* DD/MM/YYYY */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m/%Y", tm_); break; case MENU_TIMEDATE_STYLE_DDMM: /* DD/MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m", tm_); break; case MENU_TIMEDATE_STYLE_HMS: /* HH:MM:SS */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%H:%M:%S", tm_); break; case MENU_TIMEDATE_STYLE_HM: /* HH:MM */ strftime(menu_datetime_cache, sizeof(menu_datetime_cache), "%H:%M", tm_); break; case MENU_TIMEDATE_STYLE_YMD_HMS_AM_PM: /* YYYY-MM-DD HH:MM:SS (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%Y-%m-%d %I:%M:%S %p", tm_); break; case MENU_TIMEDATE_STYLE_YMD_HM_AM_PM: /* YYYY-MM-DD HH:MM (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%Y-%m-%d %I:%M %p", tm_); break; case MENU_TIMEDATE_STYLE_MDYYYY_HMS_AM_PM: /* MM-DD-YYYY HH:MM:SS (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%m-%d-%Y %I:%M:%S %p", tm_); break; case MENU_TIMEDATE_STYLE_MDYYYY_HM_AM_PM: /* MM-DD-YYYY HH:MM (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%m-%d-%Y %I:%M %p", tm_); break; case MENU_TIMEDATE_STYLE_MD_HM_AM_PM: /* MM/DD HH:MM (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%m/%d %I:%M %p", tm_); break; case MENU_TIMEDATE_STYLE_DDMMYYYY_HMS_AM_PM: /* DD/MM/YYYY HH:MM:SS (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m/%Y %I:%M:%S %p", tm_); break; case MENU_TIMEDATE_STYLE_DDMMYYYY_HM_AM_PM: /* DD/MM/YYYY HH:MM (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m/%Y %I:%M %p", tm_); break; case MENU_TIMEDATE_STYLE_DDMM_HM_AM_PM: /* DD/MM HH:MM (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%d/%m %I:%M %p", tm_); break; case MENU_TIMEDATE_STYLE_HMS_AM_PM: /* HH:MM:SS (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%I:%M:%S %p", tm_); break; case MENU_TIMEDATE_STYLE_HM_AM_PM: /* HH:MM (am/pm) */ strftime_am_pm(menu_datetime_cache, sizeof(menu_datetime_cache), "%I:%M %p", tm_); break; } } /* Copy cached datetime string to input * menu_display_ctx_datetime_t struct */ strlcpy(datetime->s, menu_datetime_cache, datetime->len); } /* Display current (battery) power state */ void menu_display_powerstate(gfx_display_ctx_powerstate_t *powerstate) { int percent = 0; enum frontend_powerstate state = FRONTEND_POWERSTATE_NONE; if (!powerstate) return; /* Trigger an update, if required */ if (menu_driver_current_time_us - menu_driver_powerstate_last_time_us >= POWERSTATE_CHECK_INTERVAL) { menu_driver_powerstate_last_time_us = menu_driver_current_time_us; task_push_get_powerstate(); } /* Get last recorded state */ state = get_last_powerstate(&percent); /* Populate gfx_display_ctx_powerstate_t */ powerstate->battery_enabled = (state != FRONTEND_POWERSTATE_NONE) && (state != FRONTEND_POWERSTATE_NO_SOURCE); if (powerstate->battery_enabled) { powerstate->charging = (state == FRONTEND_POWERSTATE_CHARGING); powerstate->percent = percent > 0 ? (unsigned)percent : 0; snprintf(powerstate->s, powerstate->len, "%u%%", powerstate->percent); } else { powerstate->charging = false; powerstate->percent = 0; } } /* Iterate the menu driver for one frame. */ bool menu_driver_iterate(menu_ctx_iterate_t *iterate, retro_time_t current_time) { /* Get current time */ menu_driver_current_time_us = current_time; if (menu_driver_pending_quick_menu) { /* If the user had requested that the Quick Menu * be spawned during the previous frame, do this now * and exit the function to go to the next frame. */ menu_driver_pending_quick_menu = false; menu_entries_flush_stack(NULL, MENU_SETTINGS); gfx_display_set_msg_force(true); generic_action_ok_displaylist_push("", NULL, "", 0, 0, 0, ACTION_OK_DL_CONTENT_SETTINGS); menu_driver_selection_ptr = 0; return true; } if ( menu_driver_ctx && menu_driver_ctx->iterate) { if (menu_driver_ctx->iterate(menu_driver_data, menu_userdata, iterate->action) != -1) return true; } else if (generic_menu_iterate(menu_driver_data, menu_userdata, iterate->action, current_time) != -1) return true; return false; } bool menu_driver_list_cache(menu_ctx_list_t *list) { if (!list || !menu_driver_ctx || !menu_driver_ctx->list_cache) return false; menu_driver_ctx->list_cache(menu_userdata, list->type, list->action); return true; } /* Clear all the menu lists. */ bool menu_driver_list_clear(file_list_t *list) { if (!list) return false; if (menu_driver_ctx->list_clear) menu_driver_ctx->list_clear(list); return true; } bool menu_driver_list_insert(menu_ctx_list_t *list) { if (!list || !menu_driver_ctx || !menu_driver_ctx->list_insert) return false; menu_driver_ctx->list_insert(menu_userdata, list->list, list->path, list->fullpath, list->label, list->idx, list->entry_type); return true; } bool menu_driver_list_set_selection(file_list_t *list) { if (!list) return false; if (!menu_driver_ctx || !menu_driver_ctx->list_set_selection) return false; menu_driver_ctx->list_set_selection(menu_userdata, list); return true; } static void menu_driver_set_id(void) { const char *driver_name = NULL; gfx_display_set_driver_id(MENU_DRIVER_ID_UNKNOWN); if (!menu_driver_ctx || !menu_driver_ctx->ident) return; driver_name = menu_driver_ctx->ident; if (string_is_empty(driver_name)) return; if (string_is_equal(driver_name, "rgui")) gfx_display_set_driver_id(MENU_DRIVER_ID_RGUI); else if (string_is_equal(driver_name, "ozone")) gfx_display_set_driver_id(MENU_DRIVER_ID_OZONE); else if (string_is_equal(driver_name, "glui")) gfx_display_set_driver_id(MENU_DRIVER_ID_GLUI); else if (string_is_equal(driver_name, "xmb")) gfx_display_set_driver_id(MENU_DRIVER_ID_XMB); else if (string_is_equal(driver_name, "xui")) gfx_display_set_driver_id(MENU_DRIVER_ID_XUI); else if (string_is_equal(driver_name, "stripes")) gfx_display_set_driver_id(MENU_DRIVER_ID_STRIPES); } static bool generic_menu_init_list(void *data) { menu_displaylist_info_t info; file_list_t *menu_stack = menu_entries_get_menu_stack_ptr(0); file_list_t *selection_buf = menu_entries_get_selection_buf_ptr(0); menu_displaylist_info_init(&info); info.label = strdup( msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU)); info.enum_idx = MENU_ENUM_LABEL_MAIN_MENU; menu_entries_append_enum(menu_stack, info.path, info.label, MENU_ENUM_LABEL_MAIN_MENU, info.type, info.flags, 0); info.list = selection_buf; if (menu_displaylist_ctl(DISPLAYLIST_MAIN_MENU, &info)) menu_displaylist_process(&info); menu_displaylist_info_free(&info); return true; } static bool menu_driver_init_internal(bool video_is_threaded) { /* ID must be set first, since it is required for * the proper determination of pixel/dpi scaling * parameters (and some menu drivers fetch the * current pixel/dpi scale during 'menu_driver_ctx->init()') */ menu_driver_set_id(); if (menu_driver_ctx->init) { menu_driver_data = (menu_handle_t*) menu_driver_ctx->init(&menu_userdata, video_is_threaded); menu_driver_data->userdata = menu_userdata; menu_driver_data->driver_ctx = menu_driver_ctx; } if (!menu_driver_data || !menu_init(menu_driver_data)) return false; { /* TODO/FIXME - can we get rid of this? Is this needed? */ settings_t *settings = config_get_ptr(); configuration_set_string(settings, settings->arrays.menu_driver, menu_driver_ctx->ident); } if (menu_driver_ctx->lists_init) { if (!menu_driver_ctx->lists_init(menu_driver_data)) return false; } else generic_menu_init_list(menu_driver_data); return true; } bool menu_driver_init(bool video_is_threaded) { command_event(CMD_EVENT_CORE_INFO_INIT, NULL); command_event(CMD_EVENT_LOAD_CORE_PERSIST, NULL); if ( menu_driver_data || menu_driver_init_internal(video_is_threaded)) { if (menu_driver_ctx && menu_driver_ctx->context_reset) { menu_driver_ctx->context_reset(menu_userdata, video_is_threaded); return true; } } /* If driver initialisation failed, must reset * driver id to 'unknown' */ gfx_display_set_driver_id(MENU_DRIVER_ID_UNKNOWN); return false; } void menu_driver_navigation_set(bool scroll) { if (menu_driver_ctx->navigation_set) menu_driver_ctx->navigation_set(menu_userdata, scroll); } void menu_driver_populate_entries(menu_displaylist_info_t *info) { if (menu_driver_ctx && menu_driver_ctx->populate_entries) menu_driver_ctx->populate_entries( menu_userdata, info->path, info->label, info->type); } bool menu_driver_push_list(menu_ctx_displaylist_t *disp_list) { if (menu_driver_ctx->list_push) if (menu_driver_ctx->list_push(menu_driver_data, menu_userdata, disp_list->info, disp_list->type) == 0) return true; return false; } void menu_driver_set_thumbnail_system(char *s, size_t len) { if (menu_driver_ctx && menu_driver_ctx->set_thumbnail_system) menu_driver_ctx->set_thumbnail_system(menu_userdata, s, len); } void menu_driver_get_thumbnail_system(char *s, size_t len) { if (menu_driver_ctx && menu_driver_ctx->get_thumbnail_system) menu_driver_ctx->get_thumbnail_system(menu_userdata, s, len); } void menu_driver_set_thumbnail_content(char *s, size_t len) { if (menu_driver_ctx && menu_driver_ctx->set_thumbnail_content) menu_driver_ctx->set_thumbnail_content(menu_userdata, s); } /* Teardown function for the menu driver. */ void menu_driver_destroy(void) { menu_driver_pending_quick_menu = false; menu_driver_prevent_populate = false; menu_driver_data_own = false; menu_driver_ctx = NULL; menu_userdata = NULL; } bool menu_driver_list_get_entry(menu_ctx_list_t *list) { if (!menu_driver_ctx || !menu_driver_ctx->list_get_entry) { list->entry = NULL; return false; } list->entry = menu_driver_ctx->list_get_entry(menu_userdata, list->type, (unsigned int)list->idx); return true; } bool menu_driver_list_get_selection(menu_ctx_list_t *list) { if (!menu_driver_ctx || !menu_driver_ctx->list_get_selection) { list->selection = 0; return false; } list->selection = menu_driver_ctx->list_get_selection(menu_userdata); return true; } bool menu_driver_list_get_size(menu_ctx_list_t *list) { if (!menu_driver_ctx || !menu_driver_ctx->list_get_size) { list->size = 0; return false; } list->size = menu_driver_ctx->list_get_size(menu_userdata, list->type); return true; } bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) { switch (state) { case RARCH_MENU_CTL_SET_PENDING_QUICK_MENU: menu_entries_flush_stack(NULL, MENU_SETTINGS); menu_driver_pending_quick_menu = true; break; case RARCH_MENU_CTL_FIND_DRIVER: { int i; driver_ctx_info_t drv; settings_t *settings = config_get_ptr(); drv.label = "menu_driver"; drv.s = settings->arrays.menu_driver; driver_ctl(RARCH_DRIVER_CTL_FIND_INDEX, &drv); i = (int)drv.len; if (i >= 0) menu_driver_ctx = (const menu_ctx_driver_t*) menu_driver_find_handle(i); else { if (verbosity_is_enabled()) { unsigned d; RARCH_WARN("Couldn't find any menu driver named \"%s\"\n", settings->arrays.menu_driver); RARCH_LOG_OUTPUT("Available menu drivers are:\n"); for (d = 0; menu_driver_find_handle(d); d++) RARCH_LOG_OUTPUT("\t%s\n", menu_driver_find_ident(d)); RARCH_WARN("Going to default to first menu driver...\n"); } menu_driver_ctx = (const menu_ctx_driver_t*) menu_driver_find_handle(0); if (!menu_driver_ctx) { retroarch_fail(1, "find_menu_driver()"); return false; } } } break; case RARCH_MENU_CTL_SET_PREVENT_POPULATE: menu_driver_prevent_populate = true; break; case RARCH_MENU_CTL_UNSET_PREVENT_POPULATE: menu_driver_prevent_populate = false; break; case RARCH_MENU_CTL_IS_PREVENT_POPULATE: return menu_driver_prevent_populate; case RARCH_MENU_CTL_SET_OWN_DRIVER: menu_driver_data_own = true; break; case RARCH_MENU_CTL_UNSET_OWN_DRIVER: menu_driver_data_own = false; break; case RARCH_MENU_CTL_OWNS_DRIVER: return menu_driver_data_own; case RARCH_MENU_CTL_DEINIT: if (menu_driver_ctx && menu_driver_ctx->context_destroy) menu_driver_ctx->context_destroy(menu_userdata); if (menu_driver_data_own) return true; playlist_free_cached(); #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) menu_shader_manager_free(); #endif #ifdef HAVE_NETWORKING core_updater_list_free_cached(); #endif if (menu_driver_data) { unsigned i; scroll_acceleration = 0; menu_driver_selection_ptr = 0; scroll_index_size = 0; for (i = 0; i < SCROLL_INDEX_SIZE; i++) scroll_index_list[i] = 0; menu_input_reset(); if (menu_driver_ctx && menu_driver_ctx->free) menu_driver_ctx->free(menu_userdata); if (menu_userdata) free(menu_userdata); menu_userdata = NULL; gfx_display_set_driver_id(MENU_DRIVER_ID_UNKNOWN); #ifndef HAVE_DYNAMIC if (frontend_driver_has_fork()) #endif { rarch_system_info_t *system = runloop_get_system_info(); libretro_free_system_info(&system->info); memset(&system->info, 0, sizeof(struct retro_system_info)); } gfx_display_free(); menu_entries_settings_deinit(); menu_entries_list_deinit(); if (menu_driver_data->core_buf) free(menu_driver_data->core_buf); menu_driver_data->core_buf = NULL; menu_entries_need_refresh = false; menu_entries_nonblocking_refresh = false; menu_entries_begin = 0; command_event(CMD_EVENT_HISTORY_DEINIT, NULL); rarch_favorites_deinit(); menu_dialog_reset(); free(menu_driver_data); } menu_driver_data = NULL; break; case RARCH_MENU_CTL_LIST_FREE: { menu_ctx_list_t *list = (menu_ctx_list_t*)data; if (menu_driver_ctx) { if (menu_driver_ctx->list_free) menu_driver_ctx->list_free(list->list, list->idx, list->list_size); } if (list->list) { file_list_free_userdata (list->list, list->idx); file_list_free_actiondata(list->list, list->idx); } } break; case RARCH_MENU_CTL_ENVIRONMENT: { menu_ctx_environment_t *menu_environ = (menu_ctx_environment_t*)data; if (menu_driver_ctx->environ_cb) { if (menu_driver_ctx->environ_cb(menu_environ->type, menu_environ->data, menu_userdata) == 0) return true; } } return false; case RARCH_MENU_CTL_POINTER_DOWN: { menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data; if (!menu_driver_ctx || !menu_driver_ctx->pointer_down) { point->retcode = 0; return false; } point->retcode = menu_driver_ctx->pointer_down(menu_userdata, point->x, point->y, point->ptr, point->cbs, point->entry, point->action); } break; case RARCH_MENU_CTL_POINTER_UP: { menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data; if (!menu_driver_ctx || !menu_driver_ctx->pointer_up) { point->retcode = 0; return false; } point->retcode = menu_driver_ctx->pointer_up(menu_userdata, point->x, point->y, point->ptr, point->gesture, point->cbs, point->entry, point->action); } break; case RARCH_MENU_CTL_OSK_PTR_AT_POS: { unsigned width = 0; unsigned height = 0; menu_ctx_pointer_t *point = (menu_ctx_pointer_t*)data; if (!menu_driver_ctx || !menu_driver_ctx->osk_ptr_at_pos) { point->retcode = 0; return false; } video_driver_get_size(&width, &height); point->retcode = menu_driver_ctx->osk_ptr_at_pos(menu_userdata, point->x, point->y, width, height); } break; case RARCH_MENU_CTL_BIND_INIT: { menu_ctx_bind_t *bind = (menu_ctx_bind_t*)data; if (!menu_driver_ctx || !menu_driver_ctx->bind_init) { bind->retcode = 0; return false; } bind->retcode = menu_driver_ctx->bind_init( bind->cbs, bind->path, bind->label, bind->type, bind->idx); } break; case RARCH_MENU_CTL_UPDATE_THUMBNAIL_PATH: { size_t selection = menu_driver_selection_ptr; if (!menu_driver_ctx || !menu_driver_ctx->update_thumbnail_path) return false; menu_driver_ctx->update_thumbnail_path(menu_userdata, (unsigned)selection, 'L'); menu_driver_ctx->update_thumbnail_path(menu_userdata, (unsigned)selection, 'R'); } break; case RARCH_MENU_CTL_UPDATE_THUMBNAIL_IMAGE: { if (!menu_driver_ctx || !menu_driver_ctx->update_thumbnail_image) return false; menu_driver_ctx->update_thumbnail_image(menu_userdata); } break; case RARCH_MENU_CTL_REFRESH_THUMBNAIL_IMAGE: { unsigned *i = (unsigned*)data; if (!i || !menu_driver_ctx || !menu_driver_ctx->refresh_thumbnail_image) return false; menu_driver_ctx->refresh_thumbnail_image(menu_userdata, *i); } break; case RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_PATH: { size_t selection = menu_driver_selection_ptr; if (!menu_driver_ctx || !menu_driver_ctx->update_savestate_thumbnail_path) return false; menu_driver_ctx->update_savestate_thumbnail_path(menu_userdata, (unsigned)selection); } break; case RARCH_MENU_CTL_UPDATE_SAVESTATE_THUMBNAIL_IMAGE: { if (!menu_driver_ctx || !menu_driver_ctx->update_savestate_thumbnail_image) return false; menu_driver_ctx->update_savestate_thumbnail_image(menu_userdata); } break; case MENU_NAVIGATION_CTL_CLEAR: { bool *pending_push = (bool*)data; /* Always set current selection to first entry */ menu_driver_selection_ptr = 0; /* menu_driver_navigation_set() will be called * at the next 'push'. * If a push is *not* pending, have to do it here * instead */ if (!(*pending_push)) { menu_driver_navigation_set(true); if (menu_driver_ctx->navigation_clear) menu_driver_ctx->navigation_clear( menu_userdata, *pending_push); } } break; case MENU_NAVIGATION_CTL_INCREMENT: { settings_t *settings = config_get_ptr(); unsigned scroll_speed = *((unsigned*)data); size_t menu_list_size = menu_entries_get_size(); bool wraparound_enable = settings->bools.menu_navigation_wraparound_enable; if (menu_driver_selection_ptr >= menu_list_size - 1 && !wraparound_enable) return false; if ((menu_driver_selection_ptr + scroll_speed) < menu_list_size) { size_t idx = menu_driver_selection_ptr + scroll_speed; menu_driver_selection_ptr = idx; menu_driver_navigation_set(true); } else { if (wraparound_enable) { bool pending_push = false; menu_driver_ctl(MENU_NAVIGATION_CTL_CLEAR, &pending_push); } else if (menu_list_size > 0) menu_driver_ctl(MENU_NAVIGATION_CTL_SET_LAST, NULL); } if (menu_driver_ctx->navigation_increment) menu_driver_ctx->navigation_increment(menu_userdata); } break; case MENU_NAVIGATION_CTL_DECREMENT: { size_t idx = 0; settings_t *settings = config_get_ptr(); unsigned scroll_speed = *((unsigned*)data); size_t menu_list_size = menu_entries_get_size(); bool wraparound_enable = settings->bools.menu_navigation_wraparound_enable; if (menu_driver_selection_ptr == 0 && !wraparound_enable) return false; if (menu_driver_selection_ptr >= scroll_speed) idx = menu_driver_selection_ptr - scroll_speed; else { idx = menu_list_size - 1; if (!wraparound_enable) idx = 0; } menu_driver_selection_ptr = idx; menu_driver_navigation_set(true); if (menu_driver_ctx->navigation_decrement) menu_driver_ctx->navigation_decrement(menu_userdata); } break; case MENU_NAVIGATION_CTL_SET_LAST: { size_t menu_list_size = menu_entries_get_size(); size_t new_selection = menu_list_size - 1; menu_driver_selection_ptr = new_selection; if (menu_driver_ctx->navigation_set_last) menu_driver_ctx->navigation_set_last(menu_userdata); } break; case MENU_NAVIGATION_CTL_ASCEND_ALPHABET: { size_t i = 0; size_t menu_list_size = menu_entries_get_size(); if (!scroll_index_size) return false; if (menu_driver_selection_ptr == scroll_index_list[scroll_index_size - 1]) menu_driver_selection_ptr = menu_list_size - 1; else { while (i < scroll_index_size - 1 && scroll_index_list[i + 1] <= menu_driver_selection_ptr) i++; menu_driver_selection_ptr = scroll_index_list[i + 1]; if (menu_driver_selection_ptr >= menu_list_size) menu_driver_selection_ptr = menu_list_size - 1; } if (menu_driver_ctx->navigation_ascend_alphabet) menu_driver_ctx->navigation_ascend_alphabet( menu_userdata, &menu_driver_selection_ptr); } break; case MENU_NAVIGATION_CTL_DESCEND_ALPHABET: { size_t i = 0; if (!scroll_index_size) return false; if (menu_driver_selection_ptr == 0) return false; i = scroll_index_size - 1; while (i && scroll_index_list[i - 1] >= menu_driver_selection_ptr) i--; if (i > 0) menu_driver_selection_ptr = scroll_index_list[i - 1]; if (menu_driver_ctx->navigation_descend_alphabet) menu_driver_ctx->navigation_descend_alphabet( menu_userdata, &menu_driver_selection_ptr); } break; case MENU_NAVIGATION_CTL_GET_SCROLL_ACCEL: { size_t *sel = (size_t*)data; if (!sel) return false; *sel = scroll_acceleration; } break; case MENU_NAVIGATION_CTL_SET_SCROLL_ACCEL: { size_t *sel = (size_t*)data; if (!sel) return false; scroll_acceleration = (unsigned)(*sel); } break; default: case RARCH_MENU_CTL_NONE: break; } return true; }