From 03fc86a1475851ae315224c0b4704e3fb48b9661 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 9 May 2020 13:52:04 -0700 Subject: [PATCH] UI: Return to game settings after restart. This is more convenient. Also restarts the game if needed. Added a way to pass args to Android here as well, and fixed displaying an error if the shortcut file doesn't exist on Android (like if you deleted it after creating the shortcut.) --- UI/GameSettingsScreen.cpp | 27 ++++--- UI/GameSettingsScreen.h | 2 + UI/MiscScreens.cpp | 9 ++- UI/MiscScreens.h | 9 +-- UI/NativeApp.cpp | 26 +++++-- Windows/W32Util/Misc.cpp | 11 ++- Windows/W32Util/Misc.h | 2 +- Windows/main.cpp | 20 ++++-- android/jni/app-android.cpp | 72 +++++++++++++++++-- .../src/org/ppsspp/ppsspp/NativeActivity.java | 8 ++- .../src/org/ppsspp/ppsspp/PpssppActivity.java | 10 ++- ext/native/ui/screen.cpp | 50 ++++++------- ext/native/ui/screen.h | 2 +- 13 files changed, 183 insertions(+), 65 deletions(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index c597a68394..f11be70585 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1195,14 +1195,25 @@ void GameSettingsScreen::CallbackMemstickFolder(bool yes) { } #endif +void GameSettingsScreen::TriggerRestart(const char *why) { + // Extra save here to make sure the choice really gets saved even if there are shutdown bugs in + // the GPU backend code. + g_Config.Save(why); + std::string param = "--gamesettings"; + if (editThenRestore_) { + // We won't pass the gameID, so don't resume back into settings. + param = ""; + } else if (!gamePath_.empty()) { + param += " \"" + ReplaceAll(ReplaceAll(gamePath_, "\\", "\\\\"), "\"", "\\\"") + "\""; + } + System_SendMessage("graphics_restart", param.c_str()); +} + void GameSettingsScreen::CallbackRenderingBackend(bool yes) { // If the user ends up deciding not to restart, set the config back to the current backend // so it doesn't get switched by accident. if (yes) { - // Extra save here to make sure the choice really gets saved even if there are shutdown bugs in - // the GPU backend code. - g_Config.Save("GameSettingsScreen::RenderingBackendYes"); - System_SendMessage("graphics_restart", ""); + TriggerRestart("GameSettingsScreen::RenderingBackendYes"); } else { g_Config.iGPUBackend = (int)GetGPUBackend(); } @@ -1212,10 +1223,7 @@ void GameSettingsScreen::CallbackRenderingDevice(bool yes) { // If the user ends up deciding not to restart, set the config back to the current backend // so it doesn't get switched by accident. if (yes) { - // Extra save here to make sure the choice really gets saved even if there are shutdown bugs in - // the GPU backend code. - g_Config.Save("GameSettingsScreen::RenderingDeviceYes"); - System_SendMessage("graphics_restart", ""); + TriggerRestart("GameSettingsScreen::RenderingDeviceYes"); } else { std::string *deviceNameSetting = GPUDeviceNameSetting(); if (deviceNameSetting) @@ -1227,8 +1235,7 @@ void GameSettingsScreen::CallbackRenderingDevice(bool yes) { void GameSettingsScreen::CallbackInflightFrames(bool yes) { if (yes) { - g_Config.Save("GameSettingsScreen::InflightFramesYes"); - System_SendMessage("graphics_restart", ""); + TriggerRestart("GameSettingsScreen::InflightFramesYes"); } else { g_Config.iInflightFrames = prevInflightFrames_; } diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 2cf38a3a36..cfcd7fae27 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -48,6 +48,8 @@ protected: bool UseVerticalLayout() const; private: + void TriggerRestart(const char *why); + std::string gameID_; bool lastVertical_; UI::CheckBox *enableReportsCheckbox_; diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 8888df4b55..27fadc85fd 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -420,7 +420,14 @@ void NewLanguageScreen::OnCompleted(DialogResult result) { void LogoScreen::Next() { if (!switched_) { switched_ = true; - if (boot_filename.size()) { + if (gotoGameSettings_) { + if (boot_filename.size()) { + screenManager()->switchScreen(new EmuScreen(boot_filename)); + } else { + screenManager()->switchScreen(new MainScreen()); + } + screenManager()->push(new GameSettingsScreen(boot_filename)); + } else if (boot_filename.size()) { screenManager()->switchScreen(new EmuScreen(boot_filename)); } else { screenManager()->switchScreen(new MainScreen()); diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index 34b0e0f40b..459974de48 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -114,8 +114,8 @@ private: class LogoScreen : public UIScreen { public: - LogoScreen() - : frames_(0), switched_(false) {} + LogoScreen(bool gotoGameSettings = false) + : gotoGameSettings_(gotoGameSettings) {} bool key(const KeyInput &key) override; bool touch(const TouchInput &touch) override; void update() override; @@ -125,8 +125,9 @@ public: private: void Next(); - int frames_; - bool switched_; + int frames_ = 0; + bool switched_ = false; + bool gotoGameSettings_ = false; }; class CreditsScreen : public UIDialogScreenWithBackground { diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index b71f30a90f..3c29f900b2 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -92,17 +92,18 @@ #include "Core/WebServer.h" #include "GPU/GPUInterface.h" +#include "UI/BackgroundAudio.h" +#include "UI/ControlMappingScreen.h" +#include "UI/DiscordIntegration.h" #include "UI/EmuScreen.h" #include "UI/GameInfoCache.h" +#include "UI/GPUDriverTestScreen.h" #include "UI/HostTypes.h" -#include "UI/OnScreenDisplay.h" #include "UI/MiscScreens.h" +#include "UI/OnScreenDisplay.h" #include "UI/RemoteISOScreen.h" #include "UI/TiltEventProcessor.h" -#include "UI/BackgroundAudio.h" #include "UI/TextureUtil.h" -#include "UI/DiscordIntegration.h" -#include "UI/GPUDriverTestScreen.h" #if !defined(MOBILE_DEVICE) #include "Common/KeyMap.h" @@ -551,6 +552,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch const char *stateToLoad = 0; bool gotBootFilename = false; + bool gotoGameSettings = false; + bool gotoTouchScreenTest = false; boot_filename = ""; // Parse command line @@ -593,6 +596,10 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch g_Config.bPauseMenuExitsEmulator = true; if (!strcmp(argv[i], "--fullscreen")) g_Config.bFullScreen = true; + if (!strcmp(argv[i], "--touchscreentest")) + gotoTouchScreenTest = true; + if (!strcmp(argv[i], "--gamesettings")) + gotoGameSettings = true; break; } } else { @@ -628,7 +635,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch std::unique_ptr fileLoader(ConstructFileLoader(boot_filename)); if (!fileLoader->Exists()) { fprintf(stderr, "File not found: %s\n", boot_filename.c_str()); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__ANDROID__) // Ignore and proceed. #else // Bail. @@ -638,7 +645,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } } else { fprintf(stderr, "Can only boot one file"); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__ANDROID__) // Ignore and proceed. #else // Bail. @@ -711,7 +718,12 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } screenManager = new ScreenManager(); - if (skipLogo) { + if (gotoGameSettings) { + screenManager->switchScreen(new LogoScreen(true)); + } else if (gotoTouchScreenTest) { + screenManager->switchScreen(new MainScreen()); + screenManager->push(new TouchTestScreen()); + } else if (skipLogo) { screenManager->switchScreen(new EmuScreen(boot_filename)); } else { screenManager->switchScreen(new LogoScreen()); diff --git a/Windows/W32Util/Misc.cpp b/Windows/W32Util/Misc.cpp index ca26ad7a4b..3bb86f6bf6 100644 --- a/Windows/W32Util/Misc.cpp +++ b/Windows/W32Util/Misc.cpp @@ -153,13 +153,20 @@ namespace W32Util moduleFilename.resize(sz); } - void ExitAndRestart() { + void ExitAndRestart(bool overrideArgs, const std::string &args) { // This preserves arguments (for example, config file) and working directory. std::wstring workingDirectory; std::wstring moduleFilename; GetSelfExecuteParams(workingDirectory, moduleFilename); - const wchar_t *cmdline = RemoveExecutableFromCommandLine(GetCommandLineW()); + const wchar_t *cmdline; + std::wstring wargs; + if (overrideArgs) { + wargs = ConvertUTF8ToWString(args); + cmdline = wargs.c_str(); + } else { + cmdline = RemoveExecutableFromCommandLine(GetCommandLineW()); + } ShellExecute(nullptr, nullptr, moduleFilename.c_str(), cmdline, workingDirectory.c_str(), SW_SHOW); ExitProcess(0); diff --git a/Windows/W32Util/Misc.h b/Windows/W32Util/Misc.h index 680a48c62e..623de73233 100644 --- a/Windows/W32Util/Misc.h +++ b/Windows/W32Util/Misc.h @@ -11,7 +11,7 @@ namespace W32Util BOOL CopyTextToClipboard(HWND hwnd, const char *text); BOOL CopyTextToClipboard(HWND hwnd, const std::wstring &wtext); void MakeTopMost(HWND hwnd, bool topMost); - void ExitAndRestart(); + void ExitAndRestart(bool overrideArgs = false, const std::string &args = ""); void GetSelfExecuteParams(std::wstring &workingDirectory, std::wstring &moduleFilename); } diff --git a/Windows/main.cpp b/Windows/main.cpp index 73938e6093..3795597e26 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -96,6 +96,8 @@ static std::string langRegion; static std::string osName; static std::string gpuDriverVersion; +static std::string restartArgs; + HMENU g_hPopupMenus; int g_activeWindow = 0; @@ -283,6 +285,7 @@ void System_SendMessage(const char *command, const char *parameter) { PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0); } } else if (!strcmp(command, "graphics_restart")) { + restartArgs = parameter == nullptr ? "" : parameter; if (IsDebuggerPresent()) { PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_RESTART_EMUTHREAD, 0, 0); } else { @@ -415,7 +418,14 @@ static bool DetectVulkanInExternalProcess() { std::vector GetWideCmdLine() { wchar_t **wargv; int wargc = -1; - wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); + // This is used for the WM_USER_RESTART_EMUTHREAD path. + if (!restartArgs.empty()) { + std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + restartArgs); + wargv = CommandLineToArgvW(wargs.c_str(), &wargc); + restartArgs.clear(); + } else { + wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); + } std::vector wideArgs(wargv, wargv + wargc); LocalFree(wargv); @@ -447,12 +457,12 @@ static void WinMainInit() { } static void WinMainCleanup() { - if (g_Config.bRestartRequired) { - W32Util::ExitAndRestart(); - } - net::Shutdown(); CoUninitialize(); + + if (g_Config.bRestartRequired) { + W32Util::ExitAndRestart(!restartArgs.empty(), restartArgs); + } } int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) { diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 88b66a00e9..94105f01cd 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -399,6 +399,60 @@ extern "C" jstring Java_org_ppsspp_ppsspp_NativeApp_queryConfig return jresult; } +static void parse_args(std::vector &args, const std::string value) { + // Simple argument parser so we can take args from extra params. + const char *p = value.c_str(); + + while (*p != '\0') { + while (isspace(*p)) { + p++; + } + if (*p == '\0') { + break; + } + + bool done = false; + bool quote = false; + std::string arg; + + while (!done) { + size_t sz = strcspn(p, "\"\\ \r\n\t"); + arg += std::string(p, sz); + p += sz; + + switch (*p) { + case '"': + quote = !quote; + p++; + break; + + case '\\': + p++; + arg += std::string(p, 1); + p++; + break; + + case '\0': + done = true; + break; + + default: + // If it's not the above, it's whitespace. + if (!quote) { + done = true; + } + break; + } + } + + args.push_back(arg); + + while (isspace(*p)) { + p++; + } + } +} + extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init (JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath, jstring jdataDir, jstring jexternalDir, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam, @@ -455,18 +509,22 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version); - // If shortcut_param is not empty, pass it as additional varargs argument to NativeInit() method. + // If shortcut_param is not empty, pass it as additional arguments to the NativeInit() method. // NativeInit() is expected to treat extra argument as boot_filename, which in turn will start game immediately. // NOTE: Will only work if ppsspp started from Activity.onCreate(). Won't work if ppsspp app start from onResume(). - if (shortcut_param.empty()) { - const char *argv[2] = {app_name.c_str(), 0}; - NativeInit(1, argv, user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); - } else { - const char *argv[3] = {app_name.c_str(), shortcut_param.c_str(), 0}; - NativeInit(2, argv, user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); + std::vector args; + std::vector temp; + args.push_back(app_name.c_str()); + if (!shortcut_param.empty()) { + parse_args(temp, shortcut_param); + for (const auto &arg : temp) { + args.push_back(arg.c_str()); + } } + NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalDir.c_str(), cacheDir.c_str()); + retry: // Now that we've loaded config, set javaGL. javaGL = NativeQueryConfig("androidJavaGL") == "true"; diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 9bfc178cd9..be62d9abdd 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -75,6 +75,7 @@ public abstract class NativeActivity extends Activity { protected NativeRenderer nativeRenderer; private String shortcutParam = ""; + private static String overrideShortcutParam = null; public static String runCommand; public static String commandParameter; @@ -302,9 +303,11 @@ public abstract class NativeActivity extends Activity { String model = Build.MANUFACTURER + ":" + Build.MODEL; String languageRegion = Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry(); + String shortcut = overrideShortcutParam == null ? shortcutParam : overrideShortcutParam; + overrideShortcutParam = null; NativeApp.audioConfig(optimalFramesPerBuffer, optimalSampleRate); - NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, externalStorageDir, libraryDir, cacheDir, shortcutParam, Build.VERSION.SDK_INT, Build.BOARD); + NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, externalStorageDir, libraryDir, cacheDir, shortcut, Build.VERSION.SDK_INT, Build.BOARD); // Allow C++ to tell us to use JavaGL or not. javaGL = "true".equalsIgnoreCase(NativeApp.queryConfig("androidJavaGL")); @@ -1254,6 +1257,9 @@ public abstract class NativeActivity extends Activity { recreate(); } else if (command.equals("graphics_restart")) { Log.i(TAG, "graphics_restart"); + if (params != null && !params.equals("")) { + overrideShortcutParam = params; + } shuttingDown = true; recreate(); } else if (command.equals("ask_permission") && params.equals("storage")) { diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index f6496168b8..4269f7f98b 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -12,6 +12,8 @@ public class PpssppActivity extends NativeActivity { private static final String TAG = "PpssppActivity"; // Key used by shortcut. public static final String SHORTCUT_EXTRA_KEY = "org.ppsspp.ppsspp.Shortcuts"; + // Key used for debugging. + public static final String ARGS_EXTRA_KEY = "org.ppsspp.ppsspp.Args"; private static boolean m_hasUnsupportedABI = false; private static boolean m_hasNoNativeBinary = false; @@ -78,14 +80,18 @@ public class PpssppActivity extends NativeActivity { if (data != null) { String path = intent.getData().getPath(); Log.i(TAG, "Found Shortcut Parameter in data: " + path); - super.setShortcutParam(path); + super.setShortcutParam("\"" + path.replace("\\", "\\\\").replace("\"", "\\\"") + "\""); // Toast.makeText(getApplicationContext(), path, Toast.LENGTH_SHORT).show(); } else { String param = getIntent().getStringExtra(SHORTCUT_EXTRA_KEY); + String args = getIntent().getStringExtra(ARGS_EXTRA_KEY); Log.e(TAG, "Got ACTION_VIEW without a valid uri, trying param"); if (param != null) { Log.i(TAG, "Found Shortcut Parameter in extra-data: " + param); - super.setShortcutParam(getIntent().getStringExtra(SHORTCUT_EXTRA_KEY)); + super.setShortcutParam("\"" + param.replace("\\", "\\\\").replace("\"", "\\\"") + "\""); + } else if (args != null) { + Log.i(TAG, "Found args parameter in extra-data: " + args); + super.setShortcutParam(args); } else { Log.e(TAG, "Shortcut missing parameter!"); super.setShortcutParam(""); diff --git a/ext/native/ui/screen.cpp b/ext/native/ui/screen.cpp index a5e105aa56..958c1d70c3 100644 --- a/ext/native/ui/screen.cpp +++ b/ext/native/ui/screen.cpp @@ -8,7 +8,6 @@ #include "ui/view.h" ScreenManager::ScreenManager() { - nextScreen_ = 0; uiContext_ = 0; dialogFinished_ = 0; } @@ -18,7 +17,7 @@ ScreenManager::~ScreenManager() { } void ScreenManager::switchScreen(Screen *screen) { - if (screen == nextScreen_) { + if (!nextStack_.empty() && screen == nextStack_.front().screen) { ELOG("Already switching to this screen"); return; } @@ -26,23 +25,23 @@ void ScreenManager::switchScreen(Screen *screen) { // will only become apparent if the dialog is closed. The previous screen will stick around // until that switch. // TODO: is this still true? - if (nextScreen_ != 0) { - ELOG("Already had a nextScreen_! Asynchronous open while doing something? Deleting the new screen."); + if (!nextStack_.empty()) { + ELOG("Already had a nextStack_! Asynchronous open while doing something? Deleting the new screen."); delete screen; return; } - if (screen == 0) { + if (screen == nullptr) { WLOG("Swiching to a zero screen, this can't be good"); } if (stack_.empty() || screen != stack_.back().screen) { - nextScreen_ = screen; - nextScreen_->setScreenManager(this); + screen->setScreenManager(this); + nextStack_.push_back({ screen, 0 }); } } void ScreenManager::update() { std::lock_guard guard(inputLock_); - if (nextScreen_) { + if (!nextStack_.empty()) { switchToNext(); } @@ -53,22 +52,25 @@ void ScreenManager::update() { void ScreenManager::switchToNext() { std::lock_guard guard(inputLock_); - if (!nextScreen_) { - ELOG("switchToNext: No nextScreen_!"); + if (nextStack_.empty()) { + ELOG("switchToNext: No nextStack_!"); } - Layer temp = {0, 0}; + Layer temp = {nullptr, 0}; if (!stack_.empty()) { temp = stack_.back(); stack_.pop_back(); } - Layer newLayer = {nextScreen_, 0}; - stack_.push_back(newLayer); + stack_.push_back(nextStack_.front()); if (temp.screen) { delete temp.screen; } - nextScreen_ = 0; - UI::SetFocusedView(0); + UI::SetFocusedView(nullptr); + + for (size_t i = 1; i < nextStack_.size(); ++i) { + stack_.push_back(nextStack_[i]); + } + nextStack_.clear(); } bool ScreenManager::touch(const TouchInput &touch) { @@ -197,19 +199,16 @@ Screen *ScreenManager::topScreen() const { void ScreenManager::shutdown() { std::lock_guard guard(inputLock_); - for (auto x = stack_.begin(); x != stack_.end(); x++) - delete x->screen; + for (auto layer : stack_) + delete layer.screen; stack_.clear(); - delete nextScreen_; - nextScreen_ = nullptr; + for (auto layer : nextStack_) + delete layer.screen; + nextStack_.clear(); } void ScreenManager::push(Screen *screen, int layerFlags) { std::lock_guard guard(inputLock_); - if (nextScreen_ && stack_.empty()) { - // we're during init, this is OK - switchToNext(); - } screen->setScreenManager(this); if (screen->isTransparent()) { layerFlags |= LAYER_TRANSPARENT; @@ -224,7 +223,10 @@ void ScreenManager::push(Screen *screen, int layerFlags) { touch(input); Layer layer = {screen, layerFlags}; - stack_.push_back(layer); + if (nextStack_.empty()) + stack_.push_back(layer); + else + nextStack_.push_back(layer); } void ScreenManager::pop() { diff --git a/ext/native/ui/screen.h b/ext/native/ui/screen.h index abca7f9769..0b97108afd 100644 --- a/ext/native/ui/screen.h +++ b/ext/native/ui/screen.h @@ -147,7 +147,6 @@ private: void switchToNext(); void processFinishDialog(); - Screen *nextScreen_; UIContext *uiContext_; Draw::DrawContext *thin3DContext_; @@ -166,4 +165,5 @@ private: // Dialog stack. These are shown "on top" of base screens and the Android back button works as expected. // Used for options, in-game menus and other things you expect to be able to back out from onto something. std::vector stack_; + std::vector nextStack_; };