diff --git a/config.def.h b/config.def.h index 1cc18f882d8a..1d48ab0dc9ec 100644 --- a/config.def.h +++ b/config.def.h @@ -634,6 +634,14 @@ * controller is connected in port 1 */ #define DEFAULT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED false +/* Conditional overlay profiles (FR #18178): default behaviour mode and the + * debounce window (ms) before a profile switch is committed. The legacy + * DEFAULT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED bool above is migrated into the + * behaviour enum on config load (see configuration.c). */ +#define DEFAULT_OVERLAY_BEHAVIOR OVERLAY_BEHAVIOR_STATIC + +#define DEFAULT_OVERLAY_SWITCH_DELAY_MS 800 + #define DEFAULT_OVERLAY_SHOW_MOUSE_CURSOR false #ifdef HAKCHI diff --git a/configuration.c b/configuration.c index 8b605b4e3a6a..834d3a87bf31 100644 --- a/configuration.c +++ b/configuration.c @@ -1741,6 +1741,7 @@ static struct config_path_setting *populate_settings_path( #ifdef HAVE_OVERLAY SETTING_PATH("input_overlay", settings->paths.path_overlay, false, NULL, true); + SETTING_PATH("input_overlay_minimal", settings->paths.path_overlay_minimal, false, NULL, true); SETTING_PATH("input_osk_overlay", settings->paths.path_osk_overlay, false, NULL, true); SETTING_PATH("overlay_directory", settings->paths.directory_overlay, true, NULL, true); SETTING_PATH("osk_overlay_directory", settings->paths.directory_osk_overlay, true, NULL, true); @@ -2653,6 +2654,8 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("input_overlay_dpad_diagonal_sensitivity", &settings->uints.input_overlay_dpad_diagonal_sensitivity, true, DEFAULT_OVERLAY_DPAD_DIAGONAL_SENSITIVITY, false); SETTING_UINT("input_overlay_abxy_diagonal_sensitivity", &settings->uints.input_overlay_abxy_diagonal_sensitivity, true, DEFAULT_OVERLAY_ABXY_DIAGONAL_SENSITIVITY, false); SETTING_UINT("input_overlay_analog_recenter_zone", &settings->uints.input_overlay_analog_recenter_zone, true, DEFAULT_INPUT_OVERLAY_ANALOG_RECENTER_ZONE, false); + SETTING_UINT("input_overlay_behavior", &settings->uints.input_overlay_behavior, true, DEFAULT_OVERLAY_BEHAVIOR, false); + SETTING_UINT("input_overlay_switch_delay_ms", &settings->uints.input_overlay_switch_delay_ms, true, DEFAULT_OVERLAY_SWITCH_DELAY_MS, false); #endif #ifdef HAVE_LIBNX @@ -3356,6 +3359,11 @@ void config_set_defaults(void *data) settings->paths.directory_overlay, FILE_PATH_DEFAULT_OVERLAY, sizeof(settings->paths.path_overlay)); + /* No default for path_overlay_minimal: leave it empty so the + * resolver falls back to the main preset until the user explicitly + * picks a minimal preset via Settings -> On-Screen Display -> On-Screen + * Overlay -> Minimal Overlay Preset. Avoids pointing at an asset that + * may not ship in the user's overlay bundle. */ if (!*settings->paths.path_osk_overlay) fill_pathname_join_special(settings->paths.path_osk_overlay, settings->paths.directory_overlay, @@ -4018,7 +4026,10 @@ static bool config_load_file(global_t *global, const char *extra_path = NULL; #ifdef HAVE_OVERLAY char old_overlay_path[PATH_MAX_LENGTH], new_overlay_path[PATH_MAX_LENGTH]; + char old_overlay_minimal_path[PATH_MAX_LENGTH], new_overlay_minimal_path[PATH_MAX_LENGTH]; config_get_path(conf, "input_overlay", old_overlay_path, sizeof(old_overlay_path)); + config_get_path(conf, "input_overlay_minimal", + old_overlay_minimal_path, sizeof(old_overlay_minimal_path)); #endif strlcpy(tmp_append_path, path_get(RARCH_PATH_CONFIG_OVERRIDE), sizeof(tmp_append_path)); @@ -4048,6 +4059,11 @@ static bool config_load_file(global_t *global, config_get_path(conf, "input_overlay", new_overlay_path, sizeof(new_overlay_path)); if (!string_is_equal(old_overlay_path, new_overlay_path)) retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_OVERLAY_PRESET, NULL); + config_get_path(conf, "input_overlay_minimal", + new_overlay_minimal_path, sizeof(new_overlay_minimal_path)); + if (!string_is_equal(old_overlay_minimal_path, new_overlay_minimal_path)) + retroarch_override_setting_set( + RARCH_OVERRIDE_SETTING_OVERLAY_MINIMAL_PRESET, NULL); #endif } @@ -4059,6 +4075,26 @@ static bool config_load_file(global_t *global, retroarch_ctl(RARCH_CTL_SET_PERFCNT_ENABLE, NULL); } +#ifdef HAVE_OVERLAY + /* Migrate the legacy "input_overlay_hide_when_gamepad_connected" bool to + * the new "input_overlay_behavior" enum (FR #18178). Only applies when + * the new key is absent from the config (older installs): a true legacy + * bool becomes HIDE_WHEN_GAMEPAD; otherwise the loaded enum value is kept. + * The legacy key is left in the config for downgrade safety. */ + { + unsigned beh_tmp = 0; + if (!config_get_uint(conf, "input_overlay_behavior", &beh_tmp)) + { + bool hide_tmp = false; + config_get_bool(conf, "input_overlay_hide_when_gamepad_connected", + &hide_tmp); + if (hide_tmp) + settings->uints.input_overlay_behavior = + OVERLAY_BEHAVIOR_HIDE_WHEN_GAMEPAD; + } + } +#endif + /* Overrides */ if (rarch_flags & RARCH_FLAGS_HAS_SET_USERNAME) diff --git a/configuration.h b/configuration.h index dc478b9ab2d6..a9ea1c10eb0f 100644 --- a/configuration.h +++ b/configuration.h @@ -355,6 +355,8 @@ typedef struct settings unsigned input_overlay_dpad_diagonal_sensitivity; unsigned input_overlay_abxy_diagonal_sensitivity; unsigned input_overlay_analog_recenter_zone; + unsigned input_overlay_behavior; + unsigned input_overlay_switch_delay_ms; unsigned input_overlay_lightgun_trigger_delay; unsigned input_overlay_lightgun_two_touch_input; unsigned input_overlay_lightgun_three_touch_input; @@ -641,6 +643,7 @@ typedef struct settings char path_cheat_database[PATH_MAX_LENGTH]; char path_content_database[PATH_MAX_LENGTH]; char path_overlay[PATH_MAX_LENGTH]; + char path_overlay_minimal[PATH_MAX_LENGTH]; char path_osk_overlay[PATH_MAX_LENGTH]; char path_record_config[PATH_MAX_LENGTH]; char path_stream_config[PATH_MAX_LENGTH]; diff --git a/input/drivers/android_input.c b/input/drivers/android_input.c index df37d1a05146..9b83f950edbb 100644 --- a/input/drivers/android_input.c +++ b/input/drivers/android_input.c @@ -166,6 +166,79 @@ bool (*engine_lookup_name)(char *buf, int *vendorId, int *productId, size_t len, int id); void (*engine_handle_dpad)(struct android_app *, AInputEvent*, int, int); +/* Pending-removed-device queue. Populated from the Java UI thread by the + * JNI export below (RetroActivityCommon.onInputDeviceRemoved) and drained + * from the native input poll thread on every poll. Guarded by + * android_app->mutex (the same lock used by APP_CMD_* handlers in this + * file), which is a known-good cross-thread primitive on Android. A small + * fixed queue size handles multi-device disconnect bursts (e.g. two BT + * controllers powered off together); overflow drops the newest event + * rather than clobbering pending entries. */ +#define ANDROID_REMOVED_QUEUE_SIZE 8 +static int g_android_removed_ids[ANDROID_REMOVED_QUEUE_SIZE]; +static int g_android_removed_count = 0; + +JNIEXPORT void JNICALL +Java_com_retroarch_browser_retroactivity_RetroActivityCommon_inputDeviceRemoved + (JNIEnv *env, jobject this_obj, jint device_id) +{ + struct android_app *android_app = (struct android_app*)g_android; + (void)env; + (void)this_obj; + /* Native side not up yet (activity early lifecycle): drop the event. + * RetroArch will re-autoconfig on next input event from the device if + * it reconnects, so this is safe to ignore. */ + if (!android_app) + return; + slock_lock(android_app->mutex); + if (g_android_removed_count < ANDROID_REMOVED_QUEUE_SIZE) + g_android_removed_ids[g_android_removed_count++] = (int)device_id; + slock_unlock(android_app->mutex); +} + +/* Called from android_input_poll. Drains queued device-removed events + * delivered by Android's InputManager and clears the matching pad_states + * slot + RetroArch input config name, so input_config_get_device_name(port) + * reflects the OS state. This is what lets the Conditional Overlay Profile + * resolver (FR #18178) see the controller as gone after a Bluetooth + * controller powers off or otherwise unbinds. */ +static void android_input_drain_pending_removed(android_input_t *android) +{ + struct android_app *android_app = (struct android_app*)g_android; + int local_ids[ANDROID_REMOVED_QUEUE_SIZE]; + int local_count = 0; + int i; + unsigned port; + if (!android_app) + return; + slock_lock(android_app->mutex); + if (g_android_removed_count > 0) + { + local_count = g_android_removed_count; + memcpy(local_ids, g_android_removed_ids, + sizeof(int) * (size_t)local_count); + g_android_removed_count = 0; + } + slock_unlock(android_app->mutex); + for (i = 0; i < local_count; i++) + { + int removed_id = local_ids[i]; + for (port = 0; port < MAX_USERS; port++) + { + if (android->pad_states[port].id == removed_id) + { + /* -1 sentinel rather than 0: Android InputDevice.getDeviceId() + * can legitimately return 0 for some internal/virtual devices, + * so a 0-cleared slot could spuriously match a future query. */ + android->pad_states[port].id = -1; + android->pad_states[port].name[0] = '\0'; + input_config_clear_device_name(port); + break; + } + } + } +} + static void android_input_poll_input_gingerbread(android_input_t *android); static void android_input_poll_input_default(android_input_t *android); static void (*android_input_poll_input)(android_input_t *android); @@ -1832,6 +1905,8 @@ static void android_input_poll(void *data) android_input_t *android = (android_input_t*)data; settings_t *settings = config_get_ptr(); + android_input_drain_pending_removed(android); + while ((ident = ALooper_pollAll(settings->uints.input_block_timeout, NULL, NULL, NULL)) >= 0) diff --git a/input/input_driver.c b/input/input_driver.c index a98b81a9cdc0..366c08132dcc 100644 --- a/input/input_driver.c +++ b/input/input_driver.c @@ -3,6 +3,7 @@ * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2017 - Daniel De Matteis * Copyright (C) 2016-2019 - Andr s Su rez (input mapper code) + * Copyright (C) 2026 - M00NR00ST3R (conditional overlay profiles) * * 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 @@ -6167,6 +6168,32 @@ void input_overlay_set_visibility(int overlay_idx, ol->iface->set_alpha(ol->iface_data, overlay_idx, 0.0); } +/* Conditional overlay profiles (FR #18178). + * Pure resolver: maps the configured behaviour mode and current controller + * state to the overlay profile that should be active. No side effects, so + * it is safe to call every frame. Additional input sources (e.g. an active + * stylus) can be added as parameters here without touching the call sites. */ +enum overlay_profile overlay_resolve_profile( + unsigned behavior, bool controller_connected, + bool minimal_available) +{ + switch (behavior) + { + case OVERLAY_BEHAVIOR_HIDE_WHEN_GAMEPAD: + return controller_connected + ? OVERLAY_PROFILE_HIDDEN : OVERLAY_PROFILE_FULL; + case OVERLAY_BEHAVIOR_CONDITIONAL: + if (controller_connected) + return minimal_available + ? OVERLAY_PROFILE_MINIMAL : OVERLAY_PROFILE_FULL; + return OVERLAY_PROFILE_FULL; + case OVERLAY_BEHAVIOR_STATIC: + default: + break; + } + return OVERLAY_PROFILE_FULL; +} + static bool input_overlay_want_hidden(void) { settings_t *settings = config_get_ptr(); @@ -6176,9 +6203,19 @@ static bool input_overlay_want_hidden(void) if (settings->bools.input_overlay_hide_in_menu) hide = (menu_state_get_ptr()->flags & MENU_ST_FLAG_ALIVE) != 0; #endif - if (settings->bools.input_overlay_hide_when_gamepad_connected - && !settings->bools.input_overlay_pointer_enable) - hide = hide || (input_config_get_device_name(0) != NULL); + /* Hard-hide (unload) only when the resolved profile is HIDDEN and pointer + * input is not enabled; with pointer input the runloop soft-hides instead + * so mouse/lightgun input keeps working. */ + if (!settings->bools.input_overlay_pointer_enable) + { + unsigned behavior = settings->uints.input_overlay_behavior; + bool controller_connected = (input_config_get_device_name(0) != NULL); + bool minimal_available = (*settings->paths.path_overlay_minimal != '\0'); + if (overlay_resolve_profile(behavior, controller_connected, + minimal_available) + == OVERLAY_PROFILE_HIDDEN) + hide = true; + } return hide; } @@ -6306,13 +6343,17 @@ static void input_overlay_loaded(retro_task_t *task, if (!enable_overlay) input_overlay_unload(); - /* Soft-hide when gamepad connected but pointer input enabled */ + /* Soft-hide when the resolved profile is HIDDEN but pointer input is + * enabled, so the overlay stays loaded for mouse/lightgun (FR #18178). */ { settings_t *settings = config_get_ptr(); + unsigned behavior = settings->uints.input_overlay_behavior; if (enable_overlay - && settings->bools.input_overlay_hide_when_gamepad_connected && settings->bools.input_overlay_pointer_enable - && input_config_get_device_name(0) != NULL) + && overlay_resolve_profile(behavior, + input_config_get_device_name(0) != NULL, + *settings->paths.path_overlay_minimal != '\0') + == OVERLAY_PROFILE_HIDDEN) ol->flags |= INPUT_OVERLAY_GAMEPAD_HIDDEN; } @@ -6350,6 +6391,22 @@ static const char *input_overlay_path(bool want_osk) if (want_osk) return settings->paths.path_osk_overlay; + + /* Conditional profiles: when the resolved profile is MINIMAL, skip the + * auto-preferred lookup and return the user's minimal preset directly. + * The resolver only returns MINIMAL when the path is non-empty, so no + * further fallback is needed here. */ + { + unsigned behavior = settings->uints.input_overlay_behavior; + bool controller_active = (input_config_get_device_name(0) != NULL); + bool minimal_available = (*settings->paths.path_overlay_minimal != '\0'); + enum overlay_profile profile = overlay_resolve_profile( + behavior, controller_active, minimal_available); + + if (profile == OVERLAY_PROFILE_MINIMAL) + return settings->paths.path_overlay_minimal; + } + /* If the option is set to turn this off, just return default */ if (!settings->bools.input_overlay_enable_autopreferred) return settings->paths.path_overlay; diff --git a/input/input_driver.h b/input/input_driver.h index ca3992312be5..4b5eee90d7ec 100644 --- a/input/input_driver.h +++ b/input/input_driver.h @@ -1140,6 +1140,15 @@ void input_overlay_unload(void); void input_overlay_init(void); +/* Conditional overlay profiles (FR #18178): pure resolver mapping the + * behaviour mode + controller state to the overlay profile to show. + * minimal_available must be true iff a non-empty Minimal Overlay Preset path + * is configured; when false the resolver downgrades MINIMAL to FULL so the + * caller's state machine stays in sync with what will actually be loaded. */ +enum overlay_profile overlay_resolve_profile( + unsigned behavior, bool controller_connected, + bool minimal_available); + void input_overlay_check_mouse_cursor(void); #endif diff --git a/input/input_overlay.h b/input/input_overlay.h index db0453beaa8e..afcd98b03762 100644 --- a/input/input_overlay.h +++ b/input/input_overlay.h @@ -108,6 +108,25 @@ enum overlay_show_input_type OVERLAY_SHOW_INPUT_LAST }; +/* Conditional overlay profiles (FR #18178): how the on-screen overlay + * reacts when a gamepad is connected to port 1. Stored in + * settings->uints.input_overlay_behavior. */ +enum overlay_behavior +{ + OVERLAY_BEHAVIOR_STATIC = 0, /* never change the overlay */ + OVERLAY_BEHAVIOR_HIDE_WHEN_GAMEPAD, /* hide overlay when gamepad present */ + OVERLAY_BEHAVIOR_CONDITIONAL, /* swap to the minimal preset instead */ + OVERLAY_BEHAVIOR_LAST +}; + +/* Resolved overlay state: which preset (if any) should be active now. */ +enum overlay_profile +{ + OVERLAY_PROFILE_FULL = 0, /* full/touch preset (paths.path_overlay) */ + OVERLAY_PROFILE_MINIMAL, /* minimal preset (paths.path_overlay_minimal) */ + OVERLAY_PROFILE_HIDDEN /* show nothing */ +}; + enum OVERLAY_LOADER_FLAGS { OVERLAY_LOADER_RGBA_SUPPORT = (1 << 0), diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 0124937944f4..7e26486dfd85 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -2736,6 +2736,10 @@ MSG_HASH( MENU_ENUM_LABEL_OVERLAY_PRESET, MENU_ENUM_LABEL_OVERLAY_PRESET_STR ) +MSG_HASH( + MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET, + MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET_STR + ) MSG_HASH( MENU_ENUM_LABEL_OSK_OVERLAY_PRESET, MENU_ENUM_LABEL_OSK_OVERLAY_PRESET_STR diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 75dac5f89b37..6342678bd166 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -5414,6 +5414,42 @@ MSG_HASH( "Hide the overlay when a physical controller is connected in port 1. Overlay will not be restored automatically when controller is disconnected." ) #endif +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR, + "Overlay Behavior" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_INPUT_OVERLAY_BEHAVIOR, + "Choose how the on-screen overlay reacts when a physical controller is connected in port 1. 'Static' never changes the overlay. 'Hide When Controller Is Connected' removes it. 'Conditional Profiles' switches to the Minimal Overlay Preset after the switch delay." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_STATIC, + "Static" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_HIDE_WHEN_GAMEPAD, + "Hide When Controller Is Connected" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_CONDITIONAL, + "Conditional Profiles" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_MINIMAL_PRESET, + "Minimal Overlay Preset" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_INPUT_OVERLAY_MINIMAL_PRESET, + "Overlay preset loaded in 'Conditional Profiles' mode when a controller is connected. If left empty, the main Overlay Preset is used instead." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_SWITCH_DELAY, + "Overlay Switch Delay (ms)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_INPUT_OVERLAY_SWITCH_DELAY, + "Time in milliseconds that a controller connect or disconnect must remain stable before the overlay switches, to prevent flicker on rapid plug/unplug." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_SHOW_INPUTS, "Show Inputs on Overlay" diff --git a/menu/cbs/menu_cbs_ok.c b/menu/cbs/menu_cbs_ok.c index d54574042731..a75146da3ca1 100644 --- a/menu/cbs/menu_cbs_ok.c +++ b/menu/cbs/menu_cbs_ok.c @@ -1130,6 +1130,32 @@ int generic_action_ok_displaylist_push( parent_dir, sizeof(parent_dir), true); } + info_path = parent_dir; + break; + case ACTION_OK_DL_INPUT_OVERLAY_MINIMAL_PRESET: + filebrowser_set_type(FILEBROWSER_SELECT_OVERLAY); + info.directory_ptr = idx; + info_label = MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET_STR; + info.enum_idx = MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET; + dl_type = DISPLAYLIST_FILE_BROWSER_SELECT_FILE; + + { + char expanded[PATH_MAX_LENGTH]; + fill_pathname_expand_special(expanded, + settings->paths.path_overlay_minimal, sizeof(expanded)); +#ifdef HAVE_COMPRESSION + { + char *delim = (char*)path_get_archive_delim(expanded); + if (delim) + *delim = '\0'; + } +#endif + action_ok_get_file_browser_start_path( + expanded, + settings->paths.directory_overlay, + parent_dir, sizeof(parent_dir), true); + } + info_path = parent_dir; break; case ACTION_OK_DL_OSK_OVERLAY_PRESET: @@ -2476,7 +2502,18 @@ static int generic_action_ok(const char *path, break; case ACTION_OK_SET_PATH_OVERLAY: flush_char = MENU_ENUM_LABEL_DEFERRED_ONSCREEN_OVERLAY_SETTINGS_LIST_STR; - retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_OVERLAY_PRESET, NULL); + /* Both the main and minimal overlay-preset file pickers reach this + * case (both register FILEBROWSER_SELECT_OVERLAY). Dispatch the + * override flag based on which setting the user is editing so the + * minimal-preset path participates in per-game / per-core overrides + * symmetrically with the main preset. */ + if (string_is_equal(menu_label, + MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET_STR)) + retroarch_override_setting_set( + RARCH_OVERRIDE_SETTING_OVERLAY_MINIMAL_PRESET, NULL); + else + retroarch_override_setting_set( + RARCH_OVERRIDE_SETTING_OVERLAY_PRESET, NULL); ret = set_path_generic(menu_label, action_path); break; case ACTION_OK_SET_PATH_OSK_OVERLAY: @@ -6691,6 +6728,7 @@ STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_push_generic_list, ACTION_OK_DL_GENERIC) STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_audio_dsp_plugin, ACTION_OK_DL_AUDIO_DSP_PLUGIN) STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_video_filter, ACTION_OK_DL_VIDEO_FILTER) STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_overlay_preset, ACTION_OK_DL_OVERLAY_PRESET) +STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_input_overlay_minimal_preset, ACTION_OK_DL_INPUT_OVERLAY_MINIMAL_PRESET) STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_osk_overlay_preset, ACTION_OK_DL_OSK_OVERLAY_PRESET) STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_video_font, ACTION_OK_DL_VIDEO_FONT) STATIC_DEFAULT_ACTION_OK_FUNC(action_ok_rpl_entry, ACTION_OK_DL_RPL_ENTRY) @@ -9324,6 +9362,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_AUDIO_DSP_PLUGIN, action_ok_audio_dsp_plugin}, {MENU_ENUM_LABEL_VIDEO_FILTER, action_ok_video_filter}, {MENU_ENUM_LABEL_OVERLAY_PRESET, action_ok_overlay_preset}, + {MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET, action_ok_input_overlay_minimal_preset}, {MENU_ENUM_LABEL_OSK_OVERLAY_PRESET, action_ok_osk_overlay_preset}, {MENU_ENUM_LABEL_RECORD_CONFIG, action_ok_record_configfile}, {MENU_ENUM_LABEL_STREAM_CONFIG, action_ok_stream_configfile}, @@ -9579,6 +9618,7 @@ static int menu_cbs_init_bind_ok_compare_label(menu_file_list_cbs_t *cbs, {MENU_ENUM_LABEL_AUDIO_DSP_PLUGIN, action_ok_audio_dsp_plugin}, {MENU_ENUM_LABEL_VIDEO_FILTER, action_ok_video_filter}, {MENU_ENUM_LABEL_OVERLAY_PRESET, action_ok_overlay_preset}, + {MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET, action_ok_input_overlay_minimal_preset}, {MENU_ENUM_LABEL_OSK_OVERLAY_PRESET, action_ok_osk_overlay_preset}, {MENU_ENUM_LABEL_REMAP_FILE_LOAD, action_ok_remap_file}, {MENU_ENUM_LABEL_OVERRIDE_FILE_LOAD, action_ok_override_file}, diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 8639dc51ead1..14f5b71deb33 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -747,6 +747,9 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_hide_when_gamepad_conn #else DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_hide_when_gamepad_connected, MENU_ENUM_SUBLABEL_INPUT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED) #endif +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_behavior, MENU_ENUM_SUBLABEL_INPUT_OVERLAY_BEHAVIOR) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_minimal_preset, MENU_ENUM_SUBLABEL_INPUT_OVERLAY_MINIMAL_PRESET) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_switch_delay, MENU_ENUM_SUBLABEL_INPUT_OVERLAY_SWITCH_DELAY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_show_mouse_cursor, MENU_ENUM_SUBLABEL_INPUT_OVERLAY_SHOW_MOUSE_CURSOR) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_auto_rotate, MENU_ENUM_SUBLABEL_INPUT_OVERLAY_AUTO_ROTATE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_overlay_auto_scale, MENU_ENUM_SUBLABEL_INPUT_OVERLAY_AUTO_SCALE) @@ -4396,6 +4399,15 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_overlay_hide_when_gamepad_connected); #endif break; + case MENU_ENUM_LABEL_INPUT_OVERLAY_BEHAVIOR: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_overlay_behavior); + break; + case MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_overlay_minimal_preset); + break; + case MENU_ENUM_LABEL_INPUT_OVERLAY_SWITCH_DELAY: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_overlay_switch_delay); + break; case MENU_ENUM_LABEL_INPUT_OVERLAY_SHOW_INPUTS: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_overlay_show_inputs); break; diff --git a/menu/menu_cbs.h b/menu/menu_cbs.h index c3e10d2d203e..409dcbf03ac0 100644 --- a/menu/menu_cbs.h +++ b/menu/menu_cbs.h @@ -84,6 +84,7 @@ enum ACTION_OK_DL_AUDIO_DSP_PLUGIN, ACTION_OK_DL_VIDEO_FILTER, ACTION_OK_DL_OVERLAY_PRESET, + ACTION_OK_DL_INPUT_OVERLAY_MINIMAL_PRESET, ACTION_OK_DL_OSK_OVERLAY_PRESET, ACTION_OK_DL_VIDEO_FONT, ACTION_OK_DL_SHADER_PASS, diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 67ad07fe4816..c0756bc4d837 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -10626,6 +10626,7 @@ unsigned menu_displaylist_build_list( enum overlay_show_input_type input_overlay_show_inputs = (enum overlay_show_input_type) settings->uints.input_overlay_show_inputs; + unsigned input_overlay_behavior = settings->uints.input_overlay_behavior; static menu_displaylist_build_info_selective_t build_list[] = { {MENU_ENUM_LABEL_INPUT_OVERLAY_ENABLE, PARSE_ONLY_BOOL, true }, @@ -10634,7 +10635,9 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_OVERLAY_OPACITY, PARSE_ONLY_FLOAT, false }, {MENU_ENUM_LABEL_INPUT_OVERLAY_BEHIND_MENU, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_INPUT_OVERLAY_HIDE_IN_MENU, PARSE_ONLY_BOOL, false }, - {MENU_ENUM_LABEL_INPUT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED, PARSE_ONLY_BOOL, false }, + {MENU_ENUM_LABEL_INPUT_OVERLAY_BEHAVIOR, PARSE_ONLY_UINT, false }, + {MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET, PARSE_ONLY_PATH, false }, + {MENU_ENUM_LABEL_INPUT_OVERLAY_SWITCH_DELAY, PARSE_ONLY_UINT, false }, {MENU_ENUM_LABEL_INPUT_OVERLAY_SHOW_INPUTS, PARSE_ONLY_UINT, false }, {MENU_ENUM_LABEL_INPUT_OVERLAY_SHOW_INPUTS_PORT, PARSE_ONLY_UINT, false }, {MENU_ENUM_LABEL_INPUT_OVERLAY_SHOW_MOUSE_CURSOR, PARSE_ONLY_BOOL, false }, @@ -10667,7 +10670,7 @@ unsigned menu_displaylist_build_list( { case MENU_ENUM_LABEL_INPUT_OVERLAY_BEHIND_MENU: case MENU_ENUM_LABEL_INPUT_OVERLAY_HIDE_IN_MENU: - case MENU_ENUM_LABEL_INPUT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED: + case MENU_ENUM_LABEL_INPUT_OVERLAY_BEHAVIOR: case MENU_ENUM_LABEL_INPUT_OVERLAY_SHOW_INPUTS: case MENU_ENUM_LABEL_INPUT_OVERLAY_SHOW_MOUSE_CURSOR: case MENU_ENUM_LABEL_INPUT_OVERLAY_AUTO_ROTATE: @@ -10678,6 +10681,14 @@ unsigned menu_displaylist_build_list( case MENU_ENUM_LABEL_INPUT_OVERLAY_POINTER_ENABLE: build_list[i].checked = input_overlay_enable; break; + case MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET: + build_list[i].checked = input_overlay_enable && + (input_overlay_behavior == OVERLAY_BEHAVIOR_CONDITIONAL); + break; + case MENU_ENUM_LABEL_INPUT_OVERLAY_SWITCH_DELAY: + build_list[i].checked = input_overlay_enable && + (input_overlay_behavior != OVERLAY_BEHAVIOR_STATIC); + break; case MENU_ENUM_LABEL_INPUT_OVERLAY_SHOW_INPUTS_PORT: build_list[i].checked = input_overlay_enable && (input_overlay_show_inputs == OVERLAY_SHOW_INPUT_PHYSICAL); diff --git a/menu/menu_setting.c b/menu/menu_setting.c index ac54478db79f..95b50d3428ef 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -5606,6 +5606,33 @@ static size_t setting_get_string_representation_uint_input_overlay_show_inputs( return 0; } +static size_t setting_get_string_representation_uint_overlay_behavior( + rarch_setting_t *setting, char *s, size_t len) +{ + if (setting) + { + switch (*setting->value.target.unsigned_integer) + { + case OVERLAY_BEHAVIOR_STATIC: + return strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_STATIC), + len); + case OVERLAY_BEHAVIOR_HIDE_WHEN_GAMEPAD: + return strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_HIDE_WHEN_GAMEPAD), + len); + case OVERLAY_BEHAVIOR_CONDITIONAL: + return strlcpy(s, + msg_hash_to_str( + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_CONDITIONAL), + len); + } + } + return 0; +} + static size_t setting_get_string_representation_uint_input_overlay_show_inputs_port( rarch_setting_t *setting, char *s, size_t len) { @@ -18109,22 +18136,65 @@ static bool setting_append_list( ); (*list)[list_info->index - 1].change_handler = overlay_enable_toggle_change_handler; - CONFIG_BOOL( + /* Legacy "Hide When Controller Is Connected" bool is superseded by + * input_overlay_behavior (FR #18178). The cfg key + migration in + * configuration.c are kept for downgrade safety; the menu entry is + * intentionally not registered so users see only the new enum. */ + + CONFIG_UINT( list, list_info, - &settings->bools.input_overlay_hide_when_gamepad_connected, - MENU_ENUM_LABEL_INPUT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED, - MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED, - DEFAULT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED, - MENU_ENUM_LABEL_VALUE_OFF, - MENU_ENUM_LABEL_VALUE_ON, + &settings->uints.input_overlay_behavior, + MENU_ENUM_LABEL_INPUT_OVERLAY_BEHAVIOR, + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR, + DEFAULT_OVERLAY_BEHAVIOR, &group_info, &subgroup_info, parent_group, general_write_handler, - general_read_handler, - SD_FLAG_NONE + general_read_handler ); - (*list)[list_info->index - 1].change_handler = overlay_enable_toggle_change_handler; + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX; + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; + (*list)[list_info->index - 1].action_left = &setting_uint_action_left_with_refresh; + (*list)[list_info->index - 1].action_right = &setting_uint_action_right_with_refresh; + (*list)[list_info->index - 1].get_string_representation = + &setting_get_string_representation_uint_overlay_behavior; + (*list)[list_info->index - 1].change_handler = overlay_enable_toggle_change_handler; + menu_settings_list_current_add_range(list, list_info, 0, OVERLAY_BEHAVIOR_LAST-1, 1, true, true); + + CONFIG_PATH( + list, list_info, + settings->paths.path_overlay_minimal, + sizeof(settings->paths.path_overlay_minimal), + MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET, + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_MINIMAL_PRESET, + settings->paths.directory_overlay, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + MENU_SETTINGS_LIST_CURRENT_ADD_VALUES(list, list_info, "cfg"); + MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_OVERLAY_INIT); + + CONFIG_UINT( + list, list_info, + &settings->uints.input_overlay_switch_delay_ms, + MENU_ENUM_LABEL_INPUT_OVERLAY_SWITCH_DELAY, + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_SWITCH_DELAY, + DEFAULT_OVERLAY_SWITCH_DELAY_MS, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler + ); + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; + /* Min clamped at 100 ms: anything below defeats the debounce window + * this setting exists to provide (FR #18178 motivates the delay to + * prevent USB-enumeration / hot-plug flapping causing overlay + * reload thrash). */ + menu_settings_list_current_add_range(list, list_info, 100, 3000, 50, true, true); CONFIG_UINT( list, list_info, diff --git a/msg_hash.h b/msg_hash.h index edf2388766b2..383baab91d74 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1357,6 +1357,14 @@ enum msg_hash_enums MENU_ENUM_SUBLABEL_INPUT_OVERLAY_HIDE_WHEN_GAMEPAD_CONNECTED_ANDROID, #endif + /* Conditional overlay profiles (FR #18178) */ + MENU_LABEL(INPUT_OVERLAY_BEHAVIOR), + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_STATIC, + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_HIDE_WHEN_GAMEPAD, + MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_BEHAVIOR_CONDITIONAL, + MENU_LABEL(INPUT_OVERLAY_MINIMAL_PRESET), + MENU_LABEL(INPUT_OVERLAY_SWITCH_DELAY), + MENU_LABEL(INPUT_OVERLAY_SHOW_INPUTS), MENU_LABEL(INPUT_OVERLAY_SHOW_INPUTS_PORT), MENU_ENUM_LABEL_VALUE_INPUT_OVERLAY_SHOW_INPUTS_NONE, diff --git a/msg_hash_lbl_str.h b/msg_hash_lbl_str.h index 3eff932c3904..c6fe6b41f327 100644 --- a/msg_hash_lbl_str.h +++ b/msg_hash_lbl_str.h @@ -685,6 +685,7 @@ #define MENU_ENUM_LABEL_OVERLAY_OPACITY_STR "input_overlay_opacity" #define MENU_ENUM_LABEL_OSK_OVERLAY_OPACITY_STR "input_osk_overlay_opacity" #define MENU_ENUM_LABEL_OVERLAY_PRESET_STR "input_overlay" +#define MENU_ENUM_LABEL_INPUT_OVERLAY_MINIMAL_PRESET_STR "input_overlay_minimal" #define MENU_ENUM_LABEL_OSK_OVERLAY_PRESET_STR "input_osk_overlay" #define MENU_ENUM_LABEL_OVERLAY_SCALE_LANDSCAPE_STR "input_overlay_scale_landscape" #define MENU_ENUM_LABEL_OVERLAY_ASPECT_ADJUST_LANDSCAPE_STR "input_overlay_aspect_adjust_landscape" diff --git a/pkg/android/phoenix-common/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java b/pkg/android/phoenix-common/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java index 2847e59c03b1..1b49a7b7a519 100644 --- a/pkg/android/phoenix-common/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java +++ b/pkg/android/phoenix-common/src/com/retroarch/browser/retroactivity/RetroActivityCommon.java @@ -554,6 +554,10 @@ public void onInputDeviceRemoved(int deviceId) /* Close any cached USB connection so we don't hold a stale handle. */ closeUsbConnection(deviceId); mUsbPermissionPending.remove(Integer.valueOf(deviceId)); + /* Tell the native input driver so the conditional overlay resolver + * and other consumers of input_config_get_device_name() see the + * device as gone. */ + inputDeviceRemoved(deviceId); } /** @@ -990,6 +994,15 @@ public void deleteCore(String coreName) { */ public native void safTreeAdded(String tree); + /** + * Notify the native input driver that Android removed an input device. + * Called from {@link #onInputDeviceRemoved(int)} so the conditional overlay + * resolver (FR #18178) sees a controller as gone after a Bluetooth + * controller is powered off / unbinds, and any other native consumer of + * input_config_get_device_name() reflects the OS state. + */ + public native void inputDeviceRemoved(int deviceId); + /////////////// Private methods /////////////// diff --git a/retroarch.c b/retroarch.c index b787acf1ac53..03f883b68df7 100644 --- a/retroarch.c +++ b/retroarch.c @@ -5865,6 +5865,9 @@ void retroarch_override_setting_set( case RARCH_OVERRIDE_SETTING_OVERLAY_PRESET: p_rarch->flags |= RARCH_FLAGS_HAS_SET_OVERLAY_PRESET; break; + case RARCH_OVERRIDE_SETTING_OVERLAY_MINIMAL_PRESET: + p_rarch->flags |= RARCH_FLAGS_HAS_SET_OVERLAY_MINIMAL_PRESET; + break; case RARCH_OVERRIDE_SETTING_NONE: default: break; @@ -5950,6 +5953,9 @@ void retroarch_override_setting_unset( case RARCH_OVERRIDE_SETTING_OVERLAY_PRESET: p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_OVERLAY_PRESET; break; + case RARCH_OVERRIDE_SETTING_OVERLAY_MINIMAL_PRESET: + p_rarch->flags &= ~RARCH_FLAGS_HAS_SET_OVERLAY_MINIMAL_PRESET; + break; case RARCH_OVERRIDE_SETTING_NONE: default: break; @@ -8821,6 +8827,8 @@ bool retroarch_override_setting_is_set( return ((p_rarch->flags & RARCH_FLAGS_CLI_DATABASE_SCAN) > 0); case RARCH_OVERRIDE_SETTING_OVERLAY_PRESET: return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_OVERLAY_PRESET) > 0); + case RARCH_OVERRIDE_SETTING_OVERLAY_MINIMAL_PRESET: + return ((p_rarch->flags & RARCH_FLAGS_HAS_SET_OVERLAY_MINIMAL_PRESET) > 0); case RARCH_OVERRIDE_SETTING_NONE: default: break; diff --git a/retroarch.h b/retroarch.h index 1b653ba1388c..8f27213fb647 100644 --- a/retroarch.h +++ b/retroarch.h @@ -112,7 +112,8 @@ enum rarch_state_flags RARCH_FLAGS_CLI_DATABASE_SCAN = (1 << 14), RARCH_FLAGS_HAS_SET_XDELTA_PREF = (1 << 15), RARCH_FLAGS_XDELTA_PREF = (1 << 16), - RARCH_FLAGS_HAS_SET_OVERLAY_PRESET = (1 << 17) + RARCH_FLAGS_HAS_SET_OVERLAY_PRESET = (1 << 17), + RARCH_FLAGS_HAS_SET_OVERLAY_MINIMAL_PRESET = (1 << 18) }; bool retroarch_ctl(enum rarch_ctl_state state, void *data); diff --git a/retroarch_types.h b/retroarch_types.h index d55a33c00cbe..d4c9fb0c1c92 100644 --- a/retroarch_types.h +++ b/retroarch_types.h @@ -129,6 +129,7 @@ enum rarch_override_setting RARCH_OVERRIDE_SETTING_LOG_TO_FILE, RARCH_OVERRIDE_SETTING_DATABASE_SCAN, RARCH_OVERRIDE_SETTING_OVERLAY_PRESET, + RARCH_OVERRIDE_SETTING_OVERLAY_MINIMAL_PRESET, RARCH_OVERRIDE_SETTING_LAST }; diff --git a/runloop.c b/runloop.c index 30edd52f0f0a..288712df8259 100644 --- a/runloop.c +++ b/runloop.c @@ -6,6 +6,7 @@ * Copyright (C) 2016-2019 - Brad Parker * Copyright (C) 2016-2019 - Andr�s Su�rez (input mapper code) * Copyright (C) 2016-2017 - Gregor Richards (network code) + * Copyright (C) 2026 - M00NR00ST3R (conditional overlay profiles) * * 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- @@ -5808,36 +5809,77 @@ static enum runloop_state_enum runloop_check_state( unsigned video_driver_width = video_st->width; unsigned video_driver_height = video_st->height; bool check_next_rotation = true; - bool input_overlay_hide_when_gamepad_connected = settings->bools.input_overlay_hide_when_gamepad_connected; bool input_overlay_auto_rotate = settings->bools.input_overlay_auto_rotate; - /* Check whether overlay should be hidden - * when a gamepad is connected */ - if (input_overlay_hide_when_gamepad_connected) + /* Conditional overlay profiles (FR #18178). + * Resolve the target profile each frame; when it changes, debounce by + * input_overlay_switch_delay_ms before committing. Committing reloads + * the resolver-selected preset (init) or hides the overlay (unload). + * With pointer input enabled we soft-hide via a flag instead of + * unloading, so mouse/lightgun input keeps working. */ { - static bool last_controller_connected = false; - bool controller_connected = (input_config_get_device_name(0) != NULL); - - /* When pointer input is enabled, soft-hide instead of - * unloading so mouse/lightgun input remains functional. - * Level-triggered: enforce flag state every frame. */ - if ( settings->bools.input_overlay_pointer_enable - && input_st->overlay_ptr) + static bool profile_initialized = false; + static unsigned last_profile = 0; + static unsigned pending_profile = 0; + static retro_time_t pending_since = 0; + static uint64_t last_frame_count = 0; + unsigned behavior = settings->uints.input_overlay_behavior; + bool controller_active = (input_config_get_device_name(0) != NULL); + bool minimal_available = (*settings->paths.path_overlay_minimal != '\0'); + unsigned cur_profile = (unsigned)overlay_resolve_profile( + behavior, controller_active, minimal_available); + + /* Reset cross-content stale state: if the core's frame counter + * regressed (content unload / new content load) the debounce + * timestamp from the previous session would otherwise spuriously + * satisfy the elapsed check on the next transition. */ + if (frame_count < last_frame_count) + profile_initialized = false; + last_frame_count = frame_count; + + /* First frame (this content session): sync to current state + * without acting, so a stale timestamp cannot trigger a spurious + * switch at startup. */ + if (!profile_initialized) { - if (controller_connected) + last_profile = cur_profile; + pending_profile = cur_profile; + pending_since = current_time; + profile_initialized = true; + } + + if ( settings->bools.input_overlay_pointer_enable + && input_st->overlay_ptr) + { + /* Pointer mode: keep the overlay loaded and soft-hide via flag. + * Level-triggered every frame. */ + if (cur_profile == (unsigned)OVERLAY_PROFILE_HIDDEN) input_st->overlay_ptr->flags |= INPUT_OVERLAY_GAMEPAD_HIDDEN; else input_st->overlay_ptr->flags &= ~INPUT_OVERLAY_GAMEPAD_HIDDEN; + last_profile = cur_profile; + pending_profile = cur_profile; } - else if (controller_connected != last_controller_connected) + else if (cur_profile != pending_profile) { - if (controller_connected) - input_overlay_unload(); - else - input_overlay_init(); + /* Target changed: (re)start the debounce window. */ + pending_profile = cur_profile; + pending_since = current_time; + } + else if (cur_profile != last_profile) + { + /* Stable target awaiting commit: switch once the delay elapses. */ + retro_time_t delay_us = (retro_time_t) + settings->uints.input_overlay_switch_delay_ms * 1000; + if ((current_time - pending_since) >= delay_us) + { + if (cur_profile == (unsigned)OVERLAY_PROFILE_HIDDEN) + input_overlay_unload(); + else + input_overlay_init(); + last_profile = cur_profile; + } } - - last_controller_connected = controller_connected; } /* Check next overlay hotkey */