Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config.def.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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
}

Expand All @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down
75 changes: 75 additions & 0 deletions input/drivers/android_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down
69 changes: 63 additions & 6 deletions input/input_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions input/input_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions input/input_overlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
4 changes: 4 additions & 0 deletions intl/msg_hash_lbl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading