| |
| |
|
|
| #include <map> |
| #include <thread> |
|
|
| #include <jni.h> |
|
|
| #include "common/android/android_common.h" |
| #include "common/android/applets/software_keyboard.h" |
| #include "common/android/id_cache.h" |
| #include "common/logging/log.h" |
| #include "common/string_util.h" |
| #include "core/core.h" |
|
|
| static jclass s_software_keyboard_class; |
| static jclass s_keyboard_config_class; |
| static jclass s_keyboard_data_class; |
| static jmethodID s_swkbd_execute_normal; |
| static jmethodID s_swkbd_execute_inline; |
|
|
| namespace Common::Android::SoftwareKeyboard { |
|
|
| static jobject ToJKeyboardParams(const Core::Frontend::KeyboardInitializeParameters& config) { |
| JNIEnv* env = GetEnvForThread(); |
| jobject object = env->AllocObject(s_keyboard_config_class); |
|
|
| env->SetObjectField(object, |
| env->GetFieldID(s_keyboard_config_class, "ok_text", "Ljava/lang/String;"), |
| ToJString(env, config.ok_text)); |
| env->SetObjectField( |
| object, env->GetFieldID(s_keyboard_config_class, "header_text", "Ljava/lang/String;"), |
| ToJString(env, config.header_text)); |
| env->SetObjectField(object, |
| env->GetFieldID(s_keyboard_config_class, "sub_text", "Ljava/lang/String;"), |
| ToJString(env, config.sub_text)); |
| env->SetObjectField( |
| object, env->GetFieldID(s_keyboard_config_class, "guide_text", "Ljava/lang/String;"), |
| ToJString(env, config.guide_text)); |
| env->SetObjectField( |
| object, env->GetFieldID(s_keyboard_config_class, "initial_text", "Ljava/lang/String;"), |
| ToJString(env, config.initial_text)); |
| env->SetShortField(object, |
| env->GetFieldID(s_keyboard_config_class, "left_optional_symbol_key", "S"), |
| static_cast<jshort>(config.left_optional_symbol_key)); |
| env->SetShortField(object, |
| env->GetFieldID(s_keyboard_config_class, "right_optional_symbol_key", "S"), |
| static_cast<jshort>(config.right_optional_symbol_key)); |
| env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "max_text_length", "I"), |
| static_cast<jint>(config.max_text_length)); |
| env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "min_text_length", "I"), |
| static_cast<jint>(config.min_text_length)); |
| env->SetIntField(object, |
| env->GetFieldID(s_keyboard_config_class, "initial_cursor_position", "I"), |
| static_cast<jint>(config.initial_cursor_position)); |
| env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "type", "I"), |
| static_cast<jint>(config.type)); |
| env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "password_mode", "I"), |
| static_cast<jint>(config.password_mode)); |
| env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "text_draw_type", "I"), |
| static_cast<jint>(config.text_draw_type)); |
| env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "key_disable_flags", "I"), |
| static_cast<jint>(config.key_disable_flags.raw)); |
| env->SetBooleanField(object, |
| env->GetFieldID(s_keyboard_config_class, "use_blur_background", "Z"), |
| static_cast<jboolean>(config.use_blur_background)); |
| env->SetBooleanField(object, |
| env->GetFieldID(s_keyboard_config_class, "enable_backspace_button", "Z"), |
| static_cast<jboolean>(config.enable_backspace_button)); |
| env->SetBooleanField(object, |
| env->GetFieldID(s_keyboard_config_class, "enable_return_button", "Z"), |
| static_cast<jboolean>(config.enable_return_button)); |
| env->SetBooleanField(object, |
| env->GetFieldID(s_keyboard_config_class, "disable_cancel_button", "Z"), |
| static_cast<jboolean>(config.disable_cancel_button)); |
|
|
| return object; |
| } |
|
|
| AndroidKeyboard::ResultData AndroidKeyboard::ResultData::CreateFromFrontend(jobject object) { |
| JNIEnv* env = GetEnvForThread(); |
| const jstring string = reinterpret_cast<jstring>(env->GetObjectField( |
| object, env->GetFieldID(s_keyboard_data_class, "text", "Ljava/lang/String;"))); |
| return ResultData{GetJString(env, string), |
| static_cast<Service::AM::Frontend::SwkbdResult>(env->GetIntField( |
| object, env->GetFieldID(s_keyboard_data_class, "result", "I")))}; |
| } |
|
|
| AndroidKeyboard::~AndroidKeyboard() = default; |
|
|
| void AndroidKeyboard::InitializeKeyboard( |
| bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, |
| SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) { |
| if (is_inline) { |
| LOG_WARNING( |
| Frontend, |
| "(STUBBED) called, backend requested to initialize the inline software keyboard."); |
|
|
| submit_inline_callback = std::move(submit_inline_callback_); |
| } else { |
| LOG_WARNING( |
| Frontend, |
| "(STUBBED) called, backend requested to initialize the normal software keyboard."); |
|
|
| submit_normal_callback = std::move(submit_normal_callback_); |
| } |
|
|
| parameters = std::move(initialize_parameters); |
|
|
| LOG_INFO(Frontend, |
| "\nKeyboardInitializeParameters:" |
| "\nok_text={}" |
| "\nheader_text={}" |
| "\nsub_text={}" |
| "\nguide_text={}" |
| "\ninitial_text={}" |
| "\nmax_text_length={}" |
| "\nmin_text_length={}" |
| "\ninitial_cursor_position={}" |
| "\ntype={}" |
| "\npassword_mode={}" |
| "\ntext_draw_type={}" |
| "\nkey_disable_flags={}" |
| "\nuse_blur_background={}" |
| "\nenable_backspace_button={}" |
| "\nenable_return_button={}" |
| "\ndisable_cancel_button={}", |
| Common::UTF16ToUTF8(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text), |
| Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text), |
| Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length, |
| parameters.min_text_length, parameters.initial_cursor_position, parameters.type, |
| parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw, |
| parameters.use_blur_background, parameters.enable_backspace_button, |
| parameters.enable_return_button, parameters.disable_cancel_button); |
| } |
|
|
| void AndroidKeyboard::ShowNormalKeyboard() const { |
| LOG_DEBUG(Frontend, "called, backend requested to show the normal software keyboard."); |
|
|
| ResultData data{}; |
|
|
| |
| std::thread([&] { |
| data = ResultData::CreateFromFrontend(GetEnvForThread()->CallStaticObjectMethod( |
| s_software_keyboard_class, s_swkbd_execute_normal, ToJKeyboardParams(parameters))); |
| }).join(); |
|
|
| SubmitNormalText(data); |
| } |
|
|
| void AndroidKeyboard::ShowTextCheckDialog( |
| Service::AM::Frontend::SwkbdTextCheckResult text_check_result, |
| std::u16string text_check_message) const { |
| LOG_WARNING(Frontend, "(STUBBED) called, backend requested to show the text check dialog."); |
| } |
|
|
| void AndroidKeyboard::ShowInlineKeyboard( |
| Core::Frontend::InlineAppearParameters appear_parameters) const { |
| LOG_WARNING(Frontend, |
| "(STUBBED) called, backend requested to show the inline software keyboard."); |
|
|
| LOG_INFO(Frontend, |
| "\nInlineAppearParameters:" |
| "\nmax_text_length={}" |
| "\nmin_text_length={}" |
| "\nkey_top_scale_x={}" |
| "\nkey_top_scale_y={}" |
| "\nkey_top_translate_x={}" |
| "\nkey_top_translate_y={}" |
| "\ntype={}" |
| "\nkey_disable_flags={}" |
| "\nkey_top_as_floating={}" |
| "\nenable_backspace_button={}" |
| "\nenable_return_button={}" |
| "\ndisable_cancel_button={}", |
| appear_parameters.max_text_length, appear_parameters.min_text_length, |
| appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, |
| appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, |
| appear_parameters.type, appear_parameters.key_disable_flags.raw, |
| appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, |
| appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); |
|
|
| |
| m_is_inline_active = true; |
| std::thread([&] { |
| GetEnvForThread()->CallStaticVoidMethod(s_software_keyboard_class, s_swkbd_execute_inline, |
| ToJKeyboardParams(parameters)); |
| }).join(); |
| } |
|
|
| void AndroidKeyboard::HideInlineKeyboard() const { |
| LOG_WARNING(Frontend, |
| "(STUBBED) called, backend requested to hide the inline software keyboard."); |
| } |
|
|
| void AndroidKeyboard::InlineTextChanged( |
| Core::Frontend::InlineTextParameters text_parameters) const { |
| LOG_WARNING(Frontend, |
| "(STUBBED) called, backend requested to change the inline keyboard text."); |
|
|
| LOG_INFO(Frontend, |
| "\nInlineTextParameters:" |
| "\ninput_text={}" |
| "\ncursor_position={}", |
| Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); |
|
|
| submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, |
| text_parameters.input_text, text_parameters.cursor_position); |
| } |
|
|
| void AndroidKeyboard::ExitKeyboard() const { |
| LOG_WARNING(Frontend, "(STUBBED) called, backend requested to exit the software keyboard."); |
| } |
|
|
| void AndroidKeyboard::SubmitInlineKeyboardText(std::u16string submitted_text) { |
| if (!m_is_inline_active) { |
| return; |
| } |
|
|
| m_current_text += submitted_text; |
|
|
| submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, |
| static_cast<int>(m_current_text.size())); |
| } |
|
|
| void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) { |
| static constexpr int KEYCODE_BACK = 4; |
| static constexpr int KEYCODE_ENTER = 66; |
| static constexpr int KEYCODE_DEL = 67; |
|
|
| if (!m_is_inline_active) { |
| return; |
| } |
|
|
| switch (key_code) { |
| case KEYCODE_BACK: |
| case KEYCODE_ENTER: |
| m_is_inline_active = false; |
| submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::DecidedEnter, m_current_text, |
| static_cast<s32>(m_current_text.size())); |
| break; |
| case KEYCODE_DEL: |
| m_current_text.pop_back(); |
| submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, |
| static_cast<int>(m_current_text.size())); |
| break; |
| } |
| } |
|
|
| void AndroidKeyboard::SubmitNormalText(const ResultData& data) const { |
| submit_normal_callback(data.result, Common::UTF8ToUTF16(data.text), true); |
| } |
|
|
| void InitJNI(JNIEnv* env) { |
| s_software_keyboard_class = reinterpret_cast<jclass>( |
| env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard"))); |
| s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef( |
| env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig"))); |
| s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef( |
| env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData"))); |
|
|
| s_swkbd_execute_normal = env->GetStaticMethodID( |
| s_software_keyboard_class, "executeNormal", |
| "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/" |
| "applets/keyboard/SoftwareKeyboard$KeyboardData;"); |
| s_swkbd_execute_inline = env->GetStaticMethodID( |
| s_software_keyboard_class, "executeInline", |
| "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V"); |
| } |
|
|
| void CleanupJNI(JNIEnv* env) { |
| env->DeleteGlobalRef(s_software_keyboard_class); |
| env->DeleteGlobalRef(s_keyboard_config_class); |
| env->DeleteGlobalRef(s_keyboard_data_class); |
| } |
|
|
| } |
|
|