diff --git a/UI/ChatScreen.cpp b/UI/ChatScreen.cpp index 2f903bb774..e3b5229e27 100644 --- a/UI/ChatScreen.cpp +++ b/UI/ChatScreen.cpp @@ -121,7 +121,10 @@ UI::EventReturn ChatMenu::OnSubmit(UI::EventParams &e) { chatEdit_->SetFocus(); sendChat(chat); #elif PPSSPP_PLATFORM(ANDROID) - System_SendMessage("inputbox", "Chat:"); + auto n = GetI18NCategory("Networking"); + System_InputBoxGetString(n->T("Chat"), "", [](bool result, const std::string &value) { + sendChat(value); + }); #endif return UI::EVENT_DONE; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index cc61e7969a..e8945909ad 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -982,7 +982,32 @@ UI::EventReturn GameSettingsScreen::OnJitAffectingSetting(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnChangeMemStickDir(UI::EventParams &e) { auto sy = GetI18NCategory("System"); - System_SendMessage("inputbox", (std::string(sy->T("Memory Stick Folder")) + ":" + g_Config.memStickDirectory).c_str()); + System_InputBoxGetString(sy->T("Memory Stick Folder"), g_Config.memStickDirectory, [&](bool result, const std::string &value) { + auto sy = GetI18NCategory("System"); + auto di = GetI18NCategory("Dialog"); + + if (result) { + std::string newPath = value; + size_t pos = newPath.find_last_not_of("/"); + // Gotta have at least something but a /, and also needs to start with a /. + if (newPath.empty() || pos == newPath.npos || newPath[0] != '/') { + settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); + return; + } + if (pos != newPath.size() - 1) { + newPath = newPath.substr(0, pos + 1); + } + + pendingMemstickFolder_ = newPath; + std::string promptMessage = sy->T("ChangingMemstickPath", "Save games, save states, and other data will not be copied to this folder.\n\nChange the Memory Stick folder?"); + if (!File::Exists(newPath)) { + promptMessage = sy->T("ChangingMemstickPathNotExists", "That folder doesn't exist yet.\n\nSave games, save states, and other data will not be copied to this folder.\n\nCreate a new Memory Stick folder?"); + } + // Add the path for clarity and proper confirmation. + promptMessage += "\n\n" + newPath + "/"; + screenManager()->push(new PromptScreen(promptMessage, di->T("Yes"), di->T("No"), std::bind(&GameSettingsScreen::CallbackMemstickFolder, this, std::placeholders::_1))); + } + }); return UI::EVENT_DONE; } @@ -1139,43 +1164,6 @@ void GameSettingsScreen::onFinish(DialogResult result) { NativeMessageReceived("gpu_clearCache", ""); } -void GameSettingsScreen::sendMessage(const char *message, const char *value) { - UIDialogScreenWithGameBackground::sendMessage(message, value); - - auto sy = GetI18NCategory("System"); - auto di = GetI18NCategory("Dialog"); - - if (!strcmp(message, "inputbox_completed")) { - std::vector inputboxValue; - SplitString(value, ':', inputboxValue); - -#if PPSSPP_PLATFORM(ANDROID) - if (inputboxValue.size() >= 2 && inputboxValue[0] == sy->T("Memory Stick Folder")) { - // Allow colons in the path. - std::string newPath = std::string(value).substr(inputboxValue[0].size() + 1); - size_t pos = newPath.find_last_not_of("/"); - // Gotta have at least something but a /, and also needs to start with a /. - if (newPath.empty() || pos == newPath.npos || newPath[0] != '/') { - settingInfo_->Show(sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), nullptr); - return; - } - if (pos != newPath.size() - 1) { - newPath = newPath.substr(0, pos + 1); - } - - pendingMemstickFolder_ = newPath; - std::string promptMessage = sy->T("ChangingMemstickPath", "Save games, save states, and other data will not be copied to this folder.\n\nChange the Memory Stick folder?"); - if (!File::Exists(newPath)) { - promptMessage = sy->T("ChangingMemstickPathNotExists", "That folder doesn't exist yet.\n\nSave games, save states, and other data will not be copied to this folder.\n\nCreate a new Memory Stick folder?"); - } - // Add the path for clarity and proper confirmation. - promptMessage += "\n\n" + newPath + "/"; - screenManager()->push(new PromptScreen(promptMessage, di->T("Yes"), di->T("No"), std::bind(&GameSettingsScreen::CallbackMemstickFolder, this, std::placeholders::_1))); - } -#endif - } -} - #if PPSSPP_PLATFORM(ANDROID) void GameSettingsScreen::CallbackMemstickFolder(bool yes) { auto sy = GetI18NCategory("System"); @@ -1289,86 +1277,73 @@ UI::EventReturn GameSettingsScreen::OnAudioDevice(UI::EventParams &e) { } UI::EventReturn GameSettingsScreen::OnChangeQuickChat0(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) auto n = GetI18NCategory("Networking"); System_InputBoxGetString(n->T("Enter Quick Chat 1"), g_Config.sQuickChat0, [](bool result, const std::string &value) { if (result) { g_Config.sQuickChat0 = value; } }); -#elif defined(__ANDROID__) - System_SendMessage("inputbox", ("quickchat0:" + g_Config.sQuickChat0).c_str()); #endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat1(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) auto n = GetI18NCategory("Networking"); System_InputBoxGetString(n->T("Enter Quick Chat 2"), g_Config.sQuickChat1, [](bool result, const std::string &value) { if (result) { g_Config.sQuickChat1 = value; } }); -#elif defined(__ANDROID__) - System_SendMessage("inputbox", ("quickchat1:" + g_Config.sQuickChat1).c_str()); #endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat2(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) auto n = GetI18NCategory("Networking"); System_InputBoxGetString(n->T("Enter Quick Chat 3"), g_Config.sQuickChat2, [](bool result, const std::string &value) { if (result) { g_Config.sQuickChat2 = value; } }); -#elif defined(__ANDROID__) - System_SendMessage("inputbox", ("quickchat2:" + g_Config.sQuickChat2).c_str()); #endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat3(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) auto n = GetI18NCategory("Networking"); System_InputBoxGetString(n->T("Enter Quick Chat 4"), g_Config.sQuickChat3, [](bool result, const std::string &value) { if (result) { g_Config.sQuickChat3 = value; } }); -#elif defined(__ANDROID__) - System_SendMessage("inputbox", ("quickchat3:" + g_Config.sQuickChat3).c_str()); #endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeQuickChat4(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) auto n = GetI18NCategory("Networking"); System_InputBoxGetString(n->T("Enter Quick Chat 5"), g_Config.sQuickChat4, [](bool result, const std::string &value) { if (result) { g_Config.sQuickChat4 = value; } }); -#elif defined(__ANDROID__) - System_SendMessage("inputbox", ("quickchat4:" + g_Config.sQuickChat4).c_str()); #endif return UI::EVENT_DONE; } UI::EventReturn GameSettingsScreen::OnChangeNickname(UI::EventParams &e) { -#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) auto n = GetI18NCategory("Networking"); System_InputBoxGetString(n->T("Enter a new PSP nickname"), g_Config.sNickName, [](bool result, const std::string &value) { if (result) { g_Config.sNickName = StripSpaces(value); } }); -#elif defined(__ANDROID__) - // TODO: The return value is handled in NativeApp::inputbox_completed. This is horrific. - System_SendMessage("inputbox", ("nickname:" + g_Config.sNickName).c_str()); #endif return UI::EVENT_DONE; } @@ -1377,7 +1352,12 @@ UI::EventReturn GameSettingsScreen::OnChangeproAdhocServerAddress(UI::EventParam auto sy = GetI18NCategory("System"); #if defined(__ANDROID__) - System_SendMessage("inputbox", ("IP:" + g_Config.proAdhocServer).c_str()); + auto n = GetI18NCategory("Networking"); + System_InputBoxGetString(n->T("IP"), g_Config.proAdhocServer, [](bool result, const std::string &value) { + if (result) { + g_Config.proAdhocServer = value; + } + }); #else screenManager()->push(new HostnameSelectScreen(&g_Config.proAdhocServer, sy->T("proAdhocServer Address:"))); #endif diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 6f56b2fbcb..7a82b7f463 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -34,7 +34,6 @@ public: void update() override; void onFinish(DialogResult result) override; - void sendMessage(const char *message, const char *value) override; std::string tag() const override { return "settings"; } protected: diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index e47ff1117d..933a5605a2 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -159,13 +159,19 @@ struct PendingMessage { std::string value; }; +struct PendingInputBox { + std::function cb; + bool result; + std::string value; +}; + static std::mutex pendingMutex; static std::vector pendingMessages; +static std::vector pendingInputBoxes; static Draw::DrawContext *g_draw; static Draw::Pipeline *colorPipeline; static Draw::Pipeline *texColorPipeline; static UIContext *uiContext; -static std::vector inputboxValue; #ifdef _WIN32 WindowsAudioBackend *winAudioBackend; @@ -445,6 +451,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch setlocale( LC_ALL, "C" ); std::string user_data_path = savegame_dir; pendingMessages.clear(); + pendingInputBoxes.clear(); #ifdef IOS user_data_path += "/"; #endif @@ -1093,42 +1100,6 @@ void HandleGlobalMessage(const std::string &msg, const std::string &value) { if (msg == "inputDeviceConnected") { KeyMap::NotifyPadConnected(value); } - if (msg == "inputbox_completed") { - SplitString(value, ':', inputboxValue); - std::string setString = inputboxValue.size() > 1 ? inputboxValue[1] : ""; - if (inputboxValue[0] == "IP") - g_Config.proAdhocServer = setString; - else if (inputboxValue[0] == "nickname") - g_Config.sNickName = setString; - else if (inputboxValue[0] == "remoteiso_subdir") - g_Config.sRemoteISOSubdir = setString; - else if (inputboxValue[0] == "remoteiso_server") - g_Config.sLastRemoteISOServer = setString; - - if (inputboxValue[0] == "quickchat0") - g_Config.sQuickChat0 = setString; - if (inputboxValue[0] == "quickchat1") - g_Config.sQuickChat1 = setString; - if (inputboxValue[0] == "quickchat2") - g_Config.sQuickChat2 = setString; - if (inputboxValue[0] == "quickchat3") - g_Config.sQuickChat3 = setString; - if (inputboxValue[0] == "quickchat4") - g_Config.sQuickChat4 = setString; - if (inputboxValue[0] == "nickname") - g_Config.sNickName = setString; - if (inputboxValue[0] == "Chat") { - if (inputboxValue.size() > 2) - { - std::string chatString = value; - chatString.erase(0, 5); - sendChat(chatString); - } else { - sendChat(setString); - } - } - inputboxValue.clear(); - } if (msg == "bgImage_updated") { if (!value.empty()) { std::string dest = GetSysDirectory(DIRECTORY_SYSTEM) + (endsWithNoCase(value, ".jpg") ? "background.jpg" : "background.png"); @@ -1183,15 +1154,21 @@ void NativeUpdate() { PROFILE_END_FRAME(); std::vector toProcess; + std::vector inputToProcess; { std::lock_guard lock(pendingMutex); toProcess = std::move(pendingMessages); + inputToProcess = std::move(pendingInputBoxes); pendingMessages.clear(); + pendingInputBoxes.clear(); } - for (size_t i = 0; i < toProcess.size(); i++) { - HandleGlobalMessage(toProcess[i].msg, toProcess[i].value); - screenManager->sendMessage(toProcess[i].msg.c_str(), toProcess[i].value.c_str()); + for (const auto &item : toProcess) { + HandleGlobalMessage(item.msg, item.value); + screenManager->sendMessage(item.msg.c_str(), item.value.c_str()); + } + for (const auto &item : inputToProcess) { + item.cb(item.result, item.value); } g_DownloadManager.Update(); @@ -1338,6 +1315,15 @@ void NativeMessageReceived(const char *message, const char *value) { pendingMessages.push_back(pendingMessage); } +void NativeInputBoxReceived(std::function cb, bool result, const std::string &value) { + std::lock_guard lock(pendingMutex); + PendingInputBox pendingMessage; + pendingMessage.cb = cb; + pendingMessage.result = result; + pendingMessage.value = value; + pendingInputBoxes.push_back(pendingMessage); +} + void NativeResized() { // NativeResized can come from any thread so we just set a flag, then process it later. ILOG("NativeResized - setting flag"); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index 4f9955f678..654be8d6e7 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -520,12 +520,22 @@ void RemoteISOSettingsScreen::CreateViews() { } UI::EventReturn RemoteISOSettingsScreen::OnClickRemoteServer(UI::EventParams &e) { - System_SendMessage("inputbox", ("remoteiso_server:" + g_Config.sLastRemoteISOServer).c_str()); +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) + auto ri = GetI18NCategory("RemoteISO"); + System_InputBoxGetString(ri->T("Remote Server"), g_Config.sLastRemoteISOServer, [](bool result, const std::string &value) { + g_Config.sLastRemoteISOServer = value; + }); +#endif return UI::EVENT_DONE; } UI::EventReturn RemoteISOSettingsScreen::OnClickRemoteISOSubdir(UI::EventParams &e) { - System_SendMessage("inputbox", ("remoteiso_subdir:" + g_Config.sRemoteISOSubdir).c_str()); +#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(__ANDROID__) + auto ri = GetI18NCategory("RemoteISO"); + System_InputBoxGetString(ri->T("Remote Subdirectory"), g_Config.sRemoteISOSubdir, [](bool result, const std::string &value) { + g_Config.sRemoteISOSubdir = value; + }); +#endif return UI::EVENT_DONE; } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 0ceace3732..8c5995c574 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -123,6 +123,8 @@ static jmethodID postCommand; static jobject nativeActivity; static volatile bool exitRenderLoop; static bool renderLoopRunning; +static int inputBoxSequence = 1; +std::map> inputBoxCallbacks; static float dp_xscale = 1.0f; static float dp_yscale = 1.0f; @@ -572,6 +574,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { ILOG("Not shutting down renderer - not initialized"); } + inputBoxCallbacks.clear(); NativeShutdown(); VFSShutdown(); while (frameCommands.size()) @@ -667,6 +670,33 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv } } +void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function cb) { + int seq = inputBoxSequence++; + inputBoxCallbacks[seq] = cb; + + std::string serialized = StringFromFormat("%d:%s:%s", seq, title.c_str(), defaultValue.c_str()); + System_SendMessage("inputbox", serialized.c_str()); +} + +extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendInputBox(JNIEnv *env, jclass, jstring jseqID, jboolean result, jstring jvalue) { + std::string seqID = GetJavaString(env, jseqID); + std::string value = GetJavaString(env, jvalue); + + int seq = 0; + if (!TryParse(seqID, &seq)) { + ELOG("Invalid inputbox seqID value: %s", seqID.c_str()); + return; + } + + auto entry = inputBoxCallbacks.find(seq); + if (entry == inputBoxCallbacks.end()) { + ELOG("Did not find inputbox callback for %s, shutdown?", seqID.c_str()); + return; + } + + NativeInputBoxReceived(entry->second, result, value); +} + void UpdateRunLoopAndroid(JNIEnv *env) { NativeUpdate(); diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 700318133a..97a22748bc 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1169,8 +1169,8 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C return bld; } - // The return value is sent elsewhere. TODO in java, in SendMessage in C++. - public void inputBox(final String title, String defaultText, String defaultAction) { + // The return value is sent to C++ via seqID. + public void inputBox(final String seqID, final String title, String defaultText, String defaultAction) { final FrameLayout fl = new FrameLayout(this); final EditText input = new EditText(this); input.setGravity(Gravity.CENTER); @@ -1202,14 +1202,14 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C .setPositiveButton(defaultAction, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface d, int which) { - NativeApp.sendMessage("inputbox_completed", title + ":" + input.getText().toString()); + NativeApp.sendInputBox(seqID, true, input.getText().toString()); d.dismiss(); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface d, int which) { - NativeApp.sendMessage("inputbox_failed", ""); + NativeApp.sendInputBox(seqID, false, ""); d.cancel(); } }) @@ -1311,15 +1311,17 @@ public abstract class NativeActivity extends Activity implements SurfaceHolder.C inputMethodManager.toggleSoftInputFromWindow(surfView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; } else if (command.equals("inputbox")) { + String seqID = ""; String title = "Input"; String defString = ""; - String[] param = params.split(":"); - if (param[0].length() > 0) - title = param[0]; - if (param.length > 1) - defString = param[1]; - Log.i(TAG, "Launching inputbox: " + title + " " + defString); - inputBox(title, defString, "OK"); + String[] param = params.split(":", 3); + seqID = param[0]; + if (param.length > 1 && param[1].length() > 0) + title = param[1]; + if (param.length > 2) + defString = param[2]; + Log.i(TAG, "Launching inputbox: #" + seqID + " " + title + " " + defString); + inputBox(seqID, title, defString, "OK"); return true; } else if (command.equals("vibrate")) { int milliseconds = -1; diff --git a/android/src/org/ppsspp/ppsspp/NativeApp.java b/android/src/org/ppsspp/ppsspp/NativeApp.java index fec8c5c002..46598c8263 100644 --- a/android/src/org/ppsspp/ppsspp/NativeApp.java +++ b/android/src/org/ppsspp/ppsspp/NativeApp.java @@ -48,6 +48,7 @@ public class NativeApp { public static native boolean accelerometer(float x, float y, float z); public static native void sendMessage(String msg, String arg); + public static native void sendInputBox(String seqID, boolean result, String value); public static native String queryConfig(String queryName); diff --git a/ext/native/base/NativeApp.h b/ext/native/base/NativeApp.h index 2af0c90ce9..c5c56206cd 100644 --- a/ext/native/base/NativeApp.h +++ b/ext/native/base/NativeApp.h @@ -35,6 +35,9 @@ void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, boo // Generic host->C++ messaging, used for functionality like system-native popup input boxes. void NativeMessageReceived(const char *message, const char *value); +// This is used to communicate back and thread requested input box strings. +void NativeInputBoxReceived(std::function cb, bool result, const std::string &value); + // Easy way for the Java side to ask the C++ side for configuration options, such as // the rotation lock which must be controlled from Java on Android. // It is currently not called on non-Android platforms.