Updated swkbd to use native android input

This commit is contained in:
SSimco 2024-10-25 13:31:21 +03:00
parent e8ebeab313
commit bdd6b3f835
17 changed files with 359 additions and 48 deletions

View File

@ -983,7 +983,9 @@ void LatteRenderTarget_copyToBackbuffer(LatteTextureView* textureView, bool isPa
g_renderer->HandleScreenshotRequest(textureView, isPadView);
if (!g_renderer->ImguiBegin(!isPadView))
return;
swkbd_render(!isPadView);
#if !__ANDROID__
swkbd::render(!isPadView);
#endif
nn::erreula::render(!isPadView);
LatteOverlay_render(isPadView);
g_renderer->ImguiEnd();
@ -1003,7 +1005,7 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin
const bool tabPressed = GuiSystem::isKeyDown(GuiSystem::PlatformKeyCodes::TAB);
const bool ctrlPressed = GuiSystem::isKeyDown(GuiSystem::PlatformKeyCodes::LCONTROL);
bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed;
bool showDRC = swkbd::hasKeyboardInputHook() == false && tabPressed;
bool& alwaysDisplayDRC = LatteGPUState.alwaysDisplayDRC;
if (ctrlPressed && tabPressed)

View File

@ -1,3 +1,4 @@
#include "OS/libs/swkbd/swkbd.h"
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "Cafe/OS/libs/gx2/GX2.h"
@ -18,7 +19,7 @@
#define SWKBD_STATE_DISPLAYED (2) // visible
#define SWKBD_STATE_DISAPPEARING (3) // fade-out ?
typedef struct
typedef struct
{
uint32 ukn00; // constructor?
uint32 ukn04; // destructor?
@ -26,7 +27,7 @@ typedef struct
MEMPTR<void> changeString; // some function address
}SwkbdIEventReceiverVTable_t;
typedef struct
typedef struct
{
MEMPTR<SwkbdIEventReceiverVTable_t> vTable;
// todo - more elements? (currently separated from this struct)
@ -42,7 +43,7 @@ struct swkbdReceiverArg_t
sint32be selectFrom;
};
typedef struct
typedef struct
{
uint32 ukn000;
uint32 controllerType;
@ -67,7 +68,7 @@ typedef struct
swkbdReceiverArg_t receiverArg;
}SwkbdKeyboardArg_t;
typedef struct
typedef struct
{
// this structure resides in PPC addressable memory space
wchar_t formStringBuffer[SWKBD_FORM_STRING_MAX_LENGTH];
@ -86,11 +87,18 @@ typedef struct
bool shiftActivated;
bool returnState;
bool cancelState;
}swkbdInternalState_t;
swkbdInternalState_t* swkbdInternalState = NULL;
std::shared_ptr<swkbd::swkbdCallbacks> s_swkbdCallbacks;
void swkbd::setSwkbdCallbacks(const std::shared_ptr<swkbd::swkbdCallbacks>& swkbdCallbacks)
{
s_swkbdCallbacks = swkbdCallbacks;
}
void swkbdExport_SwkbdCreate(PPCInterpreter_t* hCPU)
{
cemuLog_logDebug(LogType::Force, "swkbd.SwkbdCreate(0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6]);
@ -142,12 +150,12 @@ void swkbdExport_SwkbdGetStateInputForm(PPCInterpreter_t* hCPU)
// ?
typedef struct
typedef struct
{
MPTR vTable; // guessed
}swkdbIEventReceiver_t;
typedef struct
typedef struct
{
uint32 ukn00;
uint32 ukn04;
@ -171,7 +179,7 @@ void swkbdExport_SwkbdSetReceiver(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, 0);
}
typedef struct
typedef struct
{
/* +0x00 */ uint32 ukn00;
/* +0x04 */ uint32 ukn04;
@ -235,6 +243,9 @@ void swkbdExport_SwkbdAppearInputForm(PPCInterpreter_t* hCPU)
swkbdInternalState->formStringBuffer[0] = '\0';
swkbdInternalState->formStringLength = 0;
}
if (s_swkbdCallbacks)
s_swkbdCallbacks->showSoftwareKeyboard({swkbdInternalState->formStringBuffer, swkbdInternalState->formStringBuffer + swkbdInternalState->formStringLength}, swkbdInternalState->maxTextLength);
osLib_returnFromFunction(hCPU, 1);
}
@ -257,6 +268,11 @@ void swkbdExport_SwkbdAppearKeyboard(PPCInterpreter_t* hCPU)
swkbdInternalState->formStringBuffer[0] = '\0';
swkbdInternalState->formStringLength = 0;
swkbdInternalState->keyboardArg = *keyboardArg;
if (s_swkbdCallbacks)
{
sint32 maxLength = std::max(swkbdInternalState->keyboardArg.receiverArg.stringBufSize - 1, 0);
s_swkbdCallbacks->showSoftwareKeyboard({swkbdInternalState->formStringBuffer, swkbdInternalState->formStringBuffer + swkbdInternalState->formStringLength}, maxLength);
}
osLib_returnFromFunction(hCPU, 1);
}
@ -264,6 +280,8 @@ void swkbdExport_SwkbdDisappearInputForm(PPCInterpreter_t* hCPU)
{
debug_printf("SwkbdDisappearInputForm__3RplFv LR: %08x\n", hCPU->spr.LR);
swkbdInternalState->isActive = false;
if (s_swkbdCallbacks)
s_swkbdCallbacks->hideSoftwareKeyboard();
osLib_returnFromFunction(hCPU, 1);
}
@ -271,6 +289,8 @@ void swkbdExport_SwkbdDisappearKeyboard(PPCInterpreter_t* hCPU)
{
debug_printf("SwkbdDisappearKeyboard__3RplFv LR: %08x\n", hCPU->spr.LR);
swkbdInternalState->isActive = false;
if (s_swkbdCallbacks)
s_swkbdCallbacks->hideSoftwareKeyboard();
osLib_returnFromFunction(hCPU, 1);
}
@ -292,7 +312,7 @@ void swkbdExport_SwkbdIsDecideOkButton(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, 0);
}
typedef struct
typedef struct
{
uint32be ukn00;
uint32be ukn04;
@ -349,13 +369,12 @@ void swkbdExport_SwkbdIsNeedCalcSubThreadPredict(PPCInterpreter_t* hCPU)
osLib_returnFromFunction(hCPU, r?1:0);
}
void swkbd_keyInput(uint32 keyCode);
void swkbd_render(bool mainWindow)
void swkbd::render(bool mainWindow)
{
// only render if active
if( swkbdInternalState == NULL || swkbdInternalState->isActive == false)
return;
auto& io = ImGui::GetIO();
const auto font = ImGui_GetFont(48.0f);
const auto textFont = ImGui_GetFont(24.0f);
@ -438,13 +457,13 @@ void swkbd_render(bool mainWindow)
if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0}))
{
if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0)
swkbd_keyInput(8);
swkbd::keyInput(BACKSPACE_KEYCODE);
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0)
swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated;
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0)
swkbd_keyInput(13);
swkbd::keyInput(RETURN_KEYCODE);
else
swkbd_keyInput(*key);
swkbd::keyInput(*key);
}
ImGui::SameLine();
@ -470,13 +489,13 @@ void swkbd_render(bool mainWindow)
if (ImGui::Button(key, { *key == ' ' ? 537 : (button_len + 5), 0 }))
{
if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_CIRCLE_LEFT)) == 0)
swkbd_keyInput(8);
swkbd::keyInput(BACKSPACE_KEYCODE);
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_ARROW_UP)) == 0)
swkbdInternalState->shiftActivated = !swkbdInternalState->shiftActivated;
else if (strcmp(key, _utf8WrapperPtr(ICON_FA_CHECK)) == 0)
swkbd_keyInput(13);
swkbd::keyInput(RETURN_KEYCODE);
else
swkbd_keyInput(*key);
swkbd::keyInput(*key);
}
ImGui::SameLine();
@ -492,7 +511,7 @@ void swkbd_render(bool mainWindow)
if (io.NavInputs[ImGuiNavInput_Cancel] > 0)
{
if(!swkbdInternalState->cancelState)
swkbd_keyInput(8); // backspace
swkbd::keyInput(BACKSPACE_KEYCODE);
swkbdInternalState->cancelState = true;
}
else
@ -501,7 +520,7 @@ void swkbd_render(bool mainWindow)
if (io.NavInputs[ImGuiNavInput_Input] > 0)
{
if (!swkbdInternalState->returnState)
swkbd_keyInput(13); // return
swkbd::keyInput(RETURN_KEYCODE);
swkbdInternalState->returnState = true;
}
else
@ -511,7 +530,7 @@ void swkbd_render(bool mainWindow)
ImGui::PopStyleColor();
}
bool swkbd_hasKeyboardInputHook()
bool swkbd::hasKeyboardInputHook()
{
return swkbdInternalState != NULL && swkbdInternalState->isActive;
}
@ -521,7 +540,7 @@ void swkbd_finishInput()
swkbdInternalState->decideButtonWasPressed = true; // currently we always accept the input
}
typedef struct
typedef struct
{
uint32be beginIndex;
uint32be endIndex;
@ -564,9 +583,9 @@ void swkbd_inputStringChanged()
}
}
void swkbd_keyInput(uint32 keyCode)
void swkbd::keyInput(uint32 keyCode)
{
if (keyCode == 8 || keyCode == 127) // backspace || backwards delete
if (keyCode == BACKSPACE_KEYCODE || keyCode == BACKWARDS_DELETE_KEYCODE)
{
if (swkbdInternalState->formStringLength > 0)
swkbdInternalState->formStringLength--;
@ -574,7 +593,7 @@ void swkbd_keyInput(uint32 keyCode)
swkbd_inputStringChanged();
return;
}
else if (keyCode == 13) // return
else if (keyCode == RETURN_KEYCODE)
{
swkbd_finishInput();
return;
@ -593,7 +612,7 @@ void swkbd_keyInput(uint32 keyCode)
{
// allowed
}
else if (keyCode < 32)
else if (keyCode < CONTROL_KEYCODE)
{
// control key
return;

View File

@ -1,8 +1,24 @@
void swkbd_render(bool mainWindow);
bool swkbd_hasKeyboardInputHook();
void swkbd_keyInput(uint32 keyCode);
#pragma once
namespace swkbd
{
constexpr uint32 BACKSPACE_KEYCODE = 8;
constexpr uint32 BACKWARDS_DELETE_KEYCODE = 127;
constexpr uint32 RETURN_KEYCODE = 13;
constexpr uint32 CONTROL_KEYCODE = 32;
class swkbdCallbacks
{
public:
virtual void showSoftwareKeyboard(const std::string& initialText, sint32 maxLength) = 0;
virtual void hideSoftwareKeyboard() = 0;
};
void setSwkbdCallbacks(const std::shared_ptr<swkbdCallbacks>& callbacks);
void render(bool mainWindow);
bool hasKeyboardInputHook();
void keyInput(uint32 keyCode);
void load();
}
} // namespace swkbd

View File

@ -0,0 +1,27 @@
#include "AndroidSwkbdCallbacks.h"
#include "JNIUtils.h"
AndroidSwkbdCallbacks::AndroidSwkbdCallbacks()
{
JNIUtils::ScopedJNIENV env;
m_emulationActivityClass = JNIUtils::Scopedjclass("info/cemu/Cemu/emulation/EmulationActivity");
m_showSoftwareKeyboardMethodID = env->GetStaticMethodID(*m_emulationActivityClass, "showEmulationTextInput", "(Ljava/lang/String;I)V");
m_hideSoftwareKeyboardMethodID = env->GetStaticMethodID(*m_emulationActivityClass, "hideEmulationTextInput", "()V");
}
void AndroidSwkbdCallbacks::showSoftwareKeyboard(const std::string& initialText, sint32 maxLength)
{
std::thread([&, this]() {
JNIUtils::ScopedJNIENV env;
jstring j_initialText = env->NewStringUTF(initialText.c_str());
JNIUtils::ScopedJNIENV()->CallStaticVoidMethod(*m_emulationActivityClass, m_showSoftwareKeyboardMethodID, j_initialText, maxLength);
env->DeleteLocalRef(j_initialText);
}).join();
}
void AndroidSwkbdCallbacks::hideSoftwareKeyboard()
{
std::thread([this]() {
JNIUtils::ScopedJNIENV()->CallStaticVoidMethod(*m_emulationActivityClass, m_hideSoftwareKeyboardMethodID);
}).join();
}

View File

@ -0,0 +1,15 @@
#pragma once
#include "Cafe/OS/libs/swkbd/swkbd.h"
#include "JNIUtils.h"
class AndroidSwkbdCallbacks : public swkbd::swkbdCallbacks
{
JNIUtils::Scopedjclass m_emulationActivityClass;
jmethodID m_showSoftwareKeyboardMethodID;
jmethodID m_hideSoftwareKeyboardMethodID;
public:
AndroidSwkbdCallbacks();
void showSoftwareKeyboard(const std::string& initialText, sint32 maxLength) override;
void hideSoftwareKeyboard() override;
};

View File

@ -1,6 +1,7 @@
add_library(CemuAndroid SHARED
AndroidAudio.cpp
AndroidEmulatedController.cpp
AndroidSwkbdCallbacks.cpp
CMakeLists.txt
GameTitleLoader.cpp
Image.cpp
@ -11,6 +12,7 @@ add_library(CemuAndroid SHARED
NativeInput.cpp
NativeLib.cpp
NativeSettings.cpp
NativeSwkbd.cpp
)
target_link_libraries(CemuAndroid PRIVATE

View File

@ -7,6 +7,8 @@ namespace JNIUtils
std::string JNIUtils::JStringToString(JNIEnv* env, jstring jstr)
{
if (jstr == nullptr)
return {};
const char* c_str = env->GetStringUTFChars(jstr, nullptr);
std::string str(c_str);
env->ReleaseStringUTFChars(jstr, c_str);

View File

@ -0,0 +1,56 @@
#include "JNIUtils.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
#include "AndroidSwkbdCallbacks.h"
namespace NativeSwkbd
{
std::shared_ptr<swkbd::swkbdCallbacks> s_swkbdCallbacks;
std::string s_currentInputText;
struct StrDiffs
{
size_t newTextStartIndex;
size_t numberOfCharacterToDelete;
};
StrDiffs getStringDiffs(const std::string& newText, const std::string& currentText)
{
if (newText.length() < currentText.length() && currentText.starts_with(newText))
return {newText.length(), currentText.length() - newText.length()};
if (newText.length() >= currentText.length() && newText.starts_with(currentText))
return {currentText.length(), 0};
return {0, currentText.length()};
}
} // namespace NativeSwkbd
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_initializeSwkbd([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
{
if (NativeSwkbd::s_swkbdCallbacks != nullptr)
return;
NativeSwkbd::s_swkbdCallbacks = std::make_shared<AndroidSwkbdCallbacks>();
swkbd::setSwkbdCallbacks(NativeSwkbd::s_swkbdCallbacks);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_setCurrentInputText([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring initial_text)
{
NativeSwkbd::s_currentInputText = JNIUtils::JStringToString(env, initial_text);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_onTextChanged([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jstring j_text)
{
std::string text = JNIUtils::JStringToString(env, j_text);
auto stringDiff = NativeSwkbd::getStringDiffs(text, NativeSwkbd::s_currentInputText);
for (size_t i = 0; i < stringDiff.numberOfCharacterToDelete; i++)
swkbd::keyInput(swkbd::BACKSPACE_KEYCODE);
for (size_t i = stringDiff.newTextStartIndex; i < text.length(); i++)
swkbd::keyInput(text.at(i));
NativeSwkbd::s_currentInputText = std::move(text);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_nativeinterface_NativeSwkbd_onFinishedInputEdit([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
{
swkbd::keyInput(swkbd::RETURN_KEYCODE);
}

View File

@ -7,6 +7,7 @@ import java.io.File;
import info.cemu.Cemu.nativeinterface.NativeEmulation;
import info.cemu.Cemu.nativeinterface.NativeGraphicPacks;
import info.cemu.Cemu.nativeinterface.NativeSwkbd;
public class CemuApplication extends Application {
static {
@ -37,6 +38,7 @@ public class CemuApplication extends Application {
NativeEmulation.setDPI(displayMetrics.density);
NativeEmulation.initializeActiveSettings(getInternalFolder().toString(), getInternalFolder().toString());
NativeEmulation.initializeEmulation();
NativeSwkbd.initializeSwkbd();
NativeGraphicPacks.refreshGraphicPacks();
}
}

View File

@ -1,12 +1,15 @@
package info.cemu.Cemu.emulation;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputFilter;
import android.view.KeyEvent;
import android.view.MotionEvent;
import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
@ -16,17 +19,59 @@ import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
import info.cemu.Cemu.BuildConfig;
import info.cemu.Cemu.R;
import info.cemu.Cemu.databinding.ActivityEmulationBinding;
import info.cemu.Cemu.input.InputManager;
import info.cemu.Cemu.nativeinterface.NativeSwkbd;
public class EmulationActivity extends AppCompatActivity implements Observer<EmulationData> {
private EmulationViewModel viewModel;
private boolean hasEmulationError;
public static final String LAUNCH_PATH = "LAUNCH_PATH";
private ActivityEmulationBinding binding;
private EmulationFragment emulationFragment;
public static final String EXTRA_LAUNCH_PATH = BuildConfig.APPLICATION_ID + ".LaunchPath";
private final InputManager inputManager = new InputManager();
private static EmulationActivity emulationActivityInstance;
private AlertDialog emulationTextInputDialog;
/**
* This method is called by swkbd using JNI.
*/
@SuppressWarnings("unused")
public static void showEmulationTextInput(String initialText, int maxLength) {
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog != null)
return;
NativeSwkbd.setCurrentInputText(initialText);
emulationActivityInstance.runOnUiThread(() -> {
var inputEditTextLayout = emulationActivityInstance.getLayoutInflater().inflate(R.layout.layout_emulation_input, null);
EmulationTextInputEditText inputEditText = inputEditTextLayout.requireViewById(R.id.emulation_input_text);
inputEditText.updateText(initialText);
var dialog = new MaterialAlertDialogBuilder(emulationActivityInstance)
.setView(inputEditTextLayout)
.setCancelable(false)
.setPositiveButton(R.string.done, (d, w) -> {
}).show();
var doneButton = Objects.requireNonNull(dialog.getButton(DialogInterface.BUTTON_POSITIVE));
doneButton.setEnabled(false);
doneButton.setOnClickListener(v -> inputEditText.onFinishedEdit());
inputEditText.setOnTextChangedListener(s -> doneButton.setEnabled(s.length() > 0));
if (maxLength > 0)
inputEditText.appendFilter(new InputFilter.LengthFilter(maxLength));
emulationActivityInstance.emulationTextInputDialog = dialog;
});
}
/**
* This method is called by swkbd using JNI.
*/
@SuppressWarnings("unused")
public static void hideEmulationTextInput() {
if (emulationActivityInstance == null || emulationActivityInstance.emulationTextInputDialog == null)
return;
var textInputDialog = emulationActivityInstance.emulationTextInputDialog;
emulationActivityInstance.emulationTextInputDialog = null;
emulationActivityInstance.runOnUiThread(textInputDialog::dismiss);
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
@ -45,6 +90,7 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
emulationActivityInstance = this;
getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
@ -52,14 +98,14 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
}
});
viewModel = new ViewModelProvider(this).get(EmulationViewModel.class);
EmulationViewModel viewModel = new ViewModelProvider(this).get(EmulationViewModel.class);
viewModel.getEmulationData().observe(this, this);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
Uri data = intent.getData();
String launchPath = null;
if (extras != null) {
launchPath = extras.getString(LAUNCH_PATH);
launchPath = extras.getString(EXTRA_LAUNCH_PATH);
}
if (launchPath == null && data != null) {
launchPath = data.toString();
@ -68,9 +114,9 @@ public class EmulationActivity extends AppCompatActivity implements Observer<Emu
throw new RuntimeException("launchPath is null");
}
setFullscreen();
binding = ActivityEmulationBinding.inflate(getLayoutInflater());
info.cemu.Cemu.databinding.ActivityEmulationBinding binding = ActivityEmulationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
emulationFragment = (EmulationFragment) getSupportFragmentManager().findFragmentById(R.id.emulation_frame);
EmulationFragment emulationFragment = (EmulationFragment) getSupportFragmentManager().findFragmentById(R.id.emulation_frame);
if (emulationFragment == null) {
emulationFragment = new EmulationFragment(launchPath);
getSupportFragmentManager()

View File

@ -0,0 +1,96 @@
package info.cemu.Cemu.emulation;
import android.content.Context;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.textfield.TextInputEditText;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import info.cemu.Cemu.nativeinterface.NativeSwkbd;
public class EmulationTextInputEditText extends TextInputEditText {
static private final Pattern INPUT_PATTERN = Pattern.compile("^[\\da-zA-Z \\-/;:',.?!#\\[\\]$%^&*()_@\\\\<>+=]+$");
public EmulationTextInputEditText(@NonNull Context context) {
this(context, null);
}
public EmulationTextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, com.google.android.material.R.attr.editTextStyle);
}
public void appendFilter(InputFilter inputFilter) {
setFilters(Stream.concat(Arrays.stream(getFilters()), Stream.of(inputFilter)).toArray(InputFilter[]::new));
}
public void updateText(String text) {
boolean hasFocus = hasFocus();
if (hasFocus)
clearFocus();
setText(text);
if (hasFocus)
requestFocus();
}
public EmulationTextInputEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
appendFilter((source, start, end, dest, dstart, dend) -> {
if (INPUT_PATTERN.matcher(source).matches())
return null;
return "";
});
setInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_NORMAL);
addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence text, int start, int before, int count) {
if (!hasFocus())
return;
if (onTextChangedListener != null)
onTextChangedListener.onTextChanged(text);
NativeSwkbd.onTextChanged(text.toString());
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
public interface OnTextChangedListener {
void onTextChanged(CharSequence text);
}
private OnTextChangedListener onTextChangedListener;
@Override
public void onEditorAction(int actionCode) {
var text = getText();
if (actionCode == EditorInfo.IME_ACTION_DONE && text != null && text.length() > 0) {
onFinishedEdit();
}
super.onEditorAction(actionCode);
}
public void onFinishedEdit() {
NativeSwkbd.onFinishedInputEdit();
}
public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) {
this.onTextChangedListener = onTextChangedListener;
}
}

View File

@ -48,7 +48,7 @@ public class GamesFragment extends Fragment {
currentGamePaths = new HashSet<>(NativeSettings.getGamesPaths());
gameAdapter = new GameAdapter(game -> {
Intent intent = new Intent(getContext(), EmulationActivity.class);
intent.putExtra(EmulationActivity.LAUNCH_PATH, game.path());
intent.putExtra(EmulationActivity.EXTRA_LAUNCH_PATH, game.path());
startActivity(intent);
});
gameListViewModel = new ViewModelProvider(this).get(GameListViewModel.class);
@ -74,6 +74,8 @@ public class GamesFragment extends Fragment {
MenuInflater inflater = requireActivity().getMenuInflater();
inflater.inflate(R.menu.game, menu);
Game selectedGame = gameAdapter.getSelectedGame();
if (selectedGame == null)
return;
menu.findItem(R.id.favorite).setChecked(selectedGame.isFavorite());
menu.findItem(R.id.remove_shader_caches).setEnabled(NativeGameTitles.titleHasShaderCacheFiles(selectedGame.titleId()));
}
@ -161,7 +163,6 @@ public class GamesFragment extends Fragment {
});
recyclerView.setAdapter(gameAdapter);
return binding.getRoot();
}

View File

@ -0,0 +1,11 @@
package info.cemu.Cemu.nativeinterface;
public class NativeSwkbd {
public static native void initializeSwkbd();
public static native void setCurrentInputText(String text);
public static native void onTextChanged(String text);
public static native void onFinishedInputEdit();
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<info.cemu.Cemu.emulation.EmulationTextInputEditText
android:id="@+id/emulation_input_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/emulation_input_text_hint" />
</com.google.android.material.textfield.TextInputLayout>

View File

@ -238,4 +238,6 @@
<string name="console_language_russian">Russian</string>
<string name="console_language_taiwanese">Taiwanese</string>
<string name="console_language">Console language</string>
<string name="emulation_input_text_hint">Input text</string>
<string name="done">Done</string>
</resources>

View File

@ -1613,7 +1613,7 @@ void MainWindow::OnKeyUp(wxKeyEvent& event)
{
event.Skip();
if (swkbd_hasKeyboardInputHook())
if (swkbd::hasKeyboardInputHook())
return;
const auto code = event.GetKeyCode();
@ -1640,8 +1640,8 @@ void MainWindow::OnKeyDown(wxKeyEvent& event)
void MainWindow::OnChar(wxKeyEvent& event)
{
if (swkbd_hasKeyboardInputHook())
swkbd_keyInput(event.GetUnicodeKey());
if (swkbd::hasKeyboardInputHook())
swkbd::keyInput(event.GetUnicodeKey());
// event.Skip();
}

View File

@ -149,7 +149,7 @@ void PadViewFrame::OnKeyUp(wxKeyEvent& event)
{
event.Skip();
if (swkbd_hasKeyboardInputHook())
if (swkbd::hasKeyboardInputHook())
return;
const auto code = event.GetKeyCode();
@ -173,8 +173,8 @@ void PadViewFrame::OnGesturePan(wxPanGestureEvent& event)
void PadViewFrame::OnChar(wxKeyEvent& event)
{
if (swkbd_hasKeyboardInputHook())
swkbd_keyInput(event.GetUnicodeKey());
if (swkbd::hasKeyboardInputHook())
swkbd::keyInput(event.GetUnicodeKey());
event.Skip();
}