diff options
Diffstat (limited to 'src/common/android/applets/software_keyboard.cpp')
| -rw-r--r-- | src/common/android/applets/software_keyboard.cpp | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/src/common/android/applets/software_keyboard.cpp b/src/common/android/applets/software_keyboard.cpp new file mode 100644 index 000000000..477e62b16 --- /dev/null +++ b/src/common/android/applets/software_keyboard.cpp | |||
| @@ -0,0 +1,277 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <map> | ||
| 5 | #include <thread> | ||
| 6 | |||
| 7 | #include <jni.h> | ||
| 8 | |||
| 9 | #include "common/android/android_common.h" | ||
| 10 | #include "common/android/applets/software_keyboard.h" | ||
| 11 | #include "common/android/id_cache.h" | ||
| 12 | #include "common/logging/log.h" | ||
| 13 | #include "common/string_util.h" | ||
| 14 | #include "core/core.h" | ||
| 15 | |||
| 16 | static jclass s_software_keyboard_class; | ||
| 17 | static jclass s_keyboard_config_class; | ||
| 18 | static jclass s_keyboard_data_class; | ||
| 19 | static jmethodID s_swkbd_execute_normal; | ||
| 20 | static jmethodID s_swkbd_execute_inline; | ||
| 21 | |||
| 22 | namespace Common::Android::SoftwareKeyboard { | ||
| 23 | |||
| 24 | static jobject ToJKeyboardParams(const Core::Frontend::KeyboardInitializeParameters& config) { | ||
| 25 | JNIEnv* env = GetEnvForThread(); | ||
| 26 | jobject object = env->AllocObject(s_keyboard_config_class); | ||
| 27 | |||
| 28 | env->SetObjectField(object, | ||
| 29 | env->GetFieldID(s_keyboard_config_class, "ok_text", "Ljava/lang/String;"), | ||
| 30 | ToJString(env, config.ok_text)); | ||
| 31 | env->SetObjectField( | ||
| 32 | object, env->GetFieldID(s_keyboard_config_class, "header_text", "Ljava/lang/String;"), | ||
| 33 | ToJString(env, config.header_text)); | ||
| 34 | env->SetObjectField(object, | ||
| 35 | env->GetFieldID(s_keyboard_config_class, "sub_text", "Ljava/lang/String;"), | ||
| 36 | ToJString(env, config.sub_text)); | ||
| 37 | env->SetObjectField( | ||
| 38 | object, env->GetFieldID(s_keyboard_config_class, "guide_text", "Ljava/lang/String;"), | ||
| 39 | ToJString(env, config.guide_text)); | ||
| 40 | env->SetObjectField( | ||
| 41 | object, env->GetFieldID(s_keyboard_config_class, "initial_text", "Ljava/lang/String;"), | ||
| 42 | ToJString(env, config.initial_text)); | ||
| 43 | env->SetShortField(object, | ||
| 44 | env->GetFieldID(s_keyboard_config_class, "left_optional_symbol_key", "S"), | ||
| 45 | static_cast<jshort>(config.left_optional_symbol_key)); | ||
| 46 | env->SetShortField(object, | ||
| 47 | env->GetFieldID(s_keyboard_config_class, "right_optional_symbol_key", "S"), | ||
| 48 | static_cast<jshort>(config.right_optional_symbol_key)); | ||
| 49 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "max_text_length", "I"), | ||
| 50 | static_cast<jint>(config.max_text_length)); | ||
| 51 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "min_text_length", "I"), | ||
| 52 | static_cast<jint>(config.min_text_length)); | ||
| 53 | env->SetIntField(object, | ||
| 54 | env->GetFieldID(s_keyboard_config_class, "initial_cursor_position", "I"), | ||
| 55 | static_cast<jint>(config.initial_cursor_position)); | ||
| 56 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "type", "I"), | ||
| 57 | static_cast<jint>(config.type)); | ||
| 58 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "password_mode", "I"), | ||
| 59 | static_cast<jint>(config.password_mode)); | ||
| 60 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "text_draw_type", "I"), | ||
| 61 | static_cast<jint>(config.text_draw_type)); | ||
| 62 | env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "key_disable_flags", "I"), | ||
| 63 | static_cast<jint>(config.key_disable_flags.raw)); | ||
| 64 | env->SetBooleanField(object, | ||
| 65 | env->GetFieldID(s_keyboard_config_class, "use_blur_background", "Z"), | ||
| 66 | static_cast<jboolean>(config.use_blur_background)); | ||
| 67 | env->SetBooleanField(object, | ||
| 68 | env->GetFieldID(s_keyboard_config_class, "enable_backspace_button", "Z"), | ||
| 69 | static_cast<jboolean>(config.enable_backspace_button)); | ||
| 70 | env->SetBooleanField(object, | ||
| 71 | env->GetFieldID(s_keyboard_config_class, "enable_return_button", "Z"), | ||
| 72 | static_cast<jboolean>(config.enable_return_button)); | ||
| 73 | env->SetBooleanField(object, | ||
| 74 | env->GetFieldID(s_keyboard_config_class, "disable_cancel_button", "Z"), | ||
| 75 | static_cast<jboolean>(config.disable_cancel_button)); | ||
| 76 | |||
| 77 | return object; | ||
| 78 | } | ||
| 79 | |||
| 80 | AndroidKeyboard::ResultData AndroidKeyboard::ResultData::CreateFromFrontend(jobject object) { | ||
| 81 | JNIEnv* env = GetEnvForThread(); | ||
| 82 | const jstring string = reinterpret_cast<jstring>(env->GetObjectField( | ||
| 83 | object, env->GetFieldID(s_keyboard_data_class, "text", "Ljava/lang/String;"))); | ||
| 84 | return ResultData{GetJString(env, string), | ||
| 85 | static_cast<Service::AM::Frontend::SwkbdResult>(env->GetIntField( | ||
| 86 | object, env->GetFieldID(s_keyboard_data_class, "result", "I")))}; | ||
| 87 | } | ||
| 88 | |||
| 89 | AndroidKeyboard::~AndroidKeyboard() = default; | ||
| 90 | |||
| 91 | void AndroidKeyboard::InitializeKeyboard( | ||
| 92 | bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters, | ||
| 93 | SubmitNormalCallback submit_normal_callback_, SubmitInlineCallback submit_inline_callback_) { | ||
| 94 | if (is_inline) { | ||
| 95 | LOG_WARNING( | ||
| 96 | Frontend, | ||
| 97 | "(STUBBED) called, backend requested to initialize the inline software keyboard."); | ||
| 98 | |||
| 99 | submit_inline_callback = std::move(submit_inline_callback_); | ||
| 100 | } else { | ||
| 101 | LOG_WARNING( | ||
| 102 | Frontend, | ||
| 103 | "(STUBBED) called, backend requested to initialize the normal software keyboard."); | ||
| 104 | |||
| 105 | submit_normal_callback = std::move(submit_normal_callback_); | ||
| 106 | } | ||
| 107 | |||
| 108 | parameters = std::move(initialize_parameters); | ||
| 109 | |||
| 110 | LOG_INFO(Frontend, | ||
| 111 | "\nKeyboardInitializeParameters:" | ||
| 112 | "\nok_text={}" | ||
| 113 | "\nheader_text={}" | ||
| 114 | "\nsub_text={}" | ||
| 115 | "\nguide_text={}" | ||
| 116 | "\ninitial_text={}" | ||
| 117 | "\nmax_text_length={}" | ||
| 118 | "\nmin_text_length={}" | ||
| 119 | "\ninitial_cursor_position={}" | ||
| 120 | "\ntype={}" | ||
| 121 | "\npassword_mode={}" | ||
| 122 | "\ntext_draw_type={}" | ||
| 123 | "\nkey_disable_flags={}" | ||
| 124 | "\nuse_blur_background={}" | ||
| 125 | "\nenable_backspace_button={}" | ||
| 126 | "\nenable_return_button={}" | ||
| 127 | "\ndisable_cancel_button={}", | ||
| 128 | Common::UTF16ToUTF8(parameters.ok_text), Common::UTF16ToUTF8(parameters.header_text), | ||
| 129 | Common::UTF16ToUTF8(parameters.sub_text), Common::UTF16ToUTF8(parameters.guide_text), | ||
| 130 | Common::UTF16ToUTF8(parameters.initial_text), parameters.max_text_length, | ||
| 131 | parameters.min_text_length, parameters.initial_cursor_position, parameters.type, | ||
| 132 | parameters.password_mode, parameters.text_draw_type, parameters.key_disable_flags.raw, | ||
| 133 | parameters.use_blur_background, parameters.enable_backspace_button, | ||
| 134 | parameters.enable_return_button, parameters.disable_cancel_button); | ||
| 135 | } | ||
| 136 | |||
| 137 | void AndroidKeyboard::ShowNormalKeyboard() const { | ||
| 138 | LOG_DEBUG(Frontend, "called, backend requested to show the normal software keyboard."); | ||
| 139 | |||
| 140 | ResultData data{}; | ||
| 141 | |||
| 142 | // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber. | ||
| 143 | std::thread([&] { | ||
| 144 | data = ResultData::CreateFromFrontend(GetEnvForThread()->CallStaticObjectMethod( | ||
| 145 | s_software_keyboard_class, s_swkbd_execute_normal, ToJKeyboardParams(parameters))); | ||
| 146 | }).join(); | ||
| 147 | |||
| 148 | SubmitNormalText(data); | ||
| 149 | } | ||
| 150 | |||
| 151 | void AndroidKeyboard::ShowTextCheckDialog( | ||
| 152 | Service::AM::Frontend::SwkbdTextCheckResult text_check_result, | ||
| 153 | std::u16string text_check_message) const { | ||
| 154 | LOG_WARNING(Frontend, "(STUBBED) called, backend requested to show the text check dialog."); | ||
| 155 | } | ||
| 156 | |||
| 157 | void AndroidKeyboard::ShowInlineKeyboard( | ||
| 158 | Core::Frontend::InlineAppearParameters appear_parameters) const { | ||
| 159 | LOG_WARNING(Frontend, | ||
| 160 | "(STUBBED) called, backend requested to show the inline software keyboard."); | ||
| 161 | |||
| 162 | LOG_INFO(Frontend, | ||
| 163 | "\nInlineAppearParameters:" | ||
| 164 | "\nmax_text_length={}" | ||
| 165 | "\nmin_text_length={}" | ||
| 166 | "\nkey_top_scale_x={}" | ||
| 167 | "\nkey_top_scale_y={}" | ||
| 168 | "\nkey_top_translate_x={}" | ||
| 169 | "\nkey_top_translate_y={}" | ||
| 170 | "\ntype={}" | ||
| 171 | "\nkey_disable_flags={}" | ||
| 172 | "\nkey_top_as_floating={}" | ||
| 173 | "\nenable_backspace_button={}" | ||
| 174 | "\nenable_return_button={}" | ||
| 175 | "\ndisable_cancel_button={}", | ||
| 176 | appear_parameters.max_text_length, appear_parameters.min_text_length, | ||
| 177 | appear_parameters.key_top_scale_x, appear_parameters.key_top_scale_y, | ||
| 178 | appear_parameters.key_top_translate_x, appear_parameters.key_top_translate_y, | ||
| 179 | appear_parameters.type, appear_parameters.key_disable_flags.raw, | ||
| 180 | appear_parameters.key_top_as_floating, appear_parameters.enable_backspace_button, | ||
| 181 | appear_parameters.enable_return_button, appear_parameters.disable_cancel_button); | ||
| 182 | |||
| 183 | // Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber. | ||
| 184 | m_is_inline_active = true; | ||
| 185 | std::thread([&] { | ||
| 186 | GetEnvForThread()->CallStaticVoidMethod(s_software_keyboard_class, s_swkbd_execute_inline, | ||
| 187 | ToJKeyboardParams(parameters)); | ||
| 188 | }).join(); | ||
| 189 | } | ||
| 190 | |||
| 191 | void AndroidKeyboard::HideInlineKeyboard() const { | ||
| 192 | LOG_WARNING(Frontend, | ||
| 193 | "(STUBBED) called, backend requested to hide the inline software keyboard."); | ||
| 194 | } | ||
| 195 | |||
| 196 | void AndroidKeyboard::InlineTextChanged( | ||
| 197 | Core::Frontend::InlineTextParameters text_parameters) const { | ||
| 198 | LOG_WARNING(Frontend, | ||
| 199 | "(STUBBED) called, backend requested to change the inline keyboard text."); | ||
| 200 | |||
| 201 | LOG_INFO(Frontend, | ||
| 202 | "\nInlineTextParameters:" | ||
| 203 | "\ninput_text={}" | ||
| 204 | "\ncursor_position={}", | ||
| 205 | Common::UTF16ToUTF8(text_parameters.input_text), text_parameters.cursor_position); | ||
| 206 | |||
| 207 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, | ||
| 208 | text_parameters.input_text, text_parameters.cursor_position); | ||
| 209 | } | ||
| 210 | |||
| 211 | void AndroidKeyboard::ExitKeyboard() const { | ||
| 212 | LOG_WARNING(Frontend, "(STUBBED) called, backend requested to exit the software keyboard."); | ||
| 213 | } | ||
| 214 | |||
| 215 | void AndroidKeyboard::SubmitInlineKeyboardText(std::u16string submitted_text) { | ||
| 216 | if (!m_is_inline_active) { | ||
| 217 | return; | ||
| 218 | } | ||
| 219 | |||
| 220 | m_current_text += submitted_text; | ||
| 221 | |||
| 222 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, | ||
| 223 | static_cast<int>(m_current_text.size())); | ||
| 224 | } | ||
| 225 | |||
| 226 | void AndroidKeyboard::SubmitInlineKeyboardInput(int key_code) { | ||
| 227 | static constexpr int KEYCODE_BACK = 4; | ||
| 228 | static constexpr int KEYCODE_ENTER = 66; | ||
| 229 | static constexpr int KEYCODE_DEL = 67; | ||
| 230 | |||
| 231 | if (!m_is_inline_active) { | ||
| 232 | return; | ||
| 233 | } | ||
| 234 | |||
| 235 | switch (key_code) { | ||
| 236 | case KEYCODE_BACK: | ||
| 237 | case KEYCODE_ENTER: | ||
| 238 | m_is_inline_active = false; | ||
| 239 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::DecidedEnter, m_current_text, | ||
| 240 | static_cast<s32>(m_current_text.size())); | ||
| 241 | break; | ||
| 242 | case KEYCODE_DEL: | ||
| 243 | m_current_text.pop_back(); | ||
| 244 | submit_inline_callback(Service::AM::Frontend::SwkbdReplyType::ChangedString, m_current_text, | ||
| 245 | static_cast<int>(m_current_text.size())); | ||
| 246 | break; | ||
| 247 | } | ||
| 248 | } | ||
| 249 | |||
| 250 | void AndroidKeyboard::SubmitNormalText(const ResultData& data) const { | ||
| 251 | submit_normal_callback(data.result, Common::UTF8ToUTF16(data.text), true); | ||
| 252 | } | ||
| 253 | |||
| 254 | void InitJNI(JNIEnv* env) { | ||
| 255 | s_software_keyboard_class = reinterpret_cast<jclass>( | ||
| 256 | env->NewGlobalRef(env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard"))); | ||
| 257 | s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef( | ||
| 258 | env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig"))); | ||
| 259 | s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef( | ||
| 260 | env->FindClass("org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardData"))); | ||
| 261 | |||
| 262 | s_swkbd_execute_normal = env->GetStaticMethodID( | ||
| 263 | s_software_keyboard_class, "executeNormal", | ||
| 264 | "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/" | ||
| 265 | "applets/keyboard/SoftwareKeyboard$KeyboardData;"); | ||
| 266 | s_swkbd_execute_inline = env->GetStaticMethodID( | ||
| 267 | s_software_keyboard_class, "executeInline", | ||
| 268 | "(Lorg/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard$KeyboardConfig;)V"); | ||
| 269 | } | ||
| 270 | |||
| 271 | void CleanupJNI(JNIEnv* env) { | ||
| 272 | env->DeleteGlobalRef(s_software_keyboard_class); | ||
| 273 | env->DeleteGlobalRef(s_keyboard_config_class); | ||
| 274 | env->DeleteGlobalRef(s_keyboard_data_class); | ||
| 275 | } | ||
| 276 | |||
| 277 | } // namespace Common::Android::SoftwareKeyboard | ||