diff --git a/Makefile.common b/Makefile.common index b658fae21b..69ae1a538f 100644 --- a/Makefile.common +++ b/Makefile.common @@ -1088,7 +1088,8 @@ ifeq ($(HAVE_MENU_COMMON), 1) menu/cbs/menu_cbs_label.o \ menu/cbs/menu_cbs_sublabel.o \ menu/cbs/menu_cbs_title.o \ - menu/menu_displaylist.o + menu/menu_displaylist.o \ + menu/menu_contentless_cores.o endif ifeq ($(HAVE_GFX_WIDGETS), 1) diff --git a/config.def.h b/config.def.h index 5f0dafee8d..2dfa1c76d3 100644 --- a/config.def.h +++ b/config.def.h @@ -730,6 +730,7 @@ static const bool content_show_playlists = true; #if defined(HAVE_LIBRETRODB) #define DEFAULT_MENU_CONTENT_SHOW_EXPLORE true #endif +#define DEFAULT_MENU_CONTENT_SHOW_CONTENTLESS_CORES MENU_CONTENTLESS_CORES_DISPLAY_SINGLE_PURPOSE #ifdef HAVE_XMB #define DEFAULT_XMB_ANIMATION 0 diff --git a/configuration.c b/configuration.c index 7c725f2e92..4908d34dc3 100644 --- a/configuration.c +++ b/configuration.c @@ -2162,6 +2162,7 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("menu_ticker_type", &settings->uints.menu_ticker_type, true, DEFAULT_MENU_TICKER_TYPE, false); SETTING_UINT("menu_scroll_delay", &settings->uints.menu_scroll_delay, true, DEFAULT_MENU_SCROLL_DELAY, false); SETTING_UINT("content_show_add_entry", &settings->uints.menu_content_show_add_entry, true, DEFAULT_MENU_CONTENT_SHOW_ADD_ENTRY, false); + SETTING_UINT("content_show_contentless_cores", &settings->uints.menu_content_show_contentless_cores, true, DEFAULT_MENU_CONTENT_SHOW_CONTENTLESS_CORES, false); SETTING_UINT("menu_screensaver_timeout", &settings->uints.menu_screensaver_timeout, true, DEFAULT_MENU_SCREENSAVER_TIMEOUT, false); #if defined(HAVE_MATERIALUI) || defined(HAVE_XMB) || defined(HAVE_OZONE) SETTING_UINT("menu_screensaver_animation", &settings->uints.menu_screensaver_animation, true, DEFAULT_MENU_SCREENSAVER_ANIMATION, false); diff --git a/configuration.h b/configuration.h index a9712d441f..4dfae63259 100644 --- a/configuration.h +++ b/configuration.h @@ -295,6 +295,7 @@ typedef struct settings unsigned menu_ticker_type; unsigned menu_scroll_delay; unsigned menu_content_show_add_entry; + unsigned menu_content_show_contentless_cores; unsigned menu_screensaver_timeout; unsigned menu_screensaver_animation; diff --git a/core_info.c b/core_info.c index 1ace5defbb..ce471d700f 100644 --- a/core_info.c +++ b/core_info.c @@ -48,7 +48,7 @@ /* Core Info Cache START */ /*************************/ -#define CORE_INFO_CACHE_VERSION "1.1" +#define CORE_INFO_CACHE_VERSION "1.2" #define CORE_INFO_CACHE_DEFAULT_CAPACITY 8 /* TODO/FIXME: Apparently rzip compression is an issue on UWP */ @@ -203,6 +203,8 @@ static bool CCJSONObjectMemberHandler(void *context, } else if (string_is_equal(pValue, "supports_no_game")) pCtx->current_entry_bool_val = &pCtx->core_info->supports_no_game; + else if (string_is_equal(pValue, "single_purpose")) + pCtx->current_entry_bool_val = &pCtx->core_info->single_purpose; else if (string_is_equal(pValue, "savestate_support_level")) pCtx->current_entry_uint_val = &pCtx->core_info->savestate_support_level; break; @@ -472,6 +474,7 @@ static void core_info_copy(core_info_t *src, core_info_t *dst) dst->savestate_support_level = src->savestate_support_level; dst->has_info = src->has_info; dst->supports_no_game = src->supports_no_game; + dst->single_purpose = src->single_purpose; dst->database_match_archive_member = src->database_match_archive_member; dst->is_experimental = src->is_experimental; dst->is_locked = src->is_locked; @@ -567,6 +570,7 @@ static void core_info_transfer(core_info_t *src, core_info_t *dst) dst->savestate_support_level = src->savestate_support_level; dst->has_info = src->has_info; dst->supports_no_game = src->supports_no_game; + dst->single_purpose = src->single_purpose; dst->database_match_archive_member = src->database_match_archive_member; dst->is_experimental = src->is_experimental; dst->is_locked = src->is_locked; @@ -1118,6 +1122,14 @@ static bool core_info_cache_write(core_info_cache_list_t *list, const char *info rjsonwriter_add_comma(writer); rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); + rjsonwriter_add_string(writer, "single_purpose"); + rjsonwriter_add_colon(writer); + rjsonwriter_add_space(writer); + rjsonwriter_add_bool(writer, info->single_purpose); + rjsonwriter_add_comma(writer); + rjsonwriter_add_newline(writer); + rjsonwriter_add_spaces(writer, 6); rjsonwriter_add_string(writer, "database_match_archive_member"); rjsonwriter_add_colon(writer); @@ -1746,6 +1758,10 @@ static void core_info_parse_config_file( &tmp_bool)) info->supports_no_game = tmp_bool; + if (config_get_bool(conf, "single_purpose", + &tmp_bool)) + info->single_purpose = tmp_bool; + if (config_get_bool(conf, "database_match_archive_member", &tmp_bool)) info->database_match_archive_member = tmp_bool; @@ -2178,6 +2194,7 @@ bool core_info_init_current_core(void) return false; current->has_info = false; current->supports_no_game = false; + current->single_purpose = false; current->database_match_archive_member = false; current->is_experimental = false; current->is_locked = false; diff --git a/core_info.h b/core_info.h index 4fe4425757..3096c38e12 100644 --- a/core_info.h +++ b/core_info.h @@ -104,6 +104,7 @@ typedef struct uint32_t savestate_support_level; bool has_info; bool supports_no_game; + bool single_purpose; bool database_match_archive_member; bool is_experimental; bool is_locked; diff --git a/driver.c b/driver.c index 99e8cfb788..a7d050e3c4 100644 --- a/driver.c +++ b/driver.c @@ -627,6 +627,7 @@ void drivers_init( #ifdef HAVE_LIBRETRODB menu_explore_context_init(); #endif + menu_contentless_cores_context_init(); } } @@ -694,6 +695,7 @@ void driver_uninit(int flags) #ifdef HAVE_LIBRETRODB menu_explore_context_deinit(); #endif + menu_contentless_cores_context_deinit(); menu_driver_ctl(RARCH_MENU_CTL_DEINIT, NULL); } diff --git a/griffin/griffin.c b/griffin/griffin.c index 2c7490d577..0478e37fec 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -1416,6 +1416,7 @@ MENU #include "../menu/cbs/menu_cbs_label.c" #include "../menu/cbs/menu_cbs_sublabel.c" #include "../menu/menu_displaylist.c" +#include "../menu/menu_contentless_cores.c" #ifdef HAVE_LIBRETRODB #include "../menu/menu_explore.c" #include "../tasks/task_menu_explore.c" diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 496b40b77a..2aa44f98fa 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -82,6 +82,14 @@ MSG_HASH( MENU_ENUM_LABEL_EXPLORE_INITIALISING_LIST, "explore_initialising_list" ) +MSG_HASH( + MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB, + "contentless_cores_tab" + ) +MSG_HASH( + MENU_ENUM_LABEL_CONTENTLESS_CORE, + "contentless_core" + ) MSG_HASH( MENU_ENUM_LABEL_ADD_TAB, "add_tab" @@ -768,6 +776,10 @@ MSG_HASH( MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST, "deferred_explore_list" ) +MSG_HASH( + MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST, + "deferred_contentless_cores_list" + ) MSG_HASH( MENU_ENUM_LABEL_DEFERRED_NETPLAY, "deferred_netplay" @@ -3752,6 +3764,10 @@ MSG_HASH( MENU_ENUM_LABEL_CONTENT_SHOW_EXPLORE, "content_show_explore" ) +MSG_HASH( + MENU_ENUM_LABEL_CONTENT_SHOW_CONTENTLESS_CORES, + "content_show_contentless_cores" + ) MSG_HASH( MENU_ENUM_LABEL_CONTENT_SHOW_FAVORITES, "content_show_favorites" @@ -4070,6 +4086,10 @@ MSG_HASH( MENU_ENUM_LABEL_GOTO_EXPLORE, "goto_explore" ) +MSG_HASH( + MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES, + "goto_contentless_cores" + ) MSG_HASH( MENU_ENUM_LABEL_MATERIALUI_ICONS_ENABLE, "materialui_icons_enable" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index a10cb12b77..a35de8bdd5 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -44,6 +44,10 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_EXPLORE_TAB, "Explore" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CONTENTLESS_CORES_TAB, + "Standalone Cores" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_ADD_TAB, "Import Content" @@ -286,6 +290,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_GOTO_EXPLORE, "Browse all content matching the database via a categorized search interface." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_GOTO_CONTENTLESS_CORES, + "Standalone Cores" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_GOTO_CONTENTLESS_CORES, + "Installed cores which can operate without loading content will appear here." + ) /* Main Menu > Online Updater */ @@ -4620,6 +4632,22 @@ MSG_HASH( MENU_ENUM_SUBLABEL_CONTENT_SHOW_EXPLORE, "Show the content explorer option. (Restart Required on Ozone/XMB)" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_CONTENT_SHOW_CONTENTLESS_CORES, + "Show 'Standalone Cores'" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_CONTENT_SHOW_CONTENTLESS_CORES, + "Specify the type of core (if any) to show in the 'Standalone Cores' menu. (Restart Required on Ozone/XMB)" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_ALL, + "All" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_SINGLE_PURPOSE, + "Single-Use" + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_TIMEDATE_ENABLE, "Show Date and Time" diff --git a/menu/cbs/menu_cbs_cancel.c b/menu/cbs/menu_cbs_cancel.c index 7280ae379e..8d253cf919 100644 --- a/menu/cbs/menu_cbs_cancel.c +++ b/menu/cbs/menu_cbs_cancel.c @@ -92,6 +92,13 @@ int action_cancel_pop_default(const char *path, return 0; } +static int action_cancel_contentless_core(const char *path, + const char *label, unsigned type, size_t idx) +{ + menu_state_get_ptr()->contentless_core_ptr = 0; + return action_cancel_pop_default(path, label, type, idx) ; +} + #ifdef HAVE_CHEATS static int action_cancel_cheat_details(const char *path, const char *label, unsigned type, size_t idx) @@ -164,6 +171,11 @@ static int menu_cbs_init_bind_cancel_compare_type( case FILE_TYPE_DOWNLOAD_CORE: BIND_ACTION_CANCEL(cbs, action_cancel_core_content); return 0; + case MENU_SETTING_ACTION_CONTENTLESS_CORE_RUN: + BIND_ACTION_CANCEL(cbs, action_cancel_contentless_core); + return 0; + default: + break; } #ifdef HAVE_CHEATS diff --git a/menu/cbs/menu_cbs_deferred_push.c b/menu/cbs/menu_cbs_deferred_push.c index d08093865f..a439abddd9 100644 --- a/menu/cbs/menu_cbs_deferred_push.c +++ b/menu/cbs/menu_cbs_deferred_push.c @@ -648,6 +648,7 @@ GENERIC_DEFERRED_PUSH_GENERAL(deferred_music_history_list, PUSH_DEFAULT, DISPLAY GENERIC_DEFERRED_PUSH_GENERAL(deferred_image_history_list, PUSH_DEFAULT, DISPLAYLIST_IMAGES_HISTORY) GENERIC_DEFERRED_PUSH_GENERAL(deferred_video_history_list, PUSH_DEFAULT, DISPLAYLIST_VIDEO_HISTORY) GENERIC_DEFERRED_PUSH_GENERAL(deferred_explore_list, PUSH_DEFAULT, DISPLAYLIST_EXPLORE) +GENERIC_DEFERRED_PUSH_GENERAL(deferred_contentless_cores_list, PUSH_DEFAULT, DISPLAYLIST_CONTENTLESS_CORES) GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST) GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_special, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_SPECIAL) GENERIC_DEFERRED_PUSH_GENERAL(deferred_push_dropdown_box_list_resolution, PUSH_DEFAULT, DISPLAYLIST_DROPDOWN_LIST_RESOLUTION) @@ -759,6 +760,7 @@ static int menu_cbs_init_bind_deferred_push_compare_label( {MENU_ENUM_LABEL_DEFERRED_IMAGES_LIST, deferred_image_history_list}, {MENU_ENUM_LABEL_DEFERRED_VIDEO_LIST, deferred_video_history_list}, {MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST, deferred_explore_list}, + {MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST, deferred_contentless_cores_list}, {MENU_ENUM_LABEL_DEFERRED_INPUT_SETTINGS_LIST, deferred_push_input_settings_list}, {MENU_ENUM_LABEL_DEFERRED_INPUT_MENU_SETTINGS_LIST, deferred_push_input_menu_settings_list}, {MENU_ENUM_LABEL_DEFERRED_INPUT_TURBO_FIRE_SETTINGS_LIST, deferred_push_input_turbo_fire_settings_list}, diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c index b8b1e53384..655c2f5b97 100644 --- a/menu/cbs/menu_cbs_get_value.c +++ b/menu/cbs/menu_cbs_get_value.c @@ -519,6 +519,25 @@ static void menu_action_setting_disp_set_label_core_manager_entry( } } +static void menu_action_setting_disp_set_label_contentless_core( + file_list_t* list, + unsigned *w, unsigned type, unsigned i, + const char *label, + char *s, size_t len, + const char *path, + char *s2, size_t len2) +{ + const char *alt = list->list[i].alt + ? list->list[i].alt + : list->list[i].path; + + *s = '\0'; + *w = 0; + + if (alt) + strlcpy(s2, alt, len2); +} + #ifndef HAVE_LAKKA_SWITCH #ifdef HAVE_LAKKA static void menu_action_setting_disp_cpu_gov_mode( @@ -1912,6 +1931,10 @@ static int menu_cbs_init_bind_get_string_representation_compare_label( BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_core_manager_entry); break; + case MENU_ENUM_LABEL_CONTENTLESS_CORE: + BIND_ACTION_GET_VALUE(cbs, + menu_action_setting_disp_set_label_contentless_core); + break; case MENU_ENUM_LABEL_CORE_OPTION_OVERRIDE_INFO: BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_core_option_override_info); @@ -2120,6 +2143,7 @@ static int menu_cbs_init_bind_get_string_representation_compare_type( case MENU_SETTING_ACTION_DELETE_ENTRY: case MENU_SETTING_ACTION_CORE_DISK_OPTIONS: case MENU_EXPLORE_TAB: + case MENU_CONTENTLESS_CORES_TAB: BIND_ACTION_GET_VALUE(cbs, menu_action_setting_disp_set_label_menu_more); break; diff --git a/menu/cbs/menu_cbs_left.c b/menu/cbs/menu_cbs_left.c index af19734db6..eb346745d3 100644 --- a/menu/cbs/menu_cbs_left.c +++ b/menu/cbs/menu_cbs_left.c @@ -1006,6 +1006,7 @@ static int menu_cbs_init_bind_left_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_SUBSYSTEM_LOAD: case MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM: case MENU_ENUM_LABEL_EXPLORE_ITEM: + case MENU_ENUM_LABEL_CONTENTLESS_CORE: case MENU_ENUM_LABEL_NO_SETTINGS_FOUND: BIND_ACTION_LEFT(cbs, action_left_mainmenu); break; @@ -1043,6 +1044,7 @@ static int menu_cbs_init_bind_left_compare_label(menu_file_list_cbs_t *cbs, break; case MENU_ENUM_LABEL_NO_ITEMS: case MENU_ENUM_LABEL_NO_PLAYLIST_ENTRIES_AVAILABLE: + case MENU_ENUM_LABEL_NO_CORES_AVAILABLE: case MENU_ENUM_LABEL_EXPLORE_INITIALISING_LIST: if ( string_ends_with_size(menu_label, "_tab", diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index a840795dfb..44e56c3102 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -612,6 +612,15 @@ int generic_action_ok_displaylist_push(const char *path, info.enum_idx = MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST; dl_type = DISPLAYLIST_GENERIC; break; + case ACTION_OK_DL_CONTENTLESS_CORES_LIST: + info.type = type; + info.directory_ptr = idx; + info_path = label; + info_label = msg_hash_to_str( + MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST); + info.enum_idx = MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST; + dl_type = DISPLAYLIST_GENERIC; + break; case ACTION_OK_DL_REMAPPINGS_PORT_LIST: info.type = type; info.directory_ptr = idx; @@ -5172,14 +5181,30 @@ int action_ok_close_content(const char *path, const char *label, unsigned type, /* Unload core */ ret = generic_action_ok_command(CMD_EVENT_UNLOAD_CORE); - /* If close content was selected via 'Main Menu > Quick Menu', - * have to flush the menu stack back to 'Main Menu' + /* If close content was selected via any means other than + * 'Playlist > Quick Menu', have to flush the menu stack * (otherwise users will be presented with an empty * 'No items' quick menu, requiring needless backwards * navigation) */ if (type == MENU_SETTING_ACTION_CLOSE) { - menu_entries_flush_stack(msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU), 0); + const char *flush_target = msg_hash_to_str(MENU_ENUM_LABEL_MAIN_MENU); + const char *parent_label = NULL; + struct menu_state *menu_st = menu_state_get_ptr(); + file_list_t *list = NULL; + + if (menu_st->entries.list) + list = MENU_LIST_GET(menu_st->entries.list, 0); + if (list && (list->size > 1)) + { + file_list_get_at_offset(list, list->size - 2, NULL, &parent_label, NULL, NULL); + + if (string_is_equal(parent_label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)) || + string_is_equal(parent_label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST))) + flush_target = parent_label; + } + + menu_entries_flush_stack(flush_target, 0); /* An annoyance - some menu drivers (Ozone...) call * RARCH_MENU_CTL_SET_PREVENT_POPULATE in awkward * places, which can cause breakage here when flushing @@ -5611,6 +5636,7 @@ DEFAULT_ACTION_OK_FUNC(action_ok_cdrom_info_list, ACTION_OK_DL_CDROM_INFO_DETAIL DEFAULT_ACTION_OK_FUNC(action_ok_goto_video, ACTION_OK_DL_VIDEO_LIST) DEFAULT_ACTION_OK_FUNC(action_ok_goto_music, ACTION_OK_DL_MUSIC_LIST) DEFAULT_ACTION_OK_FUNC(action_ok_goto_explore, ACTION_OK_DL_EXPLORE_LIST) +DEFAULT_ACTION_OK_FUNC(action_ok_goto_contentless_cores, ACTION_OK_DL_CONTENTLESS_CORES_LIST) #if defined(HAVE_CG) || defined(HAVE_GLSL) || defined(HAVE_SLANG) || defined(HAVE_HLSL) DEFAULT_ACTION_OK_FUNC(action_ok_shader_preset_save, ACTION_OK_DL_SHADER_PRESET_SAVE) DEFAULT_ACTION_OK_FUNC(action_ok_shader_preset_remove, ACTION_OK_DL_SHADER_PRESET_REMOVE) @@ -6670,12 +6696,20 @@ static int action_ok_start_core(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { content_ctx_info_t content_info; + menu_ctx_list_t list_info; content_info.argc = 0; content_info.argv = NULL; content_info.args = NULL; content_info.environ_get = NULL; + /* We are going to push a new menu; ensure + * that the current one is cached for animation + * purposes */ + list_info.type = MENU_LIST_PLAIN; + list_info.action = 0; + menu_driver_list_cache(&list_info); + path_clear(RARCH_PATH_BASENAME); if (!task_push_start_current_core(&content_info)) return -1; @@ -6683,6 +6717,54 @@ static int action_ok_start_core(const char *path, return 0; } +static int action_ok_contentless_core_run(const char *path, + const char *label, unsigned type, size_t idx, size_t entry_idx) +{ + const char *core_path = path; + /* TODO/FIXME: If this function succeeds, the + * quick menu will be pushed on the subsequent + * frame via the RARCH_MENU_CTL_SET_PENDING_QUICK_MENU + * command. The way this is implemented 'breaks' the + * menu stack record, so when leaving the quick + * menu via a 'cancel' operation, the last selected + * menu index is lost. We therefore have to cache + * the current selection here, and reapply it manually + * when building the contentless cores list... */ + size_t selection = menu_navigation_get_selection(); + + if (string_is_empty(core_path)) + return menu_cbs_exit(); + + /* If core is already running, open quick menu */ + if (retroarch_ctl(RARCH_CTL_IS_CORE_LOADED, (void*)core_path) && + retroarch_ctl(RARCH_CTL_CORE_IS_RUNNING, NULL)) + { + bool flush_menu = false; + menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, &flush_menu); + menu_state_get_ptr()->contentless_core_ptr = selection; + menu_navigation_set_selection(0); + return 0; + } + + /* Cache current menu selection *before* attempting + * to start the core, to ensure consistent menu + * navigation (i.e. running a core will in general + * cause a redraw of the menu, so must record current + * position even if the operation fails) */ + menu_state_get_ptr()->contentless_core_ptr = selection; + + /* Load and start core */ + path_clear(RARCH_PATH_BASENAME); + if (!task_push_load_contentless_core_from_menu(core_path)) + { + if (retroarch_ctl(RARCH_CTL_IS_CORE_LOADED, (void*)core_path)) + generic_action_ok_command(CMD_EVENT_UNLOAD_CORE); + return -1; + } + + return 0; +} + static int action_ok_load_archive(const char *path, const char *label, unsigned type, size_t idx, size_t entry_idx) { @@ -7384,6 +7466,9 @@ static int action_ok_core_delete(const char *path, /* Reload core info files */ command_event(CMD_EVENT_CORE_INFO_INIT, NULL); + /* Force reload of contentless cores icons */ + menu_contentless_cores_free(); + /* Return to higher level menu */ return action_cancel_pop_default(NULL, NULL, 0, 0); } @@ -7787,6 +7872,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_GOTO_IMAGES, action_ok_goto_images}, {MENU_ENUM_LABEL_GOTO_VIDEO, action_ok_goto_video}, {MENU_ENUM_LABEL_GOTO_EXPLORE, action_ok_goto_explore}, + {MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES, action_ok_goto_contentless_cores}, {MENU_ENUM_LABEL_BROWSE_START, action_ok_browse_url_start}, {MENU_ENUM_LABEL_FILE_BROWSER_CORE, action_ok_load_core}, {MENU_ENUM_LABEL_FILE_BROWSER_CORE_SELECT_FROM_COLLECTION, action_ok_core_deferred_set}, @@ -8020,6 +8106,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_PLAYLIST_MANAGER_DEFAULT_CORE, action_ok_playlist_default_core}, {MENU_ENUM_LABEL_CORE_MANAGER_LIST, action_ok_push_core_manager_list}, {MENU_ENUM_LABEL_EXPLORE_TAB, action_ok_push_default}, + {MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB, action_ok_push_default}, }; for (i = 0; i < ARRAY_SIZE(ok_list); i++) @@ -8652,6 +8739,9 @@ static int menu_cbs_init_bind_ok_compare_type(menu_file_list_cbs_t *cbs, case MENU_SETTING_ACTION_AUDIO_DSP_PLUGIN_REMOVE: BIND_ACTION_OK(cbs, action_ok_audio_dsp_plugin_remove); break; + case MENU_SETTING_ACTION_CONTENTLESS_CORE_RUN: + BIND_ACTION_OK(cbs, action_ok_contentless_core_run); + break; default: return -1; } diff --git a/menu/cbs/menu_cbs_right.c b/menu/cbs/menu_cbs_right.c index c3ff3c7414..9da4edf83d 100644 --- a/menu/cbs/menu_cbs_right.c +++ b/menu/cbs/menu_cbs_right.c @@ -1126,6 +1126,7 @@ static int menu_cbs_init_bind_right_compare_label(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_SUBSYSTEM_LOAD: case MENU_ENUM_LABEL_CONNECT_NETPLAY_ROOM: case MENU_ENUM_LABEL_EXPLORE_ITEM: + case MENU_ENUM_LABEL_CONTENTLESS_CORE: case MENU_ENUM_LABEL_NO_SETTINGS_FOUND: BIND_ACTION_RIGHT(cbs, action_right_mainmenu); break; @@ -1163,6 +1164,7 @@ static int menu_cbs_init_bind_right_compare_label(menu_file_list_cbs_t *cbs, break; case MENU_ENUM_LABEL_NO_ITEMS: case MENU_ENUM_LABEL_NO_PLAYLIST_ENTRIES_AVAILABLE: + case MENU_ENUM_LABEL_NO_CORES_AVAILABLE: case MENU_ENUM_LABEL_EXPLORE_INITIALISING_LIST: if ( string_ends_with_size(menu_label, "_tab", diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index e4e32cbd22..5e2a787f4a 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -693,6 +693,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_goto_images, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_goto_music, MENU_ENUM_SUBLABEL_GOTO_MUSIC) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_goto_video, MENU_ENUM_SUBLABEL_GOTO_VIDEO) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_goto_explore, MENU_ENUM_SUBLABEL_GOTO_EXPLORE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_goto_contentless_cores, MENU_ENUM_SUBLABEL_GOTO_CONTENTLESS_CORES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_filebrowser_settings, MENU_ENUM_SUBLABEL_MENU_FILE_BROWSER_SETTINGS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_filebrowser_open_uwp_permissions, MENU_ENUM_SUBLABEL_FILE_BROWSER_OPEN_UWP_PERMISSIONS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_filebrowser_open_picker, MENU_ENUM_SUBLABEL_FILE_BROWSER_OPEN_PICKER) @@ -855,6 +856,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_import_content_tab, DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_import_content_entry, MENU_ENUM_SUBLABEL_CONTENT_SHOW_ADD_ENTRY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_playlist_tabs, MENU_ENUM_SUBLABEL_CONTENT_SHOW_PLAYLISTS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_explore_tab, MENU_ENUM_SUBLABEL_CONTENT_SHOW_EXPLORE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_contentless_cores_tab, MENU_ENUM_SUBLABEL_CONTENT_SHOW_CONTENTLESS_CORES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_main_menu_enable_settings, MENU_ENUM_SUBLABEL_XMB_MAIN_MENU_ENABLE_SETTINGS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_rgui_show_start_screen, MENU_ENUM_SUBLABEL_RGUI_SHOW_START_SCREEN) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_header_opacity, MENU_ENUM_SUBLABEL_MATERIALUI_MENU_HEADER_OPACITY) @@ -1968,6 +1970,7 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, { case MENU_ENUM_LABEL_FILE_BROWSER_CORE: case MENU_ENUM_LABEL_CORE_MANAGER_ENTRY: + case MENU_ENUM_LABEL_CONTENTLESS_CORE: BIND_ACTION_SUBLABEL(cbs, menu_action_sublabel_file_browser_core); break; #ifdef HAVE_NETWORKING @@ -2297,6 +2300,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_CONTENT_SHOW_EXPLORE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_explore_tab); break; + case MENU_ENUM_LABEL_CONTENT_SHOW_CONTENTLESS_CORES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_menu_contentless_cores_tab); + break; case MENU_ENUM_LABEL_XMB_MAIN_MENU_ENABLE_SETTINGS: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_main_menu_enable_settings); break; @@ -2321,6 +2327,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_GOTO_EXPLORE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_goto_explore); break; + case MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_goto_contentless_cores); + break; case MENU_ENUM_LABEL_GOTO_FAVORITES: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_goto_favorites); break; diff --git a/menu/cbs/menu_cbs_title.c b/menu/cbs/menu_cbs_title.c index 1d28f891df..9222ab8c13 100644 --- a/menu/cbs/menu_cbs_title.c +++ b/menu/cbs/menu_cbs_title.c @@ -754,6 +754,8 @@ DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_title_deferred_favorites_list, MENU DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_title_deferred_images_list, MENU_ENUM_LABEL_VALUE_GOTO_IMAGES) DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_title_deferred_music_list, MENU_ENUM_LABEL_VALUE_GOTO_MUSIC) DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_title_deferred_video_list, MENU_ENUM_LABEL_VALUE_GOTO_VIDEO) +DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_title_deferred_contentless_cores_list, MENU_ENUM_LABEL_VALUE_GOTO_CONTENTLESS_CORES) + DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_core_updater_list, MENU_ENUM_LABEL_VALUE_CORE_UPDATER_LIST) DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_core_manager_list, MENU_ENUM_LABEL_VALUE_CORE_MANAGER_LIST) DEFAULT_TITLE_SEARCH_FILTER_MACRO(action_get_core_cheat_options_list, MENU_ENUM_LABEL_VALUE_CORE_CHEAT_OPTIONS) @@ -853,18 +855,19 @@ static int action_get_title_group_settings(const char *path, const char *label, * tab, but its actual title is set elsewhere - so treat * it as a generic top-level item */ title_info_list_t info_list[] = { - {MENU_ENUM_LABEL_MAIN_MENU, MENU_ENUM_LABEL_VALUE_MAIN_MENU, false }, - {MENU_ENUM_LABEL_HISTORY_TAB, MENU_ENUM_LABEL_VALUE_HISTORY_TAB, true }, - {MENU_ENUM_LABEL_FAVORITES_TAB, MENU_ENUM_LABEL_VALUE_FAVORITES_TAB, true }, - {MENU_ENUM_LABEL_IMAGES_TAB, MENU_ENUM_LABEL_VALUE_IMAGES_TAB, true }, - {MENU_ENUM_LABEL_MUSIC_TAB, MENU_ENUM_LABEL_VALUE_MUSIC_TAB, true }, - {MENU_ENUM_LABEL_VIDEO_TAB, MENU_ENUM_LABEL_VALUE_VIDEO_TAB, true }, - {MENU_ENUM_LABEL_SETTINGS_TAB, MENU_ENUM_LABEL_VALUE_SETTINGS_TAB, false }, - {MENU_ENUM_LABEL_PLAYLISTS_TAB, MENU_ENUM_LABEL_VALUE_PLAYLISTS_TAB, false }, - {MENU_ENUM_LABEL_ADD_TAB, MENU_ENUM_LABEL_VALUE_ADD_TAB, false }, - {MENU_ENUM_LABEL_EXPLORE_TAB, MENU_ENUM_LABEL_VALUE_EXPLORE_TAB, false }, - {MENU_ENUM_LABEL_NETPLAY_TAB, MENU_ENUM_LABEL_VALUE_NETPLAY_TAB, false }, - {MENU_ENUM_LABEL_HORIZONTAL_MENU, MENU_ENUM_LABEL_VALUE_HORIZONTAL_MENU, false }, + {MENU_ENUM_LABEL_MAIN_MENU, MENU_ENUM_LABEL_VALUE_MAIN_MENU, false }, + {MENU_ENUM_LABEL_HISTORY_TAB, MENU_ENUM_LABEL_VALUE_HISTORY_TAB, true }, + {MENU_ENUM_LABEL_FAVORITES_TAB, MENU_ENUM_LABEL_VALUE_FAVORITES_TAB, true }, + {MENU_ENUM_LABEL_IMAGES_TAB, MENU_ENUM_LABEL_VALUE_IMAGES_TAB, true }, + {MENU_ENUM_LABEL_MUSIC_TAB, MENU_ENUM_LABEL_VALUE_MUSIC_TAB, true }, + {MENU_ENUM_LABEL_VIDEO_TAB, MENU_ENUM_LABEL_VALUE_VIDEO_TAB, true }, + {MENU_ENUM_LABEL_SETTINGS_TAB, MENU_ENUM_LABEL_VALUE_SETTINGS_TAB, false }, + {MENU_ENUM_LABEL_PLAYLISTS_TAB, MENU_ENUM_LABEL_VALUE_PLAYLISTS_TAB, false }, + {MENU_ENUM_LABEL_ADD_TAB, MENU_ENUM_LABEL_VALUE_ADD_TAB, false }, + {MENU_ENUM_LABEL_EXPLORE_TAB, MENU_ENUM_LABEL_VALUE_EXPLORE_TAB, false }, + {MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB, MENU_ENUM_LABEL_VALUE_CONTENTLESS_CORES_TAB, false }, + {MENU_ENUM_LABEL_NETPLAY_TAB, MENU_ENUM_LABEL_VALUE_NETPLAY_TAB, false }, + {MENU_ENUM_LABEL_HORIZONTAL_MENU, MENU_ENUM_LABEL_VALUE_HORIZONTAL_MENU, false }, }; for (i = 0; i < ARRAY_SIZE(info_list); i++) @@ -989,6 +992,7 @@ static int menu_cbs_init_bind_title_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_DEFERRED_IMAGES_LIST, action_get_title_deferred_images_list}, {MENU_ENUM_LABEL_DEFERRED_MUSIC_LIST, action_get_title_deferred_music_list}, {MENU_ENUM_LABEL_DEFERRED_VIDEO_LIST, action_get_title_deferred_video_list}, + {MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST, action_get_title_deferred_contentless_cores_list}, {MENU_ENUM_LABEL_DEFERRED_DRIVER_SETTINGS_LIST, action_get_driver_settings_list}, {MENU_ENUM_LABEL_DEFERRED_AUDIO_SETTINGS_LIST, action_get_audio_settings_list}, {MENU_ENUM_LABEL_DEFERRED_AUDIO_RESAMPLER_SETTINGS_LIST, action_get_audio_resampler_settings_list}, diff --git a/menu/drivers/materialui.c b/menu/drivers/materialui.c index d17e4bcd2c..42c6fa79a3 100644 --- a/menu/drivers/materialui.c +++ b/menu/drivers/materialui.c @@ -1303,7 +1303,8 @@ enum materialui_node_icon_type MUI_ICON_TYPE_NONE = 0, MUI_ICON_TYPE_INTERNAL, MUI_ICON_TYPE_MENU_EXPLORE, - MUI_ICON_TYPE_PLAYLIST + MUI_ICON_TYPE_PLAYLIST, + MUI_ICON_TYPE_MENU_CONTENTLESS_CORE }; /* This structure holds auxiliary information for @@ -2797,6 +2798,7 @@ static void materialui_compute_entries_box_default( has_icon = mui->textures.list[node->icon_texture_index] != 0; break; case MUI_ICON_TYPE_MENU_EXPLORE: + case MUI_ICON_TYPE_MENU_CONTENTLESS_CORE: has_icon = true; break; case MUI_ICON_TYPE_PLAYLIST: @@ -3996,6 +3998,9 @@ static void materialui_render_menu_entry_default( case MUI_ICON_TYPE_MENU_EXPLORE: icon_texture = menu_explore_get_entry_icon(entry_type); break; + case MUI_ICON_TYPE_MENU_CONTENTLESS_CORE: + icon_texture = menu_contentless_cores_get_entry_icon(entry->label); + break; case MUI_ICON_TYPE_PLAYLIST: icon_texture = materialui_get_playlist_icon( mui, node->icon_texture_index); @@ -10105,6 +10110,7 @@ static void materialui_list_insert( case MENU_SETTING_ACTION_CORE_MANAGER_OPTIONS: case MENU_SETTING_ACTION_CORE_LOCK: case MENU_EXPLORE_TAB: + case MENU_CONTENTLESS_CORES_TAB: node->icon_texture_index = MUI_TEXTURE_CORES; node->icon_type = MUI_ICON_TYPE_INTERNAL; break; @@ -10189,6 +10195,9 @@ static void materialui_list_insert( node->icon_texture_index = MUI_TEXTURE_FILE; node->icon_type = MUI_ICON_TYPE_INTERNAL; break; + case MENU_SETTING_ACTION_CONTENTLESS_CORE_RUN: + node->icon_type = MUI_ICON_TYPE_MENU_CONTENTLESS_CORE; + break; case FILE_TYPE_RPL_ENTRY: case MENU_SETTING_DROPDOWN_ITEM: case MENU_SETTING_DROPDOWN_ITEM_RESOLUTION: diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c index 7869e8e2ce..69a10f4e6f 100644 --- a/menu/drivers/ozone.c +++ b/menu/drivers/ozone.c @@ -144,6 +144,7 @@ enum #if defined(HAVE_LIBRETRODB) OZONE_SYSTEM_TAB_EXPLORE, #endif + OZONE_SYSTEM_TAB_CONTENTLESS_CORES, /* End of this enum - use the last one to determine num of possible tabs */ OZONE_SYSTEM_TAB_LAST @@ -181,6 +182,8 @@ enum OZONE_TAB_TEXTURES OZONE_TAB_TEXTURE_IMAGE, OZONE_TAB_TEXTURE_NETWORK, OZONE_TAB_TEXTURE_SCAN_CONTENT, + OZONE_TAB_TEXTURE_EXPLORE, + OZONE_TAB_TEXTURE_CONTENTLESS_CORES, OZONE_TAB_TEXTURE_LAST }; @@ -570,6 +573,7 @@ struct ozone_handle bool is_db_manager_list; bool is_file_list; bool is_quick_menu; + bool is_contentless_cores; bool first_frame; struct @@ -604,15 +608,17 @@ static const char *OZONE_TEXTURES_FILES[OZONE_TEXTURE_LAST] = { }; static const char *OZONE_TAB_TEXTURES_FILES[OZONE_TAB_TEXTURE_LAST] = { - "retroarch", - "settings", - "history", - "favorites", - "music", - "video", - "image", - "netplay", - "add" + "retroarch", /* MAIN_MENU */ + "settings", /* SETTINGS_TAB */ + "history", /* HISTORY_TAB */ + "favorites", /* FAVORITES_TAB */ + "music", /* MUSIC_TAB */ + "video", /* VIDEO_TAB */ + "image", /* IMAGES_TAB */ + "netplay", /* NETPLAY_TAB */ + "add", /* ADD_TAB */ + "retroarch", /* EXPLORE_TAB */ + "retroarch" /* CONTENTLESS_CORES_TAB */ }; static const enum msg_hash_enums ozone_system_tabs_value[OZONE_SYSTEM_TAB_LAST] = { @@ -630,12 +636,11 @@ static const enum msg_hash_enums ozone_system_tabs_value[OZONE_SYSTEM_TAB_LAST] #ifdef HAVE_NETWORKING MENU_ENUM_LABEL_VALUE_NETPLAY_TAB, #endif -#ifdef HAVE_LIBRETRODB MENU_ENUM_LABEL_VALUE_ADD_TAB, - MENU_ENUM_LABEL_VALUE_EXPLORE_TAB -#else - MENU_ENUM_LABEL_VALUE_ADD_TAB +#ifdef HAVE_LIBRETRODB + MENU_ENUM_LABEL_VALUE_EXPLORE_TAB, #endif + MENU_ENUM_LABEL_VALUE_CONTENTLESS_CORES_TAB }; static const enum menu_settings_type ozone_system_tabs_type[OZONE_SYSTEM_TAB_LAST] = { @@ -653,12 +658,11 @@ static const enum menu_settings_type ozone_system_tabs_type[OZONE_SYSTEM_TAB_LAS #ifdef HAVE_NETWORKING MENU_NETPLAY_TAB, #endif -#ifdef HAVE_LIBRETRODB MENU_ADD_TAB, - MENU_EXPLORE_TAB -#else - MENU_ADD_TAB +#ifdef HAVE_LIBRETRODB + MENU_EXPLORE_TAB, #endif + MENU_CONTENTLESS_CORES_TAB }; static const enum msg_hash_enums ozone_system_tabs_idx[OZONE_SYSTEM_TAB_LAST] = { @@ -676,12 +680,11 @@ static const enum msg_hash_enums ozone_system_tabs_idx[OZONE_SYSTEM_TAB_LAST] = #ifdef HAVE_NETWORKING MENU_ENUM_LABEL_NETPLAY_TAB, #endif -#ifdef HAVE_LIBRETRODB MENU_ENUM_LABEL_ADD_TAB, - MENU_ENUM_LABEL_EXPLORE_TAB -#else - MENU_ENUM_LABEL_ADD_TAB +#ifdef HAVE_LIBRETRODB + MENU_ENUM_LABEL_EXPLORE_TAB, #endif + MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB }; static const unsigned ozone_system_tabs_icons[OZONE_SYSTEM_TAB_LAST] = { @@ -699,7 +702,11 @@ static const unsigned ozone_system_tabs_icons[OZONE_SYSTEM_TAB_LAST] = { #ifdef HAVE_NETWORKING OZONE_TAB_TEXTURE_NETWORK, #endif - OZONE_TAB_TEXTURE_SCAN_CONTENT + OZONE_TAB_TEXTURE_SCAN_CONTENT, +#ifdef HAVE_LIBRETRODB + OZONE_TAB_TEXTURE_EXPLORE, +#endif + OZONE_TAB_TEXTURE_CONTENTLESS_CORES }; static const char *OZONE_THEME_TEXTURES_FILES[OZONE_THEME_TEXTURE_LAST] = { @@ -1597,7 +1604,7 @@ static void ozone_set_background_running_opacity( static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, enum msg_hash_enums enum_idx, const char *enum_path, - unsigned type, bool active) + const char *enum_label, unsigned type, bool active) { switch (enum_idx) { @@ -1675,6 +1682,8 @@ static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_MUSIC]; case MENU_ENUM_LABEL_GOTO_EXPLORE: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RDB]; + case MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES: + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE]; /* Menu icons */ case MENU_ENUM_LABEL_CONTENT_SETTINGS: @@ -1735,6 +1744,8 @@ static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, case MENU_ENUM_LABEL_UPDATE_DATABASES: case MENU_ENUM_LABEL_DATABASE_MANAGER_LIST: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RDB]; + case MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB: + return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE]; case MENU_ENUM_LABEL_CURSOR_MANAGER_LIST: return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CURSOR]; case MENU_ENUM_LABEL_HELP_LIST: @@ -1930,6 +1941,12 @@ static uintptr_t ozone_entries_icon_get_texture(ozone_handle_t *ozone, break; } #endif + case MENU_ENUM_LABEL_CONTENTLESS_CORE: + { + uintptr_t icon = menu_contentless_cores_get_entry_icon(enum_label); + if (icon) return icon; + break; + } default: break; } @@ -3288,6 +3305,7 @@ static bool ozone_is_playlist(ozone_handle_t *ozone, bool depth) #ifdef HAVE_LIBRETRODB case OZONE_SYSTEM_TAB_EXPLORE: #endif + case OZONE_SYSTEM_TAB_CONTENTLESS_CORES: is_playlist = false; break; case OZONE_SYSTEM_TAB_HISTORY: @@ -4490,7 +4508,7 @@ static void ozone_compute_entries_position( if (ozone->is_playlist && entries_end == 1) { uintptr_t tex = ozone_entries_icon_get_texture(ozone, - entry.enum_idx, entry.path, entry.type, false); + entry.enum_idx, entry.path, entry.label, entry.type, false); ozone->empty_playlist = tex == ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_CORE_INFO]; } else @@ -4783,7 +4801,7 @@ border_iterate: MENU_ENTRY_INIT(entry); entry.path_enabled = false; - entry.label_enabled = false; + entry.label_enabled = ozone->is_contentless_cores; menu_entry_get(&entry, 0, (unsigned)i, selection_buf, true); if (entry.enum_idx == MENU_ENUM_LABEL_CHEEVOS_PASSWORD) @@ -4856,7 +4874,7 @@ border_iterate: /* Icon */ tex = ozone_entries_icon_get_texture(ozone, - entry.enum_idx, entry.path, entry.type, entry_selected); + entry.enum_idx, entry.path, entry.label, entry.type, entry_selected); if (tex != ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SUBSETTING]) { uintptr_t texture = tex; @@ -7150,6 +7168,12 @@ static void *ozone_init(void **userdata, bool video_is_threaded) ozone->tabs[++ozone->system_tab_end] = OZONE_SYSTEM_TAB_EXPLORE; #endif +#if defined(HAVE_DYNAMIC) + if (settings->uints.menu_content_show_contentless_cores != + MENU_CONTENTLESS_CORES_DISPLAY_NONE) + ozone->tabs[++ozone->system_tab_end] = OZONE_SYSTEM_TAB_CONTENTLESS_CORES; +#endif + menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); gfx_display_set_width(width); @@ -9844,14 +9868,16 @@ static void ozone_populate_entries(void *data, new_depth = (int)ozone_list_get_size(ozone, MENU_LIST_PLAIN); - animate = new_depth != ozone->depth; - ozone->fade_direction = new_depth <= ozone->depth; - ozone->depth = new_depth; - ozone->is_playlist = ozone_is_playlist(ozone, true); - ozone->is_db_manager_list = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DATABASE_MANAGER_LIST)); - ozone->is_file_list = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES)); - ozone->is_quick_menu = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS)) || - string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_SETTINGS)); + animate = new_depth != ozone->depth; + ozone->fade_direction = new_depth <= ozone->depth; + ozone->depth = new_depth; + ozone->is_playlist = ozone_is_playlist(ozone, true); + ozone->is_db_manager_list = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DATABASE_MANAGER_LIST)); + ozone->is_file_list = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_FAVORITES)); + ozone->is_quick_menu = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_RPL_ENTRY_ACTIONS)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENT_SETTINGS)); + ozone->is_contentless_cores = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST)); if (animate) if (ozone->categories_selection_ptr == ozone->categories_active_idx_old) diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c index b7f5badcb5..19bacc060a 100644 --- a/menu/drivers/xmb.c +++ b/menu/drivers/xmb.c @@ -248,6 +248,7 @@ enum #if defined(HAVE_LIBRETRODB) XMB_SYSTEM_TAB_EXPLORE, #endif + XMB_SYSTEM_TAB_CONTENTLESS_CORES, /* End of this enum - use the last one to determine num of possible tabs */ XMB_SYSTEM_TAB_MAX_LENGTH @@ -294,6 +295,7 @@ typedef struct xmb_handle #if defined(HAVE_LIBRETRODB) xmb_node_t explore_tab_node; #endif + xmb_node_t contentless_cores_tab_node; xmb_node_t netplay_tab_node; menu_input_pointer_t pointer; @@ -399,6 +401,7 @@ typedef struct xmb_handle /* Favorites, History, Images, Music, Videos, user generated */ bool is_playlist; bool is_db_manager_list; + bool is_contentless_cores; /* Load Content file browser */ bool is_file_list; @@ -1978,6 +1981,8 @@ static xmb_node_t* xmb_get_node(xmb_handle_t *xmb, unsigned i) case XMB_SYSTEM_TAB_EXPLORE: return &xmb->explore_tab_node; #endif + case XMB_SYSTEM_TAB_CONTENTLESS_CORES: + return &xmb->contentless_cores_tab_node; default: if (i > xmb->system_tab_end) return xmb_get_userdata_from_horizontal_list( @@ -2472,6 +2477,10 @@ static void xmb_populate_entries(void *data, /* Determine whether this is a database manager list */ xmb->is_db_manager_list = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_DATABASE_MANAGER_LIST)); + /* Determine whether this is the contentless cores menu */ + xmb->is_contentless_cores = string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)) || + string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST)); + /* Determine whether this is a 'file list' * (needed for handling thumbnails when viewing images * via 'load content') @@ -2539,7 +2548,8 @@ static void xmb_populate_entries(void *data, static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, xmb_node_t *core_node, xmb_node_t *node, enum msg_hash_enums enum_idx, const char *enum_path, - unsigned type, bool active, bool checked) + const char *enum_label, unsigned type, bool active, + bool checked) { switch (enum_idx) { @@ -2635,7 +2645,8 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, return xmb->textures.list[XMB_TEXTURE_MUSIC]; case MENU_ENUM_LABEL_GOTO_EXPLORE: return xmb->textures.list[XMB_TEXTURE_MAIN_MENU]; - + case MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES: + return xmb->textures.list[XMB_TEXTURE_MAIN_MENU]; case MENU_ENUM_LABEL_LOAD_DISC: case MENU_ENUM_LABEL_DUMP_DISC: #ifdef HAVE_LAKKA @@ -2865,6 +2876,13 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb, break; } #endif + case MENU_ENUM_LABEL_CONTENTLESS_CORE: + { + uintptr_t icon = menu_contentless_cores_get_entry_icon(enum_label); + if (icon) + return icon; + break; + } default: break; } @@ -3224,7 +3242,7 @@ static int xmb_draw_item( return 0; MENU_ENTRY_INIT(entry); - entry.label_enabled = false; + entry.label_enabled = xmb->is_contentless_cores; entry.sublabel_enabled = (i == current); menu_entry_get(&entry, 0, i, list, true); entry_type = entry.type; @@ -3582,7 +3600,8 @@ static int xmb_draw_item( math_matrix_4x4 mymat_tmp; gfx_display_ctx_rotate_draw_t rotate_draw; uintptr_t texture = xmb_icon_get_id(xmb, core_node, node, - entry.enum_idx, entry.path, entry_type, (i == current), entry.checked); + entry.enum_idx, entry.path, entry.label, + entry_type, (i == current), entry.checked); float x = icon_x; float y = icon_y; float scale_factor = node->zoom; @@ -5933,6 +5952,12 @@ static void *xmb_init(void **userdata, bool video_is_threaded) xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_EXPLORE; #endif +#if defined(HAVE_DYNAMIC) + if (settings->uints.menu_content_show_contentless_cores != + MENU_CONTENTLESS_CORES_DISPLAY_NONE) + xmb->tabs[++xmb->system_tab_end] = XMB_SYSTEM_TAB_CONTENTLESS_CORES; +#endif + menu_driver_ctl(RARCH_MENU_CTL_UNSET_PREVENT_POPULATE, NULL); /* TODO/FIXME - we don't use framebuffer at all @@ -6410,6 +6435,10 @@ static void xmb_context_reset_textures( xmb->explore_tab_node.zoom = xmb->categories_active_zoom; #endif + xmb->contentless_cores_tab_node.icon = xmb->textures.list[XMB_TEXTURE_MAIN_MENU]; + xmb->contentless_cores_tab_node.alpha = xmb->categories_active_alpha; + xmb->contentless_cores_tab_node.zoom = xmb->categories_active_zoom; + #ifdef HAVE_NETWORKING xmb->netplay_tab_node.icon = xmb->textures.list[XMB_TEXTURE_NETPLAY]; xmb->netplay_tab_node.alpha = xmb->categories_active_alpha; @@ -6871,6 +6900,12 @@ static void xmb_list_cache(void *data, enum menu_list_type type, unsigned action MENU_EXPLORE_TAB; break; #endif + case XMB_SYSTEM_TAB_CONTENTLESS_CORES: + menu_stack->list[stack_size - 1].label = + strdup(msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)); + menu_stack->list[stack_size - 1].type = + MENU_CONTENTLESS_CORES_TAB; + break; default: menu_stack->list[stack_size - 1].label = strdup(msg_hash_to_str(MENU_ENUM_LABEL_HORIZONTAL_MENU)); diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index b567a3f678..735cd06a6f 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -77,6 +77,7 @@ enum ACTION_OK_DL_IMAGES_LIST, ACTION_OK_DL_VIDEO_LIST, ACTION_OK_DL_EXPLORE_LIST, + ACTION_OK_DL_CONTENTLESS_CORES_LIST, ACTION_OK_DL_MUSIC_LIST, ACTION_OK_DL_SHADER_PARAMETERS, ACTION_OK_DL_SHADER_PRESET, diff --git a/menu/menu_contentless_cores.c b/menu/menu_contentless_cores.c new file mode 100644 index 0000000000..b170ac2933 --- /dev/null +++ b/menu/menu_contentless_cores.c @@ -0,0 +1,302 @@ +/* RetroArch - A frontend for libretro. + * Copyright (C) 2011-2020 - Daniel De Matteis + * Copyright (C) 2019-2022 - James Leaver + * + * 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 "menu_driver.h" +#include "menu_displaylist.h" +#include "../retroarch.h" +#include "../core_info.h" +#include "../configuration.h" + +#define CONTENTLESS_CORE_ICON_DEFAULT "default.png" + +typedef struct +{ + uintptr_t **system; + uintptr_t fallback; +} contentless_core_icons_t; + +typedef struct +{ + contentless_core_icons_t *icons; + bool icons_enabled; +} contentless_cores_state_t; + +static contentless_cores_state_t *contentless_cores_state = NULL; + +static void contentless_cores_unload_icons(contentless_cores_state_t *state) +{ + size_t i, cap; + + if (!state || !state->icons) + return; + + if (state->icons->fallback) + video_driver_texture_unload(&state->icons->fallback); + + for (i = 0, cap = RHMAP_CAP(state->icons->system); i != cap; i++) + { + if (RHMAP_KEY(state->icons->system, i)) + { + uintptr_t *icon = state->icons->system[i]; + + if (!icon) + continue; + + video_driver_texture_unload(icon); + free(icon); + } + } + + RHMAP_FREE(state->icons->system); + free(state->icons); + state->icons = NULL; +} + +static void contentless_cores_load_icons(contentless_cores_state_t *state) +{ + bool rgba_supported = video_driver_supports_rgba(); + core_info_list_t *core_info_list = NULL; + char icon_directory[PATH_MAX_LENGTH]; + char icon_path[PATH_MAX_LENGTH]; + size_t i; + + icon_directory[0] = '\0'; + icon_path[0] = '\0'; + + if (!state) + return; + + /* Unload any existing icons */ + contentless_cores_unload_icons(state); + + if (!state->icons_enabled) + return; + + /* Create new icon container */ + state->icons = (contentless_core_icons_t*)calloc( + 1, sizeof(*state->icons)); + + /* Get icon directory */ + fill_pathname_application_special(icon_directory, + sizeof(icon_directory), + APPLICATION_SPECIAL_DIRECTORY_ASSETS_SYSICONS); + + if (string_is_empty(icon_directory)) + return; + + /* Load fallback icon */ + fill_pathname_join(icon_path, icon_directory, + CONTENTLESS_CORE_ICON_DEFAULT, sizeof(icon_path)); + + if (path_is_valid(icon_path)) + { + struct texture_image ti = {0}; + ti.supports_rgba = rgba_supported; + + if (image_texture_load(&ti, icon_path)) + { + if (ti.pixels) + video_driver_texture_load(&ti, + TEXTURE_FILTER_MIPMAP_LINEAR, + &state->icons->fallback); + + image_texture_free(&ti); + } + } + + /* Get icons for all contentless cores */ + core_info_get_list(&core_info_list); + + if (!core_info_list) + return; + + for (i = 0; i < core_info_list->count; i++) + { + core_info_t *core_info = core_info_get(core_info_list, i); + + /* Icon name is the first entry in the core + * info database list */ + if (core_info && + core_info->supports_no_game && + core_info->databases_list && + (core_info->databases_list->size > 0)) + { + const char *icon_name = + core_info->databases_list->elems[0].data; + struct texture_image ti = {0}; + ti.supports_rgba = rgba_supported; + + fill_pathname_join(icon_path, icon_directory, + icon_name, sizeof(icon_path)); + strlcat(icon_path, ".png", sizeof(icon_path)); + + if (!path_is_valid(icon_path)) + continue; + + if (image_texture_load(&ti, icon_path)) + { + if (ti.pixels) + { + uintptr_t *icon = (uintptr_t*)calloc(1, sizeof(*icon)); + + video_driver_texture_load(&ti, + TEXTURE_FILTER_MIPMAP_LINEAR, + icon); + + /* Add icon to hash map */ + RHMAP_SET_STR(state->icons->system, core_info->core_file_id.str, icon); + } + + image_texture_free(&ti); + } + } + } +} + +uintptr_t menu_contentless_cores_get_entry_icon(const char *core_id) +{ + contentless_cores_state_t *state = contentless_cores_state; + uintptr_t *icon = NULL; + + if (!state || + !state->icons_enabled || + !state->icons || + string_is_empty(core_id)) + return 0; + + icon = RHMAP_GET_STR(state->icons->system, core_id); + + if (icon) + return *icon; + + return state->icons->fallback; +} + +void menu_contentless_cores_context_init(void) +{ + if (!contentless_cores_state) + return; + + contentless_cores_load_icons(contentless_cores_state); +} + +void menu_contentless_cores_context_deinit(void) +{ + if (!contentless_cores_state) + return; + + contentless_cores_unload_icons(contentless_cores_state); +} + +void menu_contentless_cores_free(void) +{ + if (!contentless_cores_state) + return; + + contentless_cores_unload_icons(contentless_cores_state); + free(contentless_cores_state); + contentless_cores_state = NULL; +} + +unsigned menu_displaylist_contentless_cores(file_list_t *list, settings_t *settings) +{ + unsigned count = 0; + enum menu_contentless_cores_display_type + core_display_type = (enum menu_contentless_cores_display_type) + settings->uints.menu_content_show_contentless_cores; + core_info_list_t *core_info_list = NULL; + + /* Get core list */ + core_info_get_list(&core_info_list); + + if (core_info_list) + { + size_t menu_index = 0; + size_t i; + + /* Sort cores alphabetically */ + core_info_qsort(core_info_list, CORE_INFO_LIST_SORT_DISPLAY_NAME); + + /* Loop through cores */ + for (i = 0; i < core_info_list->count; i++) + { + core_info_t *core_info = core_info_get(core_info_list, i); + bool core_valid = false; + + if (core_info) + { + switch (core_display_type) + { + case MENU_CONTENTLESS_CORES_DISPLAY_ALL: + core_valid = core_info->supports_no_game; + break; + case MENU_CONTENTLESS_CORES_DISPLAY_SINGLE_PURPOSE: + core_valid = core_info->supports_no_game && + core_info->single_purpose; + break; + default: + break; + } + + if (core_valid && + menu_entries_append_enum(list, + core_info->path, + core_info->core_file_id.str, + MENU_ENUM_LABEL_CONTENTLESS_CORE, + MENU_SETTING_ACTION_CONTENTLESS_CORE_RUN, + 0, 0)) + { + file_list_set_alt_at_offset( + list, menu_index, core_info->display_name); + + menu_index++; + count++; + } + } + } + } + + /* Initialise icons, if required */ + if (!contentless_cores_state && (count > 0)) + { + contentless_cores_state = (contentless_cores_state_t*)calloc(1, + sizeof(*contentless_cores_state)); + + /* Disable icons when using menu drivers without + * icon support */ + contentless_cores_state->icons_enabled = + !string_is_equal(menu_driver_ident(), "rgui"); + + contentless_cores_load_icons(contentless_cores_state); + } + + if ((count == 0) && + menu_entries_append_enum(list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_CORES_AVAILABLE), + msg_hash_to_str(MENU_ENUM_LABEL_NO_CORES_AVAILABLE), + MENU_ENUM_LABEL_NO_CORES_AVAILABLE, + 0, 0, 0)) + count++; + + return count; +} diff --git a/menu/menu_defines.h b/menu/menu_defines.h index 80ce9e7a21..ab44808b71 100644 --- a/menu/menu_defines.h +++ b/menu/menu_defines.h @@ -130,6 +130,16 @@ enum menu_add_content_entry_display_type MENU_ADD_CONTENT_ENTRY_DISPLAY_LAST }; +/* Specifies which type of core will be displayed + * in the 'contentless cores' menu */ +enum menu_contentless_cores_display_type +{ + MENU_CONTENTLESS_CORES_DISPLAY_NONE = 0, + MENU_CONTENTLESS_CORES_DISPLAY_ALL, + MENU_CONTENTLESS_CORES_DISPLAY_SINGLE_PURPOSE, + MENU_CONTENTLESS_CORES_DISPLAY_LAST +}; + enum rgui_color_theme { RGUI_THEME_CUSTOM = 0, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 63cc6b2121..30c5ed56ce 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -3586,6 +3586,17 @@ static unsigned menu_displaylist_parse_playlists( MENU_EXPLORE_TAB, 0, 0)) count++; #endif + +#if defined(HAVE_DYNAMIC) + if (settings->uints.menu_content_show_contentless_cores != + MENU_CONTENTLESS_CORES_DISPLAY_NONE) + if (menu_entries_append_enum(info->list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_CONTENTLESS_CORES), + msg_hash_to_str(MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES), + MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES, + MENU_CONTENTLESS_CORES_TAB, 0, 0)) + count++; +#endif if (settings->bools.menu_content_show_favorites) if (menu_entries_append_enum(info->list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_FAVORITES), @@ -6321,6 +6332,17 @@ unsigned menu_displaylist_build_list( MENU_EXPLORE_TAB, 0, 0)) count++; #endif + +#if defined(HAVE_DYNAMIC) + if (settings->uints.menu_content_show_contentless_cores != + MENU_CONTENTLESS_CORES_DISPLAY_NONE) + if (menu_entries_append_enum(list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_CONTENTLESS_CORES), + msg_hash_to_str(MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES), + MENU_ENUM_LABEL_GOTO_CONTENTLESS_CORES, + MENU_CONTENTLESS_CORES_TAB, 0, 0)) + count++; +#endif if (menu_content_show_favorites) if (menu_entries_append_enum(list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_GOTO_FAVORITES), @@ -8268,6 +8290,9 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_CONTENT_SHOW_SETTINGS, PARSE_ONLY_BOOL, true }, {MENU_ENUM_LABEL_CONTENT_SHOW_SETTINGS_PASSWORD, PARSE_ONLY_STRING, true}, {MENU_ENUM_LABEL_CONTENT_SHOW_EXPLORE, PARSE_ONLY_BOOL, true }, +#if defined(HAVE_DYNAMIC) + {MENU_ENUM_LABEL_CONTENT_SHOW_CONTENTLESS_CORES, PARSE_ONLY_UINT, true }, +#endif {MENU_ENUM_LABEL_CONTENT_SHOW_FAVORITES, PARSE_ONLY_BOOL, true }, {MENU_ENUM_LABEL_CONTENT_SHOW_IMAGES, PARSE_ONLY_BOOL, true }, {MENU_ENUM_LABEL_CONTENT_SHOW_MUSIC, PARSE_ONLY_BOOL, true }, @@ -8288,6 +8313,9 @@ unsigned menu_displaylist_build_list( for (i = 0; i < ARRAY_SIZE(build_list); i++) { + if (!build_list[i].checked && !include_everything) + continue; + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, build_list[i].enum_idx, build_list[i].parse_type, false) == 0) @@ -11687,6 +11715,30 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, info->need_push = true; } break; + case DISPLAYLIST_CONTENTLESS_CORES: + { + size_t contentless_core_ptr = + menu_state_get_ptr()->contentless_core_ptr; + + menu_entries_ctl(MENU_ENTRIES_CTL_CLEAR, info->list); + count = menu_displaylist_contentless_cores(info->list, settings); + + /* TODO/FIXME: Selecting an entry in the + * contentless cores list will cause the + * quick menu to be pushed on the subsequent + * frame via the RARCH_MENU_CTL_SET_PENDING_QUICK_MENU + * command. The way this is implemented 'breaks' the + * menu stack record, so when leaving the quick + * menu via a 'cancel' operation, the last selected + * menu index is lost. We therefore have to apply + * a cached index value after rebuilding the list... */ + if (contentless_core_ptr < count) + menu_navigation_set_selection(contentless_core_ptr); + + info->need_sort = false; + info->need_push = true; + } + break; case DISPLAYLIST_CORE_OPTIONS: { /* Number of displayed options is dynamic. If user opens diff --git a/menu/menu_displaylist.h b/menu/menu_displaylist.h index f7013163a3..e9f2e49961 100644 --- a/menu/menu_displaylist.h +++ b/menu/menu_displaylist.h @@ -83,6 +83,7 @@ enum menu_displaylist_ctl_state DISPLAYLIST_HORIZONTAL_CONTENT_ACTIONS, DISPLAYLIST_HISTORY, DISPLAYLIST_EXPLORE, + DISPLAYLIST_CONTENTLESS_CORES, DISPLAYLIST_FAVORITES, DISPLAYLIST_PLAYLIST, DISPLAYLIST_VIDEO_HISTORY, @@ -344,6 +345,7 @@ bool menu_displaylist_has_subsystems(void); #if defined(HAVE_LIBRETRODB) unsigned menu_displaylist_explore(file_list_t *list, settings_t *settings); #endif +unsigned menu_displaylist_contentless_cores(file_list_t *list, settings_t *settings); enum filebrowser_enums filebrowser_get_type(void); diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 3387cadaa4..9b3b573606 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -2334,6 +2334,11 @@ static bool menu_driver_displaylist_push_internal( return true; } #endif + else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB))) + { + if (menu_displaylist_ctl(DISPLAYLIST_CONTENTLESS_CORES, info, settings)) + return true; + } else if (string_is_equal(label, msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_TAB))) { if (menu_displaylist_ctl(DISPLAYLIST_NETPLAY_ROOM_LIST, info, settings)) @@ -4155,6 +4160,7 @@ int menu_driver_deferred_push_content_list(file_list_t *list) file_list_t *selection_buf = MENU_LIST_GET_SELECTION(menu_list, (unsigned)0); menu_st->selection_ptr = 0; + menu_st->contentless_core_ptr = 0; if (!menu_driver_displaylist_push( menu_st, @@ -4518,6 +4524,7 @@ bool menu_entries_append_enum( if ( enum_idx != MENU_ENUM_LABEL_PLAYLIST_ENTRY && enum_idx != MENU_ENUM_LABEL_PLAYLIST_COLLECTION_ENTRY && enum_idx != MENU_ENUM_LABEL_EXPLORE_ITEM + && enum_idx != MENU_ENUM_LABEL_CONTENTLESS_CORE && enum_idx != MENU_ENUM_LABEL_RDB_ENTRY) cbs->setting = menu_setting_find_enum(enum_idx); @@ -6974,8 +6981,12 @@ 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_st->pending_quick_menu = true; + { + bool flush_stack = !data ? true : *((bool *)data); + if (flush_stack) + menu_entries_flush_stack(NULL, MENU_SETTINGS); + menu_st->pending_quick_menu = true; + } break; case RARCH_MENU_CTL_SET_PREVENT_POPULATE: menu_st->prevent_populate = true; @@ -7000,21 +7011,25 @@ bool menu_driver_ctl(enum rarch_menu_ctl_state state, void *data) #ifdef HAVE_NETWORKING core_updater_list_free_cached(); #endif -#if defined(HAVE_MENU) && defined(HAVE_LIBRETRODB) +#if defined(HAVE_MENU) +#if defined(HAVE_LIBRETRODB) /* Before freeing the explore menu, we * must wait for any explore menu initialisation * tasks to complete */ menu_explore_wait_for_init_task(); menu_explore_free(); #endif + menu_contentless_cores_free(); +#endif if (menu_st->driver_data) { unsigned i; - menu_st->scroll.acceleration = 0; - menu_st->selection_ptr = 0; - menu_st->scroll.index_size = 0; + menu_st->scroll.acceleration = 0; + menu_st->selection_ptr = 0; + menu_st->contentless_core_ptr = 0; + menu_st->scroll.index_size = 0; for (i = 0; i < SCROLL_INDEX_SIZE; i++) menu_st->scroll.index_list[i] = 0; @@ -7517,6 +7532,7 @@ static int generic_menu_iterate( break; #endif case MENU_ENUM_LABEL_CORE_MANAGER_ENTRY: + case MENU_ENUM_LABEL_CONTENTLESS_CORE: { core_info_t *core_info = NULL; const char *path = selection_buf->list[selection].path; diff --git a/menu/menu_driver.h b/menu/menu_driver.h index 9375041e6d..bdcef9e4ce 100644 --- a/menu/menu_driver.h +++ b/menu/menu_driver.h @@ -90,6 +90,7 @@ enum menu_settings_type MENU_IMAGES_TAB, MENU_NETPLAY_TAB, MENU_EXPLORE_TAB, + MENU_CONTENTLESS_CORES_TAB, MENU_ADD_TAB, MENU_PLAYLISTS_TAB, MENU_SETTING_DROPDOWN_ITEM, @@ -268,6 +269,8 @@ enum menu_settings_type MENU_SETTING_ACTION_CORE_OPTIONS_RESET, MENU_SETTING_ACTION_CORE_OPTIONS_FLUSH, + MENU_SETTING_ACTION_CONTENTLESS_CORE_RUN, + MENU_SETTINGS_LAST }; @@ -454,6 +457,7 @@ struct menu_state size_t begin; } entries; size_t selection_ptr; + size_t contentless_core_ptr; /* Quick jumping indices with L/R. * Rebuilt when parsing directory. */ @@ -635,6 +639,11 @@ void menu_explore_free(void); void menu_explore_set_state(explore_state_t *state); #endif +uintptr_t menu_contentless_cores_get_entry_icon(const char *core_id); +void menu_contentless_cores_context_init(void); +void menu_contentless_cores_context_deinit(void); +void menu_contentless_cores_free(void); + /* Returns true if search filter is enabled * for the specified menu list */ bool menu_driver_search_filter_enabled(const char *label, unsigned type); diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 13a97f6002..5e03a6407c 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -3674,6 +3674,36 @@ static void setting_get_string_representation_uint_menu_add_content_entry_displa } } +static void setting_get_string_representation_uint_menu_contentless_cores_display_type( + rarch_setting_t *setting, + char *s, size_t len) +{ + if (!setting) + return; + + switch (*setting->value.target.unsigned_integer) + { + case MENU_CONTENTLESS_CORES_DISPLAY_NONE: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_OFF), + len); + break; + case MENU_CONTENTLESS_CORES_DISPLAY_ALL: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_ALL), + len); + break; + case MENU_CONTENTLESS_CORES_DISPLAY_SINGLE_PURPOSE: + strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_SINGLE_PURPOSE), + len); + break; + } +} + static void setting_get_string_representation_uint_rgui_menu_color_theme( rarch_setting_t *setting, char *s, size_t len) @@ -16711,6 +16741,23 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE); #endif + CONFIG_UINT( + list, list_info, + &settings->uints.menu_content_show_contentless_cores, + MENU_ENUM_LABEL_CONTENT_SHOW_CONTENTLESS_CORES, + MENU_ENUM_LABEL_VALUE_CONTENT_SHOW_CONTENTLESS_CORES, + DEFAULT_MENU_CONTENT_SHOW_CONTENTLESS_CORES, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; + (*list)[list_info->index - 1].get_string_representation = + &setting_get_string_representation_uint_menu_contentless_cores_display_type; + menu_settings_list_current_add_range(list, list_info, 0, MENU_CONTENTLESS_CORES_DISPLAY_LAST-1, 1, true, true); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX; + SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_LAKKA_ADVANCED); #ifdef HAVE_MATERIALUI if (string_is_equal(settings->arrays.menu_driver, "glui")) diff --git a/msg_hash.h b/msg_hash.h index 9de5cb63b5..a70e8f5c35 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1224,6 +1224,9 @@ enum msg_hash_enums MENU_LABEL(CONTENT_SHOW_ADD_ENTRY), MENU_LABEL(CONTENT_SHOW_PLAYLISTS), MENU_LABEL(CONTENT_SHOW_EXPLORE), + MENU_LABEL(CONTENT_SHOW_CONTENTLESS_CORES), + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_ALL, + MENU_ENUM_LABEL_VALUE_SHOW_CONTENTLESS_CORES_SINGLE_PURPOSE, MENU_LABEL(XMB_RIBBON_ENABLE), MENU_LABEL(THUMBNAILS), MENU_LABEL(THUMBNAILS_RGUI), @@ -1368,6 +1371,7 @@ enum msg_hash_enums MENU_LABEL(GOTO_IMAGES), MENU_LABEL(GOTO_VIDEO), MENU_LABEL(GOTO_EXPLORE), + MENU_LABEL(GOTO_CONTENTLESS_CORES), MENU_LABEL(ADD_TO_FAVORITES), MENU_LABEL(ADD_TO_FAVORITES_PLAYLIST), MENU_LABEL(SET_CORE_ASSOCIATION), @@ -1524,6 +1528,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_DEFERRED_MUSIC_LIST, MENU_ENUM_LABEL_DEFERRED_VIDEO_LIST, MENU_ENUM_LABEL_DEFERRED_EXPLORE_LIST, + MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST, MENU_ENUM_LABEL_DEFERRED_NETPLAY, MENU_ENUM_LABEL_DEFERRED_MUSIC, MENU_ENUM_LABEL_DEFERRED_BROWSE_URL_START, @@ -2159,6 +2164,8 @@ enum msg_hash_enums MENU_ENUM_LABEL_EXPLORE_TAB, MENU_ENUM_LABEL_EXPLORE_ITEM, MENU_ENUM_LABEL_VALUE_EXPLORE_TAB, + MENU_LABEL(CONTENTLESS_CORES_TAB), + MENU_LABEL(CONTENTLESS_CORE), MENU_LABEL(ADD_TAB), MENU_LABEL(NETPLAY_TAB), MENU_LABEL(PLAYLISTS_TAB), diff --git a/runloop.c b/runloop.c index 8c7ae6afc3..e0d03928f1 100644 --- a/runloop.c +++ b/runloop.c @@ -6678,13 +6678,20 @@ static enum runloop_state_enum runloop_check_state( /* Iterate the menu driver for one frame. */ + /* 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. */ if (menu_st->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_entries_flush_stack(NULL, MENU_SETTINGS); + menu_ctx_list_t list_info; + + /* We are going to push a new menu; ensure + * that the current one is cached for animation + * purposes */ + list_info.type = MENU_LIST_PLAIN; + list_info.action = 0; + menu_driver_list_cache(&list_info); + p_disp->msg_force = true; generic_action_ok_displaylist_push("", NULL, diff --git a/tasks/task_content.c b/tasks/task_content.c index f804fc996c..98319646da 100644 --- a/tasks/task_content.c +++ b/tasks/task_content.c @@ -2096,8 +2096,14 @@ bool task_push_start_current_core(content_ctx_info_t *content_info) if (firmware_update_status(&content_ctx)) goto end; - /* Loads content into currently selected core. */ - if (!(ret = content_load(content_info, p_content))) + /* Loads content into currently selected core. + * Note that 'content_load()' can fail and yet still + * return 'true'... In this case, the dummy core + * will be loaded; the 'start core' operation can + * therefore only be considered successful if the + * dummy core is not running following 'content_load()' */ + if (!(ret = content_load(content_info, p_content)) || + !(ret = (runloop_st->current_core_type != CORE_TYPE_DUMMY))) { if (error_string) { @@ -2159,6 +2165,101 @@ bool task_push_load_new_core( } #ifdef HAVE_MENU +bool task_push_load_contentless_core_from_menu( + const char *core_path) +{ + content_ctx_info_t content_info = {0}; + content_information_ctx_t content_ctx = {0}; + content_state_t *p_content = content_state_get_ptr(); + bool ret = true; + char *error_string = NULL; + runloop_state_t *runloop_st = runloop_state_get_ptr(); + settings_t *settings = config_get_ptr(); + const char *path_dir_system = settings->paths.directory_system; + bool check_firmware_before_loading = settings->bools.check_firmware_before_loading; + bool flush_menu = true; + const char *menu_label = NULL; + + if (string_is_empty(core_path)) + return false; + + content_info.environ_get = menu_content_environment_get; + + content_ctx.check_firmware_before_loading = check_firmware_before_loading; + content_ctx.bios_is_missing = retroarch_ctl(RARCH_CTL_IS_MISSING_BIOS, NULL); + if (!string_is_empty(path_dir_system)) + content_ctx.directory_system = strdup(path_dir_system); + + /* Set core path */ + path_set(RARCH_PATH_CORE, core_path); + + /* Clear content path */ + path_clear(RARCH_PATH_CONTENT); + +#if defined(HAVE_DYNAMIC) + /* Load core */ + command_event(CMD_EVENT_LOAD_CORE, NULL); + + runloop_set_current_core_type(CORE_TYPE_PLAIN, true); + + if (firmware_update_status(&content_ctx)) + goto end; + + /* Loads content into currently selected core. + * Note that 'content_load()' can fail and yet still + * return 'true'... In this case, the dummy core + * will be loaded; the 'start core' operation can + * therefore only be considered successful if the + * dummy core is not running following 'content_load()' */ + if (!(ret = content_load(&content_info, p_content)) || + !(ret = (runloop_st->current_core_type != CORE_TYPE_DUMMY))) + { + if (error_string) + { + runloop_msg_queue_push(error_string, 2, 90, + true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, + MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_ERR("[Content]: %s\n", error_string); + free(error_string); + } + + retroarch_menu_running(); + goto end; + } +#else + /* TODO/FIXME: Static builds do not support running + * a core directly from the 'command line' without + * supplying a content path, so this *will not work*. + * In order to support this functionality, the '-L' + * command line argument must be enabled for static + * builds to inform the frontend that the core should + * run automatically on launch. We will leave this + * non-functional code here as a place-marker for + * future devs who may wish to implement this... */ + command_event_cmd_exec(p_content, + path_get(RARCH_PATH_CONTENT), &content_ctx, + false, &error_string); + command_event(CMD_EVENT_QUIT, NULL); +#endif + + /* Push quick menu onto menu stack */ + menu_entries_get_last_stack(NULL, &menu_label, NULL, NULL, NULL); + + if (string_is_equal(menu_label, msg_hash_to_str(MENU_ENUM_LABEL_CONTENTLESS_CORES_TAB)) || + string_is_equal(menu_label, msg_hash_to_str(MENU_ENUM_LABEL_DEFERRED_CONTENTLESS_CORES_LIST))) + flush_menu = false; + + menu_driver_ctl(RARCH_MENU_CTL_SET_PENDING_QUICK_MENU, &flush_menu); + +#ifdef HAVE_DYNAMIC +end: +#endif + if (content_ctx.directory_system) + free(content_ctx.directory_system); + + return ret; +} + bool task_push_load_content_with_new_core_from_menu( const char *core_path, const char *fullpath, diff --git a/tasks/task_content.h b/tasks/task_content.h index 34289e24dc..98500ca19b 100644 --- a/tasks/task_content.h +++ b/tasks/task_content.h @@ -99,6 +99,9 @@ bool task_push_load_content_from_playlist_from_menu( content_ctx_info_t *content_info, retro_task_callback_t cb, void *user_data); + +bool task_push_load_contentless_core_from_menu( + const char *core_path); #endif bool task_push_load_content_with_core( diff --git a/tasks/task_core_backup.c b/tasks/task_core_backup.c index 4f601aa48d..5ae41b2300 100644 --- a/tasks/task_core_backup.c +++ b/tasks/task_core_backup.c @@ -36,6 +36,10 @@ #include "../core_info.h" #include "../core_backup.h" +#if defined(RARCH_INTERNAL) && defined(HAVE_MENU) +#include "../menu/menu_driver.h" +#endif + #if defined(ANDROID) #include "../play_feature_delivery/play_feature_delivery.h" #endif @@ -649,6 +653,11 @@ static void cb_task_core_restore( /* Reload core info files * > This must be done on the main thread */ command_event(CMD_EVENT_CORE_INFO_INIT, NULL); + +#if defined(RARCH_INTERNAL) && defined(HAVE_MENU) + /* Force reload of contentless cores icons */ + menu_contentless_cores_free(); +#endif } static void task_core_restore_handler(retro_task_t *task) diff --git a/tasks/task_core_updater.c b/tasks/task_core_updater.c index e1f9b30000..2481e22315 100644 --- a/tasks/task_core_updater.c +++ b/tasks/task_core_updater.c @@ -45,6 +45,7 @@ #if defined(RARCH_INTERNAL) && defined(HAVE_MENU) #include "../menu/menu_entries.h" +#include "../menu/menu_driver.h" #endif /* Get core updater list */ @@ -521,6 +522,11 @@ static void cb_task_core_updater_download( /* Reload core info files * > This must be done on the main thread */ command_event(CMD_EVENT_CORE_INFO_INIT, NULL); + +#if defined(RARCH_INTERNAL) && defined(HAVE_MENU) + /* Force reload of contentless cores icons */ + menu_contentless_cores_free(); +#endif } static void cb_decompress_task_core_updater_download(