diff --git a/Common/System/Request.cpp b/Common/System/Request.cpp index 858cf688f5..fa003fea9b 100644 --- a/Common/System/Request.cpp +++ b/Common/System/Request.cpp @@ -50,6 +50,7 @@ const char *RequestTypeAsString(SystemRequestType type) { case SystemRequestType::MOVE_TO_TRASH: return "MOVE_TO_TRASH"; case SystemRequestType::IAP_RESTORE_PURCHASES: return "IAP_RESTORE_PURCHASES"; case SystemRequestType::IAP_MAKE_PURCHASE: return "IAP_MAKE_PURCHASE"; + case SystemRequestType::SET_SOFT_KEYBOARD_FORCED: return "SET_SOFT_KEYBOARD_FORCED"; default: return "N/A"; } } diff --git a/Common/System/Request.h b/Common/System/Request.h index d55a450e81..82ca7f7556 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -208,6 +208,14 @@ enum class TextInputUser { }; void System_SetTextInputWanted(TextInputUser user, bool wanted); +// Keeps the soft keyboard up (typing as key and char events) until called with false. With keepAfterAppSwitch, +// it comes back after switching to another app and back, otherwise the platform drops it then. When the platform +// closes it, it posts UIMessage::SOFT_KEYBOARD_HIDDEN. Only Android implements this so far, though other platforms +// with SYSPROP_KEYBOARD_IS_SOFT should. +inline void System_SetSoftKeyboardForced(bool forced, bool keepAfterAppSwitch) { + g_requestManager.MakeSystemRequest(SystemRequestType::SET_SOFT_KEYBOARD_FORCED, NO_REQUESTER_TOKEN, nullptr, nullptr, "", "", (int64_t)forced, (int64_t)keepAfterAppSwitch); +} + inline void System_SetKeepScreenBright(bool keepScreenBright) { g_requestManager.MakeSystemRequest(SystemRequestType::SET_KEEP_SCREEN_BRIGHT, NO_REQUESTER_TOKEN, nullptr, nullptr, "", "", (int64_t)keepScreenBright); } diff --git a/Common/System/System.h b/Common/System/System.h index eaa90e19b2..085bc947f0 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -105,6 +105,8 @@ enum class SystemRequestType { IAP_MAKE_PURCHASE, OPEN_DISPLAY_SETTINGS, + + SET_SOFT_KEYBOARD_FORCED, }; // Run a closure on the main thread. Used to safely implement UI that runs on another thread. @@ -324,6 +326,7 @@ enum class UIMessage { SAVE_FRAME_DUMP, ADHOC_SERVER_LIST_CHANGED, SAVESTATE_SLOT_SAVED, // A slot's save state file was written. + SOFT_KEYBOARD_HIDDEN, // The platform closed a soft keyboard forced by System_SetSoftKeyboardForced. }; std::string System_GetProperty(SystemProperty prop); diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 019f85995d..9636ebbac4 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1,5 +1,4 @@ #include -#include #include #include "ppsspp_config.h" @@ -87,28 +86,6 @@ static constexpr bool kIsMobilePlatform = true; static constexpr bool kIsMobilePlatform = false; #endif -// Defined further down (see ImDebugger_SetSoftKeyboardHook). -static std::function g_softKeyboardHook; - -#if PPSSPP_PLATFORM(ANDROID) -void Android_SetKeyboardForced(bool forced); // Defined in android/jni/app-android.cpp. -void Android_SetKeyboardPersist(bool persist); // Whether the keyboard comes back after an app switch. -bool Android_ConsumeAppPaused(); // True once after the app was paused (switched away from). -bool Android_ConsumeKeyboardDismissed(); // True once after the user closed the keyboard with Back. -#endif - -// Shows or hides the soft keyboard. A hook set through ImDebugger_SetSoftKeyboardHook takes priority; otherwise -// Android uses its built-in implementation, and other platforms do nothing. -static void ApplySoftKeyboard(bool show) { - if (g_softKeyboardHook) { - g_softKeyboardHook(show); - return; - } -#if PPSSPP_PLATFORM(ANDROID) - Android_SetKeyboardForced(show); -#endif -} - void ShowInMemoryViewerMenuItem(uint32_t addr, ImControl &control) { if (ImGui::BeginMenu("Show in memory viewer")) { for (int i = 0; i < 4; i++) { @@ -2688,12 +2665,27 @@ ImDebugger::ImDebugger() { } ImDebugger::~ImDebugger() { - if (keyboardShown_) { - ApplySoftKeyboard(false); - } + UpdateSoftKeyboard(false); SaveConfig(); } +// Called every frame. The platform is only told about changes. +void ImDebugger::UpdateSoftKeyboard(bool debuggerVisible) { + // Hiding the toggle turns the keyboard off too, otherwise there'd be no way to dismiss it. + const bool show = debuggerVisible && cfg_.forceKeyboard && cfg_.showKeyboardToggle; + if (show == keyboardShown_ && (!show || cfg_.keyboardPersist == keyboardPersistSent_)) { + return; + } + keyboardShown_ = show; + keyboardPersistSent_ = cfg_.keyboardPersist; + System_SetSoftKeyboardForced(show, cfg_.keyboardPersist); +} + +void ImDebugger::OnHidden() { + cfg_.forceKeyboard = false; // So the keyboard doesn't pop back up when the debugger is reopened. + UpdateSoftKeyboard(false); +} + void ImDebugger::SaveConfig() { cfg_.SaveConfig(ConfigPath()); } @@ -2703,15 +2695,6 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: const CoreState coreState = ::coreState; const bool showDebugger = g_Config.bShowImDebugger && PSP_IsInited(); // This will be separated from showing the IM UI in general. - // The debugger got hidden some other way (hotkey, settings...): drop the keyboard so it's off when reopened. - if (!g_Config.bShowImDebugger && cfg_.forceKeyboard) { - cfg_.forceKeyboard = false; - if (keyboardShown_) { - keyboardShown_ = false; - ApplySoftKeyboard(false); - } - } - const bool allowSaveStates = !Achievements::HardcoreModeActive() && PSP_IsInited() && (!IsNetworkConnected() || g_Config.bAllowSavestateWhileConnected); if (Achievements::HardcoreModeActive()) { @@ -2756,6 +2739,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: const float em = ImGui::GetFontSize(); const bool compactMenu = cfg_.compactMenu || ImGui::GetIO().DisplaySize.x < em * 46.0f; const float rowExtra = compactMenu ? em * 0.2f : 0.0f; // Slightly taller rows for fingers. + const bool hasSoftKeyboard = System_GetPropertyBool(SYSPROP_KEYBOARD_IS_SOFT); auto beginMenu = [&](const char *label) -> bool { if (!ImGui::BeginMenu(label)) { @@ -2771,11 +2755,6 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: }; auto closeDebugger = [&]() { g_Config.bShowImDebugger = false; - cfg_.forceKeyboard = false; // So the keyboard doesn't pop back up when the debugger is reopened. - if (keyboardShown_) { - keyboardShown_ = false; - ApplySoftKeyboard(false); - } }; auto menuFile = [&]() { @@ -3048,8 +3027,10 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: ImGui::MenuItem("Dear ImGui Style editor", nullptr, &cfg_.styleEditorOpen); ImGui::Separator(); ImGui::MenuItem("Quick scroll bar", nullptr, &cfg_.quickScrollEnabled); - ImGui::MenuItem("Show keyboard toggle", nullptr, &cfg_.showKeyboardToggle); - ImGui::MenuItem("Keep keyboard after switching apps", nullptr, &cfg_.keyboardPersist); + if (hasSoftKeyboard) { + ImGui::MenuItem("Show keyboard toggle", nullptr, &cfg_.showKeyboardToggle); + ImGui::MenuItem("Keep keyboard after switching apps", nullptr, &cfg_.keyboardPersist); + } ImGui::MenuItem("Compact menu", nullptr, &cfg_.compactMenu); ImGui::SetNextItemWidth(em * 10.0f); ImGui::SliderInt("Bar left margin", &cfg_.barLeftMargin, 0, 600, "%d%%"); // Percent of the text height. @@ -3120,38 +3101,14 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: } // Soft keyboard toggle (like the keyboard button in Winlator). - if (cfg_.showKeyboardToggle) { + if (hasSoftKeyboard && cfg_.showKeyboardToggle) { ImGui::MenuItem(cfg_.forceKeyboard ? "Hide Keyboard" : "Show Keyboard", nullptr, &cfg_.forceKeyboard); } ImGui::EndMainMenuBar(); } - // Force the soft keyboard to show while the debugger is open (the platform side is a hook, see ImDebugger.h). - { -#if PPSSPP_PLATFORM(ANDROID) - // If the keyboard shouldn't survive an app switch, turn it off once we've been away. - if (Android_ConsumeAppPaused() && !cfg_.keyboardPersist) { - cfg_.forceKeyboard = false; - } - // The keyboard was closed from the system (Back button): keep the toggle in sync. - if (Android_ConsumeKeyboardDismissed()) { - cfg_.forceKeyboard = false; - } - const int persist = cfg_.keyboardPersist ? 1 : 0; - if (persist != keyboardPersistSent_) { - keyboardPersistSent_ = persist; - Android_SetKeyboardPersist(persist != 0); - } -#endif - // Hiding the toggle turns the keyboard off too, otherwise there'd be no way to dismiss it. - const bool wantKeyboard = cfg_.forceKeyboard && cfg_.showKeyboardToggle && g_Config.bShowImDebugger; - if (wantKeyboard != keyboardShown_) { - keyboardShown_ = wantKeyboard; - ApplySoftKeyboard(wantKeyboard); - } - } - + UpdateSoftKeyboard(true); if (cfg_.demoOpen) { ImGui::ShowDemoWindow(&cfg_.demoOpen); @@ -3382,17 +3339,11 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUCommon *gpuDebug, Draw: luaConsole_.Draw(cfg_); } - if (g_Config.bShowImDebugger && cfg_.quickScrollEnabled) { + if (cfg_.quickScrollEnabled) { DrawQuickScroll(); } } -// Platform hook for forcing the soft keyboard. Left empty by default (no-op), so this builds on every platform. -// Android: set it from native code to call InputMethodManager.showSoftInput / hideSoftInputFromWindow via the activity. -void ImDebugger_SetSoftKeyboardHook(std::function hook) { - g_softKeyboardHook = std::move(hook); -} - // ---- Quick scroll strip ---- // The window to scroll, or the nearest parent that actually has something to scroll. diff --git a/UI/ImDebugger/ImDebugger.h b/UI/ImDebugger/ImDebugger.h index 3ae68e1292..bf9a437bd4 100644 --- a/UI/ImDebugger/ImDebugger.h +++ b/UI/ImDebugger/ImDebugger.h @@ -3,7 +3,6 @@ #include #include #include -#include #include "ext/imgui/imgui.h" @@ -135,7 +134,7 @@ struct ImConfig { // Touch / mobile helpers. bool quickScrollEnabled = true; // Show the quick scroll strip on the right edge. bool quickScrollCollapsed = false; // Strip collapsed to a single small button. - bool forceKeyboard = false; // Keyboard currently active (toggled from the bar). Asks the platform to show the soft keyboard (see ImDebugger_SetSoftKeyboardHook). + bool forceKeyboard = false; // Keyboard currently active (toggled from the bar). Asks the platform to keep the soft keyboard up. bool showKeyboardToggle = true; // Show the keyboard button in the bar. Hiding it also turns the keyboard off. bool keyboardPersist = true; // Keep the keyboard on after switching to another app and back. int barLeftMargin = 0; // Empty space left of the first bar button, in percent of the text height (keeps it clear of rounded screen corners). @@ -202,9 +201,17 @@ public: void DeviceLost(); void SaveConfig(); + // Call every frame the debugger isn't shown, instead of Frame(). + void OnHidden(); + // The platform closed the forced soft keyboard (Back, or an app switch without "keep keyboard"). + void OnSoftKeyboardHidden() { + cfg_.forceKeyboard = false; + } + private: Path ConfigPath(); void DrawQuickScroll(); + void UpdateSoftKeyboard(bool debuggerVisible); RequesterToken reqToken_; @@ -236,14 +243,9 @@ private: ImGuiID quickScrollTarget_ = 0; // Whether we last asked the platform to show the soft keyboard. bool keyboardShown_ = false; - int keyboardPersistSent_ = -1; // Last "keep keyboard after app switch" value sent to the platform. + bool keyboardPersistSent_ = false; }; -// Optional override for the "force keyboard" toggle. The callback is called with true to show the soft keyboard and -// false to hide it. Android already has a built-in implementation (Android_SetKeyboardForced in app-android.cpp), -// so this is only needed on other platforms. -void ImDebugger_SetSoftKeyboardHook(std::function hook); - // Simple custom controls and utilities. void ImClickableValue(const char *id, uint32_t value, ImControl &control, ImCmd cmd); void ImClickableValueFloat(const char *id, float value); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 0bdce0d71f..aa04513b1c 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -440,6 +440,9 @@ void runImDebugger(Draw::DrawContext *draw) { } else { uiContext->SetOverrideScreenFrame(nullptr); SetOverrideScreenFrame(nullptr); + if (imDebugger_) { + imDebugger_->OnHidden(); + } } // Enable text input (needed for KeyInputFlags::CHAR events) only while an imgui text field is active. @@ -1519,6 +1522,11 @@ bool HandleGlobalMessage(UIMessage message, const std::string &value) { } else if (message == UIMessage::SAVE_FRAME_DUMP) { SaveFrameDump(); return true; + } else if (message == UIMessage::SOFT_KEYBOARD_HIDDEN) { + if (imDebugger_) { + imDebugger_->OnSoftKeyboardHidden(); + } + return true; } else { return false; } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index bda0f747f4..4778870450 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -313,31 +313,6 @@ void System_ShowKeyboard() { PushCommand("showKeyboard", ""); } -// Used by the ImDebugger "Kbd" toggle. Unlike System_ShowKeyboard, this keeps the keyboard up until turned off, -// and lets it type into the game view (see NativeSurfaceView.setKeyboardForced). -void Android_SetKeyboardForced(bool forced) { - PushCommand("forceKeyboard", forced ? "1" : "0"); -} - -// Whether the forced keyboard comes back by itself after switching to another app and returning. -void Android_SetKeyboardPersist(bool persist) { - PushCommand("keyboardPersist", persist ? "1" : "0"); -} - -// Set by NativeApp.pause(), read (and cleared) by the ImDebugger so it can drop the keyboard after an app switch. -static std::atomic g_appWasPaused{false}; - -bool Android_ConsumeAppPaused() { - return g_appWasPaused.exchange(false); -} - -// Set when the user closed the soft keyboard with the Back button, so the debugger can untick its toggle. -static std::atomic g_keyboardDismissed{false}; - -bool Android_ConsumeKeyboardDismissed() { - return g_keyboardDismissed.exchange(false); -} - void System_Vibrate(int length_ms) { char temp[32]; snprintf(temp, sizeof(temp), "%d", length_ms); @@ -509,6 +484,9 @@ bool System_GetPropertyBool(SystemProperty prop) { } case SYSPROP_HAS_KEYBOARD: return deviceType != DEVICE_TYPE_VR; + case SYSPROP_KEYBOARD_IS_SOFT: + // Even with a hardware keyboard connected, the soft one can still be shown. + return true; case SYSPROP_HAS_ACCELEROMETER: // A headset has the sensors, but tilting it isn't tilt control. return g_hasAccelerometer && deviceType != DEVICE_TYPE_VR; @@ -944,7 +922,6 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *, jclass) { extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) { INFO_LOG(Log::System, "NativeApp.pause() - begin"); - g_appWasPaused = true; AndroidAudio_Pause(g_audioState); INFO_LOG(Log::System, "NativeApp.pause() - end"); } @@ -1221,6 +1198,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string // The MIME type first, as a path could contain the separator. PushCommand("share_file", param2 + ":@:" + param1); return true; + case SystemRequestType::SET_SOFT_KEYBOARD_FORCED: + PushCommand("forceKeyboard", !param3 ? "off" : (param4 ? "persist" : "on")); + return true; case SystemRequestType::SET_KEEP_SCREEN_BRIGHT: PushCommand("set_keep_screen_bright", param3 ? "on" : "off"); return true; @@ -1469,8 +1449,8 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessageFromJava(JNI } else if (msg == "permission_granted") { SetPermissionStatusFromJava(prm, PERMISSION_STATUS_GRANTED); System_PostUIMessage(UIMessage::PERMISSION_GRANTED, prm); - } else if (msg == "keyboardDismissed") { - g_keyboardDismissed = true; + } else if (msg == "softKeyboardHidden") { + System_PostUIMessage(UIMessage::SOFT_KEYBOARD_HIDDEN); } else if (msg == "sustained_perf_supported") { sustainedPerfSupported = true; } else if (msg == "safe_insets") { diff --git a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java index 59b21469a2..06d518d5b1 100644 --- a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java +++ b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java @@ -22,7 +22,7 @@ public class NativeSurfaceView extends SurfaceView { // (a plain SurfaceView is ignored by showSoftInput), and typed text is passed on to the native side. private boolean keyboardForced = false; // Bring the keyboard back by itself when the app regains focus (after an app switch). - private boolean keyboardPersist = true; + private boolean keyboardPersist = false; public NativeSurfaceView(Context context) { super(context); @@ -38,13 +38,10 @@ public class NativeSurfaceView extends SurfaceView { return true; } - public void setKeyboardPersist(boolean persist) { - keyboardPersist = persist; - } - // Must be called on the UI thread. - public void setKeyboardForced(boolean forced) { + public void setKeyboardForced(boolean forced, boolean persist) { keyboardForced = forced; + keyboardPersist = persist; InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (imm == null) { return; @@ -88,11 +85,19 @@ public class NativeSurfaceView extends SurfaceView { @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (keyboardForced && keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) { - NativeApp.sendMessageFromJava("keyboardDismissed", ""); + NativeApp.sendMessageFromJava("softKeyboardHidden", ""); } return super.onKeyPreIme(keyCode, event); } + // Called from the activity's onStop. Without persistence, switching to another app ends the forced keyboard. + public void onActivityStopped() { + if (keyboardForced && !keyboardPersist) { + setKeyboardForced(false, false); + NativeApp.sendMessageFromJava("softKeyboardHidden", ""); + } + } + @Override public boolean onCheckIsTextEditor() { return keyboardForced; diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index effd12aea6..006866e06c 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -1210,6 +1210,10 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList mSensorManager.unregisterListener(this); loseAudioFocus(this.audioManager, this.audioFocusChangeListener); + if (mSurfaceView != null) { + mSurfaceView.onActivityStopped(); + } + // After a finish() and a quick relaunch, this comes after the new activity's onStart, and the audio and // the location updates are its now. if (checkCurrent("pausing audio and location in onStop")) { @@ -1845,12 +1849,9 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList inputMethodManager.hideSoftInputFromWindow(surfView.getWindowToken(), 0); return true; } else if (command.equals("forceKeyboard") && mSurfaceView != null) { - // "1" = keep the soft keyboard up (and let it type into the game), "0" = hide it again. - mSurfaceView.setKeyboardForced(params.equals("1")); - return true; - } else if (command.equals("keyboardPersist") && mSurfaceView != null) { - // "1" = bring the forced keyboard back after an app switch, "0" = don't. - mSurfaceView.setKeyboardPersist(params.equals("1")); + // "on" = keep the soft keyboard up (and let it type into the game), "persist" = also bring it back after + // an app switch, "off" = hide it again. + mSurfaceView.setKeyboardForced(!params.equals("off"), params.equals("persist")); return true; } else if (command.equals("inputbox")) { String title = "Input";