android: SoftwareKeyboard implementation
This commit is contained in:
parent
b521a57f2f
commit
042cca532a
@ -0,0 +1,230 @@
|
|||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
package org.citra.citra_emu.applets;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.text.InputFilter;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import org.citra.citra_emu.CitraApplication;
|
||||||
|
import org.citra.citra_emu.NativeLibrary;
|
||||||
|
import org.citra.citra_emu.R;
|
||||||
|
import org.citra.citra_emu.activities.EmulationActivity;
|
||||||
|
import org.citra.citra_emu.utils.Log;
|
||||||
|
|
||||||
|
public final class SoftwareKeyboard {
|
||||||
|
/// Corresponds to Frontend::ButtonConfig
|
||||||
|
private interface ButtonConfig {
|
||||||
|
int Single = 0; /// Ok button
|
||||||
|
int Dual = 1; /// Cancel | Ok buttons
|
||||||
|
int Triple = 2; /// Cancel | I Forgot | Ok buttons
|
||||||
|
int None = 3; /// No button (returned by swkbdInputText in special cases)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Corresponds to Frontend::ValidationError
|
||||||
|
public enum ValidationError {
|
||||||
|
None,
|
||||||
|
// Button Selection
|
||||||
|
ButtonOutOfRange,
|
||||||
|
// Configured Filters
|
||||||
|
MaxDigitsExceeded,
|
||||||
|
AtSignNotAllowed,
|
||||||
|
PercentNotAllowed,
|
||||||
|
BackslashNotAllowed,
|
||||||
|
ProfanityNotAllowed,
|
||||||
|
CallbackFailed,
|
||||||
|
// Allowed Input Type
|
||||||
|
FixedLengthRequired,
|
||||||
|
MaxLengthExceeded,
|
||||||
|
BlankInputNotAllowed,
|
||||||
|
EmptyInputNotAllowed,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class KeyboardConfig {
|
||||||
|
public int button_config;
|
||||||
|
public int max_text_length;
|
||||||
|
public boolean multiline_mode; /// True if the keyboard accepts multiple lines of input
|
||||||
|
public String hint_text; /// Displayed in the field as a hint before
|
||||||
|
@Nullable public String[] button_text; /// Contains the button text that the caller provides
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Corresponds to Frontend::KeyboardData
|
||||||
|
public static class KeyboardData {
|
||||||
|
public int button;
|
||||||
|
public String text;
|
||||||
|
|
||||||
|
private KeyboardData(int button, String text) {
|
||||||
|
this.button = button;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Filter implements InputFilter {
|
||||||
|
@Override
|
||||||
|
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
|
||||||
|
int dstart, int dend) {
|
||||||
|
String text = new StringBuilder(dest)
|
||||||
|
.replace(dstart, dend, source.subSequence(start, end).toString())
|
||||||
|
.toString();
|
||||||
|
if (ValidateFilters(text) == ValidationError.None) {
|
||||||
|
return null; // Accept replacement
|
||||||
|
}
|
||||||
|
return dest.subSequence(dstart, dend); // Request the subsequence to be unchanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyboardData data;
|
||||||
|
private static final Object finishLock = new Object();
|
||||||
|
|
||||||
|
private static void ExecuteImpl(KeyboardConfig config) {
|
||||||
|
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
||||||
|
|
||||||
|
data = new KeyboardData(0, "");
|
||||||
|
|
||||||
|
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
|
params.leftMargin = params.rightMargin =
|
||||||
|
CitraApplication.getAppContext().getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.dialog_margin);
|
||||||
|
|
||||||
|
// Set up the input
|
||||||
|
EditText editText = new EditText(CitraApplication.getAppContext());
|
||||||
|
editText.setHint(config.hint_text);
|
||||||
|
editText.setSingleLine(!config.multiline_mode);
|
||||||
|
editText.setLayoutParams(params);
|
||||||
|
editText.setFilters(
|
||||||
|
new InputFilter[] {new Filter(), new InputFilter.LengthFilter(config.max_text_length)});
|
||||||
|
|
||||||
|
FrameLayout container = new FrameLayout(emulationActivity);
|
||||||
|
container.addView(editText);
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity)
|
||||||
|
.setTitle(R.string.software_keyboard)
|
||||||
|
.setView(container)
|
||||||
|
.setCancelable(false);
|
||||||
|
|
||||||
|
switch (config.button_config) {
|
||||||
|
case ButtonConfig.Triple: {
|
||||||
|
final String text = config.button_text == null
|
||||||
|
? emulationActivity.getString(R.string.i_forgot)
|
||||||
|
: config.button_text[1];
|
||||||
|
builder.setNeutralButton(text, null);
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case ButtonConfig.Dual: {
|
||||||
|
final String text = config.button_text == null
|
||||||
|
? emulationActivity.getString(android.R.string.cancel)
|
||||||
|
: config.button_text[0];
|
||||||
|
builder.setNegativeButton(text, null);
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case ButtonConfig.Single: {
|
||||||
|
final String text = config.button_text == null
|
||||||
|
? emulationActivity.getString(android.R.string.ok)
|
||||||
|
: config.button_text[config.button_config];
|
||||||
|
builder.setPositiveButton(text, null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final AlertDialog dialog = builder.create();
|
||||||
|
dialog.show();
|
||||||
|
if (dialog.getButton(DialogInterface.BUTTON_POSITIVE) != null) {
|
||||||
|
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener((view) -> {
|
||||||
|
data.button = config.button_config;
|
||||||
|
data.text = editText.getText().toString();
|
||||||
|
|
||||||
|
final ValidationError error = ValidateInput(data.text);
|
||||||
|
if (error != ValidationError.None) {
|
||||||
|
HandleValidationError(config, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
synchronized (finishLock) {
|
||||||
|
finishLock.notifyAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (dialog.getButton(DialogInterface.BUTTON_NEUTRAL) != null) {
|
||||||
|
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener((view) -> {
|
||||||
|
data.button = 1;
|
||||||
|
dialog.dismiss();
|
||||||
|
synchronized (finishLock) {
|
||||||
|
finishLock.notifyAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (dialog.getButton(DialogInterface.BUTTON_NEGATIVE) != null) {
|
||||||
|
dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((view) -> {
|
||||||
|
data.button = 0;
|
||||||
|
dialog.dismiss();
|
||||||
|
synchronized (finishLock) {
|
||||||
|
finishLock.notifyAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleValidationError(KeyboardConfig config, ValidationError error) {
|
||||||
|
final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get();
|
||||||
|
String message = "";
|
||||||
|
switch (error) {
|
||||||
|
case FixedLengthRequired:
|
||||||
|
message =
|
||||||
|
emulationActivity.getString(R.string.fixed_length_required, config.max_text_length);
|
||||||
|
break;
|
||||||
|
case MaxLengthExceeded:
|
||||||
|
message =
|
||||||
|
emulationActivity.getString(R.string.max_length_exceeded, config.max_text_length);
|
||||||
|
break;
|
||||||
|
case BlankInputNotAllowed:
|
||||||
|
message = emulationActivity.getString(R.string.blank_input_not_allowed);
|
||||||
|
break;
|
||||||
|
case EmptyInputNotAllowed:
|
||||||
|
message = emulationActivity.getString(R.string.empty_input_not_allowed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
new AlertDialog.Builder(emulationActivity)
|
||||||
|
.setTitle(R.string.software_keyboard)
|
||||||
|
.setMessage(message)
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeyboardData Execute(KeyboardConfig config) {
|
||||||
|
if (config.button_config == ButtonConfig.None) {
|
||||||
|
Log.error("Unexpected button config None");
|
||||||
|
return new KeyboardData(0, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> ExecuteImpl(config));
|
||||||
|
|
||||||
|
synchronized (finishLock) {
|
||||||
|
try {
|
||||||
|
finishLock.wait();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ShowError(String error) {
|
||||||
|
NativeLibrary.displayAlertMsg(
|
||||||
|
CitraApplication.getAppContext().getResources().getString(R.string.software_keyboard),
|
||||||
|
error, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native ValidationError ValidateFilters(String text);
|
||||||
|
private static native ValidationError ValidateInput(String text);
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
add_library(main SHARED
|
add_library(main SHARED
|
||||||
|
applets/swkbd.cpp
|
||||||
|
applets/swkbd.h
|
||||||
button_manager.cpp
|
button_manager.cpp
|
||||||
button_manager.h
|
button_manager.h
|
||||||
config.cpp
|
config.cpp
|
||||||
|
153
src/android/app/src/main/jni/applets/swkbd.cpp
Normal file
153
src/android/app/src/main/jni/applets/swkbd.cpp
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <jni.h>
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "jni/applets/swkbd.h"
|
||||||
|
#include "jni/id_cache.h"
|
||||||
|
|
||||||
|
static std::string GetJString(JNIEnv* env, jstring jstr) {
|
||||||
|
if (!jstr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* s = env->GetStringUTFChars(jstr, nullptr);
|
||||||
|
std::string result = s;
|
||||||
|
env->ReleaseStringUTFChars(jstr, s);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static jclass s_software_keyboard_class;
|
||||||
|
static jclass s_keyboard_config_class;
|
||||||
|
static jclass s_keyboard_data_class;
|
||||||
|
static jclass s_validation_error_class;
|
||||||
|
static jmethodID s_swkbd_execute;
|
||||||
|
static jmethodID s_swkbd_show_error;
|
||||||
|
|
||||||
|
namespace SoftwareKeyboard {
|
||||||
|
|
||||||
|
static jobject ToJavaKeyboardConfig(const Frontend::KeyboardConfig& config) {
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
jobject object = env->AllocObject(s_keyboard_config_class);
|
||||||
|
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "button_config", "I"),
|
||||||
|
static_cast<jint>(config.button_config));
|
||||||
|
env->SetIntField(object, env->GetFieldID(s_keyboard_config_class, "max_text_length", "I"),
|
||||||
|
static_cast<jint>(config.max_text_length));
|
||||||
|
env->SetBooleanField(object, env->GetFieldID(s_keyboard_config_class, "multiline_mode", "Z"),
|
||||||
|
static_cast<jboolean>(config.multiline_mode));
|
||||||
|
env->SetObjectField(object,
|
||||||
|
env->GetFieldID(s_keyboard_config_class, "hint_text", "Ljava/lang/String;"),
|
||||||
|
env->NewStringUTF(config.hint_text.c_str()));
|
||||||
|
if (config.has_custom_button_text) {
|
||||||
|
const jclass string_class =
|
||||||
|
reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("java/lang/String")));
|
||||||
|
const jobjectArray array =
|
||||||
|
env->NewObjectArray(static_cast<jsize>(config.button_text.size()), string_class,
|
||||||
|
env->NewStringUTF(config.button_text[0].c_str()));
|
||||||
|
for (std::size_t i = 1; i < config.button_text.size(); ++i) {
|
||||||
|
env->SetObjectArrayElement(array, static_cast<jsize>(i),
|
||||||
|
env->NewStringUTF(config.button_text[i].c_str()));
|
||||||
|
}
|
||||||
|
env->SetObjectField(
|
||||||
|
object, env->GetFieldID(s_keyboard_config_class, "button_text", "[Ljava/lang/String;"),
|
||||||
|
array);
|
||||||
|
env->DeleteGlobalRef(string_class);
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Frontend::KeyboardData ToFrontendKeyboardData(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 Frontend::KeyboardData{
|
||||||
|
GetJString(env, string),
|
||||||
|
static_cast<u8>(
|
||||||
|
env->GetIntField(object, env->GetFieldID(s_keyboard_data_class, "button", "I")))};
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidKeyboard::~AndroidKeyboard() = default;
|
||||||
|
|
||||||
|
void AndroidKeyboard::Execute(const Frontend::KeyboardConfig& config) {
|
||||||
|
SoftwareKeyboard::Execute(config);
|
||||||
|
|
||||||
|
const auto data = ToFrontendKeyboardData(IDCache::GetEnvForThread()->CallStaticObjectMethod(
|
||||||
|
s_software_keyboard_class, s_swkbd_execute, ToJavaKeyboardConfig(config)));
|
||||||
|
Finalize(data.text, data.button);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidKeyboard::ShowError(const std::string& error) {
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
env->CallStaticVoidMethod(s_software_keyboard_class, s_swkbd_show_error,
|
||||||
|
env->NewStringUTF(error.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitJNI(JNIEnv* env) {
|
||||||
|
s_software_keyboard_class = reinterpret_cast<jclass>(
|
||||||
|
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/applets/SoftwareKeyboard")));
|
||||||
|
s_keyboard_config_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
||||||
|
env->FindClass("org/citra/citra_emu/applets/SoftwareKeyboard$KeyboardConfig")));
|
||||||
|
s_keyboard_data_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
||||||
|
env->FindClass("org/citra/citra_emu/applets/SoftwareKeyboard$KeyboardData")));
|
||||||
|
s_validation_error_class = reinterpret_cast<jclass>(env->NewGlobalRef(
|
||||||
|
env->FindClass("org/citra/citra_emu/applets/SoftwareKeyboard$ValidationError")));
|
||||||
|
|
||||||
|
s_swkbd_execute = env->GetStaticMethodID(
|
||||||
|
s_software_keyboard_class, "Execute",
|
||||||
|
"(Lorg/citra/citra_emu/applets/SoftwareKeyboard$KeyboardConfig;)Lorg/citra/citra_emu/"
|
||||||
|
"applets/SoftwareKeyboard$KeyboardData;");
|
||||||
|
s_swkbd_show_error =
|
||||||
|
env->GetStaticMethodID(s_software_keyboard_class, "ShowError", "(Ljava/lang/String;)V");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanupJNI(JNIEnv* env) {
|
||||||
|
env->DeleteGlobalRef(s_software_keyboard_class);
|
||||||
|
env->DeleteGlobalRef(s_keyboard_config_class);
|
||||||
|
env->DeleteGlobalRef(s_keyboard_data_class);
|
||||||
|
env->DeleteGlobalRef(s_validation_error_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace SoftwareKeyboard
|
||||||
|
|
||||||
|
jobject ToJavaValidationError(Frontend::ValidationError error) {
|
||||||
|
static const std::map<Frontend::ValidationError, const char*> ValidationErrorNameMap{{
|
||||||
|
{Frontend::ValidationError::None, "None"},
|
||||||
|
{Frontend::ValidationError::ButtonOutOfRange, "ButtonOutOfRange"},
|
||||||
|
{Frontend::ValidationError::MaxDigitsExceeded, "MaxDigitsExceeded"},
|
||||||
|
{Frontend::ValidationError::AtSignNotAllowed, "AtSignsNotAllowed"},
|
||||||
|
{Frontend::ValidationError::PercentNotAllowed, "PercentNotAllowed"},
|
||||||
|
{Frontend::ValidationError::BackslashNotAllowed, "BackslashNotAllowed"},
|
||||||
|
{Frontend::ValidationError::ProfanityNotAllowed, "ProfanityNotAllowed"},
|
||||||
|
{Frontend::ValidationError::CallbackFailed, "CallbackFailed"},
|
||||||
|
{Frontend::ValidationError::FixedLengthRequired, "FixedLengthRequired"},
|
||||||
|
{Frontend::ValidationError::MaxLengthExceeded, "MaxLengthExceeded"},
|
||||||
|
{Frontend::ValidationError::BlankInputNotAllowed, "BlankInputNotAllowed"},
|
||||||
|
{Frontend::ValidationError::EmptyInputNotAllowed, "EmptyInputNotAllowed"},
|
||||||
|
}};
|
||||||
|
ASSERT(ValidationErrorNameMap.count(error));
|
||||||
|
|
||||||
|
JNIEnv* env = IDCache::GetEnvForThread();
|
||||||
|
return env->GetStaticObjectField(
|
||||||
|
s_validation_error_class,
|
||||||
|
env->GetStaticFieldID(s_validation_error_class, ValidationErrorNameMap.at(error),
|
||||||
|
"Lorg/citra/citra_emu/applets/SoftwareKeyboard$ValidationError;"));
|
||||||
|
}
|
||||||
|
|
||||||
|
jobject Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateFilters(JNIEnv* env,
|
||||||
|
jclass clazz,
|
||||||
|
jstring text) {
|
||||||
|
|
||||||
|
const auto ret =
|
||||||
|
Core::System::GetInstance().GetSoftwareKeyboard()->ValidateFilters(GetJString(env, text));
|
||||||
|
return ToJavaValidationError(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
jobject Java_org_citra_citra_1emu_applets_SoftwareKeyboard_ValidateInput(JNIEnv* env, jclass clazz,
|
||||||
|
jstring text) {
|
||||||
|
|
||||||
|
const auto ret =
|
||||||
|
Core::System::GetInstance().GetSoftwareKeyboard()->ValidateInput(GetJString(env, text));
|
||||||
|
return ToJavaValidationError(ret);
|
||||||
|
}
|
35
src/android/app/src/main/jni/applets/swkbd.h
Normal file
35
src/android/app/src/main/jni/applets/swkbd.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include "core/frontend/applets/swkbd.h"
|
||||||
|
|
||||||
|
namespace SoftwareKeyboard {
|
||||||
|
|
||||||
|
class AndroidKeyboard final : public Frontend::SoftwareKeyboard {
|
||||||
|
public:
|
||||||
|
~AndroidKeyboard();
|
||||||
|
|
||||||
|
void Execute(const Frontend::KeyboardConfig& config) override;
|
||||||
|
void ShowError(const std::string& error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
#include "common/logging/filter.h"
|
#include "common/logging/filter.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
#include "jni/applets/swkbd.h"
|
||||||
#include "jni/id_cache.h"
|
#include "jni/id_cache.h"
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
@ -111,6 +112,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||||||
s_exit_emulation_activity =
|
s_exit_emulation_activity =
|
||||||
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
|
||||||
|
|
||||||
|
SoftwareKeyboard::InitJNI(env);
|
||||||
|
|
||||||
return JNI_VERSION;
|
return JNI_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +124,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
env->DeleteGlobalRef(s_native_library_class);
|
env->DeleteGlobalRef(s_native_library_class);
|
||||||
|
SoftwareKeyboard::CleanupJNI(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "core/frontend/scope_acquire_context.h"
|
#include "core/frontend/scope_acquire_context.h"
|
||||||
#include "core/hle/service/am/am.h"
|
#include "core/hle/service/am/am.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
#include "jni/applets/swkbd.h"
|
||||||
#include "jni/button_manager.h"
|
#include "jni/button_manager.h"
|
||||||
#include "jni/config.h"
|
#include "jni/config.h"
|
||||||
#include "jni/emu_window/emu_window.h"
|
#include "jni/emu_window/emu_window.h"
|
||||||
@ -83,18 +84,6 @@ static int AlertPromptButton() {
|
|||||||
IDCache::GetAlertPromptButton()));
|
IDCache::GetAlertPromptButton()));
|
||||||
}
|
}
|
||||||
|
|
||||||
class AndroidKeyboard final : public Frontend::SoftwareKeyboard {
|
|
||||||
public:
|
|
||||||
void Execute(const Frontend::KeyboardConfig& config) override {
|
|
||||||
SoftwareKeyboard::Execute(config);
|
|
||||||
Finalize(DisplayAlertPrompt("Enter text", config.hint_text.c_str(),
|
|
||||||
static_cast<int>(this->config.button_config)),
|
|
||||||
AlertPromptButton());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ShowError(const std::string& error) override {}
|
|
||||||
};
|
|
||||||
|
|
||||||
static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||||
// Citra core only supports a single running instance
|
// Citra core only supports a single running instance
|
||||||
std::lock_guard<std::mutex> lock(running_mutex);
|
std::lock_guard<std::mutex> lock(running_mutex);
|
||||||
@ -118,7 +107,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
|
|
||||||
// Register frontend applets
|
// Register frontend applets
|
||||||
Frontend::RegisterDefaultApplets();
|
Frontend::RegisterDefaultApplets();
|
||||||
system.RegisterSoftwareKeyboard(std::make_shared<AndroidKeyboard>());
|
system.RegisterSoftwareKeyboard(std::make_shared<SoftwareKeyboard::AndroidKeyboard>());
|
||||||
|
|
||||||
InputManager::Init();
|
InputManager::Init();
|
||||||
|
|
||||||
|
@ -137,4 +137,12 @@
|
|||||||
<string name="external_storage_not_mounted">The external storage needs to be available in order to use Citra</string>
|
<string name="external_storage_not_mounted">The external storage needs to be available in order to use Citra</string>
|
||||||
|
|
||||||
<string name="select_dir">Select This Directory</string>
|
<string name="select_dir">Select This Directory</string>
|
||||||
|
|
||||||
|
<!-- Software Keyboard -->
|
||||||
|
<string name="software_keyboard">Software Keyboard</string>
|
||||||
|
<string name="i_forgot">I Forgot</string>
|
||||||
|
<string name="fixed_length_required">Text length is not correct (should be %d characters)</string>
|
||||||
|
<string name="max_length_exceeded">Text is too long (should be no more than %d characters)</string>
|
||||||
|
<string name="blank_input_not_allowed">Blank input is not allowed</string>
|
||||||
|
<string name="empty_input_not_allowed">Empty input is not allowed</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user