| | |
| | |
| |
|
| | #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); |
| | } |
| |
|
| | } |
| |
|