12 changed files with 625 additions and 152 deletions
-
12src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java
-
25src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
-
250src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/SoftwareKeyboard.java
-
4src/android/app/src/main/jni/CMakeLists.txt
-
35src/android/app/src/main/jni/android_common/android_common.cpp
-
12src/android/app/src/main/jni/android_common/android_common.h
-
277src/android/app/src/main/jni/applets/software_keyboard.cpp
-
78src/android/app/src/main/jni/applets/software_keyboard.h
-
7src/android/app/src/main/jni/id_cache.cpp
-
62src/android/app/src/main/jni/native.cpp
-
6src/android/app/src/main/jni/native.h
-
5src/android/app/src/main/res/values/strings.xml
@ -0,0 +1,35 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include "jni/android_common/android_common.h"
|
|||
|
|||
#include <string>
|
|||
#include <string_view>
|
|||
|
|||
#include <jni.h>
|
|||
|
|||
#include "common/string_util.h"
|
|||
|
|||
std::string GetJString(JNIEnv* env, jstring jstr) { |
|||
if (!jstr) { |
|||
return {}; |
|||
} |
|||
|
|||
const jchar* jchars = env->GetStringChars(jstr, nullptr); |
|||
const jsize length = env->GetStringLength(jstr); |
|||
const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); |
|||
const std::string converted_string = Common::UTF16ToUTF8(string_view); |
|||
env->ReleaseStringChars(jstr, jchars); |
|||
|
|||
return converted_string; |
|||
} |
|||
|
|||
jstring ToJString(JNIEnv* env, std::string_view str) { |
|||
const std::u16string converted_string = Common::UTF8ToUTF16(str); |
|||
return env->NewString(reinterpret_cast<const jchar*>(converted_string.data()), |
|||
static_cast<jint>(converted_string.size())); |
|||
} |
|||
|
|||
jstring ToJString(JNIEnv* env, std::u16string_view str) { |
|||
return ToJString(env, Common::UTF16ToUTF8(str)); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
|
|||
#include <jni.h> |
|||
|
|||
std::string GetJString(JNIEnv* env, jstring jstr); |
|||
jstring ToJString(JNIEnv* env, std::string_view str); |
|||
jstring ToJString(JNIEnv* env, std::u16string_view str); |
|||
@ -0,0 +1,277 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include <map>
|
|||
#include <thread>
|
|||
|
|||
#include <jni.h>
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "common/string_util.h"
|
|||
#include "core/core.h"
|
|||
#include "jni/android_common/android_common.h"
|
|||
#include "jni/applets/software_keyboard.h"
|
|||
#include "jni/id_cache.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 SoftwareKeyboard { |
|||
|
|||
static jobject ToJKeyboardParams(const Core::Frontend::KeyboardInitializeParameters& config) { |
|||
JNIEnv* env = IDCache::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 = IDCache::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::Applets::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{}; |
|||
|
|||
// Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber.
|
|||
std::thread([&] { |
|||
data = ResultData::CreateFromFrontend(IDCache::GetEnvForThread()->CallStaticObjectMethod( |
|||
s_software_keyboard_class, s_swkbd_execute_normal, ToJKeyboardParams(parameters))); |
|||
}).join(); |
|||
|
|||
SubmitNormalText(data); |
|||
} |
|||
|
|||
void AndroidKeyboard::ShowTextCheckDialog( |
|||
Service::AM::Applets::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); |
|||
|
|||
// Pivot to a new thread, as we cannot call GetEnvForThread() from a Fiber.
|
|||
m_is_inline_active = true; |
|||
std::thread([&] { |
|||
IDCache::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::Applets::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::Applets::SwkbdReplyType::ChangedString, m_current_text, |
|||
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::Applets::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::Applets::SwkbdReplyType::ChangedString, m_current_text, |
|||
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/SoftwareKeyboard"))); |
|||
s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef( |
|||
env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig"))); |
|||
s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef( |
|||
env->FindClass("org/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardData"))); |
|||
|
|||
s_swkbd_execute_normal = env->GetStaticMethodID( |
|||
s_software_keyboard_class, "ExecuteNormal", |
|||
"(Lorg/yuzu/yuzu_emu/applets/SoftwareKeyboard$KeyboardConfig;)Lorg/yuzu/yuzu_emu/" |
|||
"applets/SoftwareKeyboard$KeyboardData;"); |
|||
s_swkbd_execute_inline = |
|||
env->GetStaticMethodID(s_software_keyboard_class, "ExecuteInline", |
|||
"(Lorg/yuzu/yuzu_emu/applets/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); |
|||
} |
|||
|
|||
} // namespace SoftwareKeyboard
|
|||
@ -0,0 +1,78 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <jni.h> |
|||
|
|||
#include "core/frontend/applets/software_keyboard.h" |
|||
|
|||
namespace SoftwareKeyboard { |
|||
|
|||
class AndroidKeyboard final : public Core::Frontend::SoftwareKeyboardApplet { |
|||
public: |
|||
~AndroidKeyboard() override; |
|||
|
|||
void Close() const override { |
|||
ExitKeyboard(); |
|||
} |
|||
|
|||
void InitializeKeyboard(bool is_inline, |
|||
Core::Frontend::KeyboardInitializeParameters initialize_parameters, |
|||
SubmitNormalCallback submit_normal_callback_, |
|||
SubmitInlineCallback submit_inline_callback_) override; |
|||
|
|||
void ShowNormalKeyboard() const override; |
|||
|
|||
void ShowTextCheckDialog(Service::AM::Applets::SwkbdTextCheckResult text_check_result, |
|||
std::u16string text_check_message) const override; |
|||
|
|||
void ShowInlineKeyboard( |
|||
Core::Frontend::InlineAppearParameters appear_parameters) const override; |
|||
|
|||
void HideInlineKeyboard() const override; |
|||
|
|||
void InlineTextChanged(Core::Frontend::InlineTextParameters text_parameters) const override; |
|||
|
|||
void ExitKeyboard() const override; |
|||
|
|||
void SubmitInlineKeyboardText(std::u16string submitted_text); |
|||
|
|||
void SubmitInlineKeyboardInput(int key_code); |
|||
|
|||
private: |
|||
struct ResultData { |
|||
static ResultData CreateFromFrontend(jobject object); |
|||
|
|||
std::string text; |
|||
Service::AM::Applets::SwkbdResult result{}; |
|||
}; |
|||
|
|||
void SubmitNormalText(const ResultData& result) const; |
|||
|
|||
Core::Frontend::KeyboardInitializeParameters parameters{}; |
|||
|
|||
mutable SubmitNormalCallback submit_normal_callback; |
|||
mutable SubmitInlineCallback submit_inline_callback; |
|||
|
|||
private: |
|||
mutable bool m_is_inline_active{}; |
|||
std::u16string m_current_text; |
|||
}; |
|||
|
|||
// Should be called in JNI_Load |
|||
void InitJNI(JNIEnv* env); |
|||
|
|||
// Should be called in JNI_Unload |
|||
void CleanupJNI(JNIEnv* env); |
|||
|
|||
} // namespace SoftwareKeyboard |
|||
|
|||
// Native function calls |
|||
extern "C" { |
|||
JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateFilters( |
|||
JNIEnv* env, jclass clazz, jstring text); |
|||
|
|||
JNIEXPORT jobject JNICALL Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateInput( |
|||
JNIEnv* env, jclass clazz, jstring text); |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue