Skip to content
Merged
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
6 changes: 6 additions & 0 deletions config.def.h
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,12 @@
* sensor input, if supported */
#define DEFAULT_INPUT_SENSORS_ENABLE true

/* Use the Android system (IME) keyboard for menu text entry instead of
* the built-in on-screen keyboard. Off by default so gamepad-only
* and no-touch devices keep the navigable on-screen keyboard at
* all times. */
#define DEFAULT_INPUT_ANDROID_SYSTEM_KEYBOARD false

/* Automatically enable game focus when running or
* resuming content */
#define DEFAULT_INPUT_AUTO_GAME_FOCUS AUTO_GAME_FOCUS_OFF
Expand Down
3 changes: 2 additions & 1 deletion configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -2549,6 +2549,7 @@ static struct config_bool_setting *populate_settings_bool(
SETTING_BOOL("menu_swap_ok_cancel_buttons", &settings->bools.input_menu_swap_ok_cancel_buttons, true, DEFAULT_MENU_SWAP_OK_CANCEL_BUTTONS, false);
SETTING_BOOL("menu_swap_scroll_buttons", &settings->bools.input_menu_swap_scroll_buttons, true, DEFAULT_MENU_SWAP_SCROLL_BUTTONS, false);
#endif
SETTING_BOOL("input_android_system_keyboard", &settings->bools.input_android_system_keyboard, true, DEFAULT_INPUT_ANDROID_SYSTEM_KEYBOARD, false);



Expand Down Expand Up @@ -9821,7 +9822,7 @@ bool input_remapping_save_file(const char *path)
{
if (remap_id == RARCH_UNMAPPED)
{
if (!runloop_st->system.input_desc_btn[i][j]
if (!runloop_st->system.input_desc_btn[i][j]
|| !*runloop_st->system.input_desc_btn[i][j])
config_unset(conf, _ident);
else
Expand Down
1 change: 1 addition & 0 deletions configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ typedef struct settings
bool input_remap_sort_by_controller_enable;
bool input_autodetect_enable;
bool input_sensors_enable;
bool input_android_system_keyboard;
bool input_overlay_enable;
bool input_overlay_enable_autopreferred;
bool input_overlay_behind_menu;
Expand Down
4 changes: 4 additions & 0 deletions frontend/drivers/platform_unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -2357,6 +2357,10 @@ static void frontend_unix_init(void *data)
"isScreenReaderEnabled", "()Z");
GET_METHOD_ID(env, android_app->accessibilitySpeak, class,
"accessibilitySpeak", "(Ljava/lang/String;)V");
GET_METHOD_ID(env, android_app->showKeyboard, class,
"showKeyboard", "(Ljava/lang/String;Ljava/lang/String;)V");
GET_METHOD_ID(env, android_app->hideKeyboard, class,
"hideKeyboard", "()V");

CALL_BOOLEAN_METHOD(env, android_app->is_play_store_build, android_app->activity->clazz, android_app->isPlayStoreBuild)

Expand Down
3 changes: 3 additions & 0 deletions frontend/drivers/platform_unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ struct android_app
jmethodID isScreenReaderEnabled;
jmethodID accessibilitySpeak;

jmethodID showKeyboard;
jmethodID hideKeyboard;

struct
{
unsigned width, height;
Expand Down
225 changes: 225 additions & 0 deletions input/drivers/android_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,228 @@ static typeof(AMotionEvent_getButtonState) *p_AMotionEvent_getButtonState;
static void *libandroid_handle;
#endif

/* Android system (IME) keyboard support for the menu OSK.
*
* Mirrors the iOS ios_keyboard_* hooks (ui/drivers/ui_cocoatouch.m):
* when the menu wants text input, the keyboard line buffer is repointed
* to an owned 512-byte buffer and the Java side raises the native soft
* keyboard (which gives copy/paste and password managers that the
* custom on-screen keyboard cannot).
*
* The Android soft keyboard runs on the UI thread, so committed/pasted
* text arrives via the onSystemKeyboardInput JNI callback on that
* thread; it is staged there (under a lock) and applied on the
* RetroArch input thread in android_keyboard_poll(). */
#define ANDROID_KBD_BUFFER_SIZE 512

static slock_t *android_kbd_lock = NULL;
static char *android_kbd_buffer = NULL; /* == keyboard_line.buffer */
static size_t *android_kbd_size_ptr = NULL;
static size_t *android_kbd_ptr_ptr = NULL;
static input_keyboard_line_complete_t android_kbd_cb = NULL;
static void *android_kbd_userdata = NULL;
static bool android_kbd_open = false;

/* Staging: written by the JNI/UI thread, drained by the input thread. */
static char android_kbd_staging[ANDROID_KBD_BUFFER_SIZE];
static bool android_kbd_dirty = false;
static bool android_kbd_finished = false;
static bool android_kbd_cancel = false;

/* Called by Java (UI thread) on every text change, and once more with
* finished = true on Done/Enter. A null text means the keyboard was
* dismissed without confirming, i.e. cancel. */
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_onSystemKeyboardInput(
JNIEnv *env, jobject this_obj, jstring text_obj, jboolean finished)
{
if (!android_kbd_lock)
return;

slock_lock(android_kbd_lock);
if (text_obj)
{
const char *text = (*env)->GetStringUTFChars(env, text_obj, NULL);
if (text)
{
strlcpy(android_kbd_staging, text, sizeof(android_kbd_staging));
(*env)->ReleaseStringUTFChars(env, text_obj, text);
}
android_kbd_cancel = false;
}
else
{
android_kbd_staging[0] = '\0';
android_kbd_cancel = true;
}
android_kbd_dirty = true;
if (finished)
android_kbd_finished = true;
slock_unlock(android_kbd_lock);
}

bool android_keyboard_start(char **buffer_ptr, size_t *size_ptr,
size_t *ptr_ptr, const char *label,
input_keyboard_line_complete_t cb, void *userdata)
{
JNIEnv *env;
char *allocated;
size_t len;
struct android_app *android_app = (struct android_app*)g_android;

if (!android_app || !android_app->showKeyboard || !buffer_ptr || !size_ptr)
return false;

if (!android_kbd_lock && !(android_kbd_lock = slock_new()))
return false;

if (!(allocated = (char*)malloc(ANDROID_KBD_BUFFER_SIZE)))
return false;

/* Seed with any existing content (e.g. when editing a value). */
if (*buffer_ptr && **buffer_ptr)
strlcpy(allocated, *buffer_ptr, ANDROID_KBD_BUFFER_SIZE);
else
allocated[0] = '\0';

/* Repoint the keyboard line at our buffer; it is freed later by
* input_keyboard_line_free(), mirroring the iOS path. */
*buffer_ptr = allocated;
len = strlen(allocated);
*size_ptr = len;
if (ptr_ptr)
*ptr_ptr = len;

slock_lock(android_kbd_lock);
android_kbd_buffer = allocated;
android_kbd_size_ptr = size_ptr;
android_kbd_ptr_ptr = ptr_ptr;
android_kbd_cb = cb;
android_kbd_userdata = userdata;
android_kbd_staging[0] = '\0';
android_kbd_dirty = false;
android_kbd_finished = false;
android_kbd_cancel = false;
android_kbd_open = true;
slock_unlock(android_kbd_lock);

if ((env = jni_thread_getenv()))
{
jstring jlabel = label ? (*env)->NewStringUTF(env, label) : NULL;
jstring jinit = (*env)->NewStringUTF(env, allocated);
CALL_VOID_METHOD_PARAM(env, android_app->activity->clazz,
android_app->showKeyboard, jlabel, jinit);
if (jlabel)
(*env)->DeleteLocalRef(env, jlabel);
if (jinit)
(*env)->DeleteLocalRef(env, jinit);
}

return true;
}

bool android_keyboard_active(void)
{
return android_kbd_open;
}

void android_keyboard_end(void)
{
JNIEnv *env = NULL;
struct android_app *android_app = (struct android_app*)g_android;

if (!android_kbd_open || !android_kbd_lock)
return;

slock_lock(android_kbd_lock);
android_kbd_open = false;
android_kbd_buffer = NULL;
android_kbd_size_ptr = NULL;
android_kbd_ptr_ptr = NULL;
android_kbd_cb = NULL;
android_kbd_userdata = NULL;
android_kbd_dirty = false;
android_kbd_finished = false;
android_kbd_cancel = false;
slock_unlock(android_kbd_lock);

if (android_app && android_app->hideKeyboard && (env = jni_thread_getenv()))
CALL_VOID_METHOD(env, android_app->activity->clazz,
android_app->hideKeyboard);
}

/* Drain staged IME text on the RetroArch input thread. */
void android_keyboard_poll(void)
{
bool finished;
bool cancel;
char *buffer;
void *userdata;
input_keyboard_line_complete_t cb;

if (!android_kbd_open || !android_kbd_lock)
return;

slock_lock(android_kbd_lock);
if (!android_kbd_dirty)
{
slock_unlock(android_kbd_lock);
return;
}

/* Sync staged text into the live keyboard line buffer so the menu
* displays it (same role as the iOS UITextField delegate). */
if (!android_kbd_cancel && android_kbd_buffer)
{
size_t len;
strlcpy(android_kbd_buffer, android_kbd_staging, ANDROID_KBD_BUFFER_SIZE);
len = strlen(android_kbd_buffer);
if (android_kbd_size_ptr)
*android_kbd_size_ptr = len;
if (android_kbd_ptr_ptr)
*android_kbd_ptr_ptr = len;
}

finished = android_kbd_finished;
cancel = android_kbd_cancel;
cb = android_kbd_cb;
userdata = android_kbd_userdata;
buffer = android_kbd_buffer;
android_kbd_dirty = false;
android_kbd_finished = false;
slock_unlock(android_kbd_lock);

if (finished)
{
input_driver_state_t *input_st = input_state_get_ptr();

/* Mirror the iOS completion block: fire the callback (NULL line
* on cancel), then release the keyboard line and unblock hotkeys.
* The callback closes the dialog, which hides the soft keyboard
* via menu_input_dialog_end() -> android_keyboard_end(). */
if (cb)
cb(userdata, cancel ? NULL : buffer);

if (input_st)
{
input_keyboard_line_free(input_st);
input_st->flags &= ~INP_FLAG_KB_MAPPING_BLOCKED;
}

/* The callback normally closes the dialog (-> android_keyboard_end),
* which clears our state. If it didn't, drop the now-freed buffer
* pointer so a late JNI callback can't use it after free. */
slock_lock(android_kbd_lock);
if (android_kbd_buffer == buffer)
{
android_kbd_buffer = NULL;
android_kbd_open = false;
android_kbd_dirty = false;
android_kbd_finished = false;
}
slock_unlock(android_kbd_lock);
}
}

static void android_keyboard_free(void)
{
unsigned i, j;
Expand Down Expand Up @@ -1961,6 +2183,9 @@ static void android_input_poll(void *data)
android_input_t *android = (android_input_t*)data;
settings_t *settings = config_get_ptr();

/* Apply any text staged by the native (IME) keyboard. */
android_keyboard_poll();

while ((ident =
ALooper_pollOnce(settings->uints.input_block_timeout,
NULL, NULL, NULL)) >= 0)
Expand Down
24 changes: 24 additions & 0 deletions input/input_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,30 @@ void input_keyboard_line_append(
void input_keyboard_line_clear(input_driver_state_t *input_st);
void input_keyboard_line_free(input_driver_state_t *input_st);

#ifdef ANDROID
/**
* android_keyboard_start:
* @buffer_ptr : Pointer to the keyboard line buffer.
* @size_ptr : Pointer to the keyboard line size.
* @ptr_ptr : Pointer to the keyboard line cursor.
* @label : Hint shown on the keyboard, or NULL.
* @cb : Line complete callback function.
* @userdata : Userdata passed to the callback.
*
* Raises the native Android (IME) keyboard for menu text entry, made to
* mirror the iOS ios_keyboard_* hooks. Swaps the custom on-screen keyboard
* for the system soft keyboard, enabling paste and password managers.
* Implemented in input/drivers/android_input.c.
*
* Returns: true if the keyboard was shown.
**/
bool android_keyboard_start(char **buffer_ptr, size_t *size_ptr,
size_t *ptr_ptr, const char *label,
input_keyboard_line_complete_t cb, void *userdata);
bool android_keyboard_active(void);
void android_keyboard_end(void);
#endif

/**
* input_keyboard_start_line:
* @userdata : Userdata.
Expand Down
6 changes: 6 additions & 0 deletions intl/msg_hash_lbl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1372,6 +1372,12 @@ MSG_HASH(
MENU_ENUM_LABEL_INPUT_SELECT_PHYSICAL_KEYBOARD_STR
)
#endif
#ifdef ANDROID
MSG_HASH(
MENU_ENUM_LABEL_INPUT_ANDROID_SYSTEM_KEYBOARD,
MENU_ENUM_LABEL_INPUT_ANDROID_SYSTEM_KEYBOARD_STR
)
#endif
MSG_HASH(
MENU_ENUM_LABEL_INPUT_SENSOR_ORIENTATION,
MENU_ENUM_LABEL_INPUT_SENSOR_ORIENTATION_STR
Expand Down
10 changes: 10 additions & 0 deletions intl/msg_hash_us.h
Original file line number Diff line number Diff line change
Expand Up @@ -10727,6 +10727,16 @@ MSG_HASH(MENU_ENUM_LABEL_VALUE_##T, us)
#undef S_UINT_AT_EX_H
#undef S_UINT_AT_EX_NS_H
#undef SETTINGS_DEF_STRINGS_PASS
#ifdef ANDROID
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_ANDROID_SYSTEM_KEYBOARD,
"Use System Keyboard"
)
MSG_HASH(
MENU_ENUM_SUBLABEL_INPUT_ANDROID_SYSTEM_KEYBOARD,
"Use the Android system keyboard for menu text entry instead of the built-in on-screen keyboard. Enables clipboard paste and password managers. Requires a touchscreen or an input method that can be navigated with a gamepad."
)
#endif
MSG_HASH(
MENU_ENUM_LABEL_VALUE_INPUT_AUTO_GAME_FOCUS_OFF,
"OFF"
Expand Down
4 changes: 4 additions & 0 deletions menu/cbs/menu_cbs_sublabel.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_nowinkey_enable, MENU_
#ifdef ANDROID
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_select_physical_keyboard, MENU_ENUM_SUBLABEL_INPUT_SELECT_PHYSICAL_KEYBOARD)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_android_input_disconnect_workaround, MENU_ENUM_SUBLABEL_ANDROID_INPUT_DISCONNECT_WORKAROUND)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_android_system_keyboard, MENU_ENUM_SUBLABEL_INPUT_ANDROID_SYSTEM_KEYBOARD)
#endif
#if defined(HAVE_MATERIALUI) || defined(HAVE_XMB) || defined(HAVE_OZONE)
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_menu_screensaver_animation, MENU_ENUM_SUBLABEL_MENU_SCREENSAVER_ANIMATION)
Expand Down Expand Up @@ -3054,6 +3055,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
case MENU_ENUM_LABEL_ANDROID_INPUT_DISCONNECT_WORKAROUND:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_android_input_disconnect_workaround);
break;
case MENU_ENUM_LABEL_INPUT_ANDROID_SYSTEM_KEYBOARD:
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_android_system_keyboard);
break;
#endif
case MENU_ENUM_LABEL_CORE_CHEAT_OPTIONS:
#ifdef HAVE_CHEATS
Expand Down
4 changes: 4 additions & 0 deletions menu/menu_displaylist.c
Original file line number Diff line number Diff line change
Expand Up @@ -8630,6 +8630,10 @@ unsigned menu_displaylist_build_list(
MENU_ENUM_LABEL_INPUT_SELECT_PHYSICAL_KEYBOARD,
PARSE_ACTION, true) == 0)
count++;
if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list,
MENU_ENUM_LABEL_INPUT_ANDROID_SYSTEM_KEYBOARD,
PARSE_ONLY_BOOL, true) == 0)
count++;
#endif

#ifdef HAVE_LIBNX
Expand Down
Loading
Loading