diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2959bd6f9f..ac6a142ed5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -308,9 +308,6 @@ jobs: brew install ldid dpkg pillow # Check Xcode version xcodebuild -version - # List available Xcode versions - ls /Applications | grep Xcode - sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Create iOS Version.txt file if: matrix.id == 'ios' diff --git a/Common/System/NativeApp.h b/Common/System/NativeApp.h index f6f2dde904..9afaf5a301 100644 --- a/Common/System/NativeApp.h +++ b/Common/System/NativeApp.h @@ -3,6 +3,8 @@ #include #include +#include "Common/Common.h" + // The Native App API. // // Implement these functions and you've got a native app. These are called @@ -17,6 +19,19 @@ struct AxisInput; class GraphicsContext; class AudioBackend; +enum class KeyModifier { + NONE = 0, + LCTRL = 1, + RCTRL = 2, + LSHIFT = 4, + RSHIFT = 8, + LALT = 16, + RALT = 32, + LMETA = 64, + RMETA = 128, +}; +ENUM_CLASS_BITOPS(KeyModifier); + // The first function to get called, just write strings to the two pointers. // This might get called multiple times in some implementations, you must be able to handle that. void NativeGetAppInfo(std::string *app_dir_name, std::string *app_nice_name, bool *landscape, std::string *version); @@ -55,6 +70,7 @@ bool NativeKey(const KeyInput &key); void NativeAxis(const AxisInput *axis, size_t count); void NativeAccelerometer(float tiltX, float tiltY, float tiltZ); void NativeMouseDelta(float dx, float dy); +KeyModifier NativeGetKeyModifiers(); // TODO: Move this to input handling somewhere? But don't want it in each backend... // Called when it's process a frame, including rendering. If the device can keep up, this // will be called sixty times per second. Main thread. diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp index 4e059df9fe..2195085816 100644 --- a/Common/UI/PopupScreens.cpp +++ b/Common/UI/PopupScreens.cpp @@ -51,11 +51,11 @@ PopupScreen::PopupScreen(std::string_view title, std::string_view button1, std:: alpha_ = 0.0f; // inherited } -void PopupScreen::touch(const TouchInput &touch) { +bool PopupScreen::touch(const TouchInput &touch) { if (!box_ || (touch.flags & TouchInputFlags::DOWN) == 0) { // Handle down-presses here. UIDialogScreen::touch(touch); - return; + return false; } // Extra bounds to avoid closing the dialog while trying to aim for something @@ -65,7 +65,7 @@ void PopupScreen::touch(const TouchInput &touch) { TriggerFinish(DR_CANCEL); } - UIDialogScreen::touch(touch); + return UIDialogScreen::touch(touch); } bool PopupScreen::key(const KeyInput &key) { diff --git a/Common/UI/PopupScreens.h b/Common/UI/PopupScreens.h index b0703acbbe..abd8ba39f1 100644 --- a/Common/UI/PopupScreens.h +++ b/Common/UI/PopupScreens.h @@ -25,7 +25,7 @@ public: virtual void CreatePopupContents(UI::ViewGroup *parent) = 0; void CreateViews() override; bool isTransparent() const override { return true; } - void touch(const TouchInput &touch) override; + bool touch(const TouchInput &touch) override; bool key(const KeyInput &key) override; void TriggerFinish(DialogResult result) override; diff --git a/Common/UI/Root.cpp b/Common/UI/Root.cpp index 0dee325ef3..dfb775eb95 100644 --- a/Common/UI/Root.cpp +++ b/Common/UI/Root.cpp @@ -193,7 +193,7 @@ bool IsScrollKey(const KeyInput &input) { } } -static KeyEventResult KeyEventToFocusMoves(const KeyInput &key) { +KeyEventResult KeyEventToFocusMoves(const KeyInput &key) { KeyEventResult retval = KeyEventResult::PASS_THROUGH; // Ignore repeats for focus moves. if ((key.flags & KeyInputFlags::DOWN) && !(key.flags & KeyInputFlags::IS_REPEAT)) { @@ -231,27 +231,6 @@ static KeyEventResult KeyEventToFocusMoves(const KeyInput &key) { return retval; } -KeyEventResult UnsyncKeyEvent(const KeyInput &key, ViewGroup *root) { - KeyEventResult retval = KeyEventToFocusMoves(key); - - // Ignore volume keys and stuff here. Not elegant but need to propagate bools through the view hierarchy as well... - switch (key.keyCode) { - case NKCODE_VOLUME_DOWN: - case NKCODE_VOLUME_UP: - case NKCODE_VOLUME_MUTE: - retval = KeyEventResult::PASS_THROUGH; - break; - default: - if (!(key.flags & KeyInputFlags::IS_REPEAT)) { - // If a repeat, we follow what KeyEventToFocusMoves set it to. - // Otherwise we signal that we used the key, always. - retval = KeyEventResult::ACCEPT; - } - break; - } - return retval; -} - bool KeyEvent(const KeyInput &key, ViewGroup *root) { return root->Key(key); } diff --git a/Common/UI/Root.h b/Common/UI/Root.h index 74a280b072..861941f6b7 100644 --- a/Common/UI/Root.h +++ b/Common/UI/Root.h @@ -34,8 +34,7 @@ enum class KeyEventResult { ACCEPT, // Let it be processed, but return true. }; -// Hooks arrow keys for navigation -KeyEventResult UnsyncKeyEvent(const KeyInput &key, ViewGroup *root); +KeyEventResult KeyEventToFocusMoves(const KeyInput &key); bool KeyEvent(const KeyInput &key, ViewGroup *root); void TouchEvent(const TouchInput &touch, ViewGroup *root); diff --git a/Common/UI/Screen.cpp b/Common/UI/Screen.cpp index 53de5bbea1..d85a1ef823 100644 --- a/Common/UI/Screen.cpp +++ b/Common/UI/Screen.cpp @@ -59,7 +59,6 @@ void ScreenManager::switchScreen(Screen *screen) { ERROR_LOG(Log::UI, "Can't switch to empty screen"); return; } - // TODO: inputLock_ ? INFO_LOG(Log::UI, "ScreenManager::switchScreen('%s')", screen->tag()); @@ -106,8 +105,6 @@ void ScreenManager::cancelScreensAbove(Screen *screen) { } void ScreenManager::update() { - std::lock_guard guard(inputLock_); - if (cancelScreensAbove_) { bool found = false; for (int i = (int)stack_.size() - 1; i >= 0; i--) { @@ -129,14 +126,75 @@ void ScreenManager::update() { // NOTE: This is not a full UIScreen update, to avoid double global event processing. overlayScreen_->update(); } - // The background screen doesn't need updating. + + // Only the front screen gets update(). if (!stack_.empty()) { stack_.back().screen->update(); + passInputToMapper_ = stack_.back().screen->PassInputToMapper(); + } + + // Process queued events. + + std::deque events; + { + std::lock_guard eventGuard(eventQueueLock_); + events = std::move(eventQueue_); + eventQueue_.clear(); + } + + for (const QueuedEvent &ev : events) { + switch (ev.type) { + case QueuedEventType::TOUCH: + { + const TouchInput &touch = ev.touch; + // Send release all events to every screen layer. + if (touch.flags & TouchInputFlags::RELEASE_ALL) { + for (auto &layer : stack_) { + Screen *screen = layer.screen; + layer.screen->touch(screen->transformTouch(touch)); + } + } else if (!stack_.empty()) { + // Let the overlay know about touch-downs, to be able to dismiss popups. + bool skip = false; + if (overlayScreen_ && (touch.flags & TouchInputFlags::DOWN)) { + skip = overlayScreen_->touch(overlayScreen_->transformTouch(touch)); + } + if (!skip) { + Screen *screen = stack_.back().screen; + stack_.back().screen->touch(screen->transformTouch(touch)); + } + } + break; + } + case QueuedEventType::KEY: + { + const KeyInput &key = ev.key; + // Send key up to every screen layer, to avoid stuck keys. + if (key.flags & KeyInputFlags::UP) { + for (auto &layer : stack_) { + layer.screen->key(key); + } + } else if (!stack_.empty()) { + stack_.back().screen->key(key); + } + break; + } + case QueuedEventType::AXIS: + { + const AxisInput &axis = ev.axis; + if (!stack_.empty()) { + stack_.back().screen->axis(axis); + } + break; + } + default: + ERROR_LOG(Log::UI, "Unknown queued event type: %d", (int)ev.type); + break; + } } } void ScreenManager::switchToNext() { - std::lock_guard guard(inputLock_); if (nextStack_.empty()) { ERROR_LOG(Log::System, "switchToNext: No nextStack_!"); } @@ -160,44 +218,28 @@ void ScreenManager::switchToNext() { } void ScreenManager::touch(const TouchInput &touch) { - std::lock_guard guard(inputLock_); - // Send release all events to every screen layer. - if (touch.flags & TouchInputFlags::RELEASE_ALL) { - for (auto &layer : stack_) { - Screen *screen = layer.screen; - layer.screen->UnsyncTouch(screen->transformTouch(touch)); - } - } else if (!stack_.empty()) { - // Let the overlay know about touch-downs, to be able to dismiss popups. - bool skip = false; - if (overlayScreen_ && (touch.flags & TouchInputFlags::DOWN)) { - skip = overlayScreen_->UnsyncTouch(overlayScreen_->transformTouch(touch)); - } - if (!skip) { - Screen *screen = stack_.back().screen; - stack_.back().screen->UnsyncTouch(screen->transformTouch(touch)); - } - } + QueuedEvent ev{}; + ev.type = QueuedEventType::TOUCH; + ev.touch = touch; + std::lock_guard guard(eventQueueLock_); + eventQueue_.push_back(ev); } -bool ScreenManager::key(const KeyInput &key) { - std::lock_guard guard(inputLock_); - bool result = false; - // Send key up to every screen layer, to avoid stuck keys. - if (key.flags & KeyInputFlags::UP) { - for (auto &layer : stack_) { - result = layer.screen->UnsyncKey(key); - } - } else if (!stack_.empty()) { - result = stack_.back().screen->UnsyncKey(key); - } - return result; +void ScreenManager::key(const KeyInput &key) { + QueuedEvent ev{}; + ev.type = QueuedEventType::KEY; + ev.key = key; + std::lock_guard guard(eventQueueLock_); + eventQueue_.push_back(ev); } void ScreenManager::axis(const AxisInput *axes, size_t count) { - std::lock_guard guard(inputLock_); - if (!stack_.empty()) { - stack_.back().screen->UnsyncAxis(axes, count); + QueuedEvent ev{}; + ev.type = QueuedEventType::AXIS; + std::lock_guard guard(eventQueueLock_); + for (size_t i = 0; i < count; i++) { + ev.axis = axes[i]; + eventQueue_.push_back(ev); } } @@ -214,7 +256,6 @@ void ScreenManager::deviceRestored(Draw::DrawContext *draw) { void ScreenManager::resized() { INFO_LOG(Log::UI, "ScreenManager::resized(dp: %dx%d)", g_display.dp_xres, g_display.dp_yres); - std::lock_guard guard(inputLock_); // Have to notify the whole stack, otherwise there will be problems when going back // to non-top screens. for (auto &layer : stack_) { @@ -359,7 +400,6 @@ void ScreenManager::sendMessage(UIMessage message, const char *value) { } void ScreenManager::shutdown() { - std::lock_guard guard(inputLock_); for (const auto &layer : stack_) delete layer.screen; stack_.clear(); @@ -373,7 +413,6 @@ void ScreenManager::shutdown() { } void ScreenManager::push(Screen *screen, int layerFlags) { - std::lock_guard guard(inputLock_); screen->setScreenManager(this); if (screen->isTransparent()) { layerFlags |= LAYER_TRANSPARENT; @@ -404,7 +443,6 @@ void ScreenManager::push(Screen *screen, int layerFlags) { } void ScreenManager::pop() { - std::lock_guard guard(inputLock_); if (!stack_.empty()) { stack_.back().screen->focusChanged(ScreenFocusChange::FOCUS_LOST_TOP); @@ -420,7 +458,6 @@ void ScreenManager::pop() { } void ScreenManager::RecreateAllViews() { - std::lock_guard guard(inputLock_); for (auto &layer : stack_) { layer.screen->RecreateViews(); } @@ -454,7 +491,6 @@ Screen *ScreenManager::dialogParent(const Screen *dialog) const { void ScreenManager::processFinishDialog() { if (dialogFinished_) { { - std::lock_guard guard(inputLock_); // Another dialog may have been pushed before the render, so search for it. Screen *caller = dialogParent(dialogFinished_); bool erased = false; diff --git a/Common/UI/Screen.h b/Common/UI/Screen.h index 4542b9e7e0..e726037f14 100644 --- a/Common/UI/Screen.h +++ b/Common/UI/Screen.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -75,6 +76,30 @@ enum class ViewLayoutMode { IgnoreBottomInset, }; +enum class InputMode { + None = 0, + Keyboard = 1, + Mouse = 2, + Other = 4, + ImDebuggerToggle = 8, // ugly hack. debugger and pause goes through. +}; +ENUM_CLASS_BITOPS(InputMode); + +enum class QueuedEventType : u8 { + KEY, + AXIS, + TOUCH, +}; + +struct QueuedEvent { + QueuedEventType type; + union { + TouchInput touch; + KeyInput key; + AxisInput axis; + }; +}; + class Screen { public: Screen() = default; @@ -94,14 +119,16 @@ public: virtual void focusChanged(ScreenFocusChange focusChange); - // Return value of UnsyncTouch is only used to let the overlay screen block touches. - virtual bool UnsyncTouch(const TouchInput &touch) = 0; - // Return value of UnsyncKey is used to not block certain system keys like volume when unhandled, on Android. - virtual bool UnsyncKey(const KeyInput &touch) = 0; - virtual void UnsyncAxis(const AxisInput *axes, size_t count) = 0; + virtual bool touch(const TouchInput &touch) = 0; + virtual bool key(const KeyInput &key) = 0; + virtual void axis(const AxisInput &axis) = 0; virtual void RecreateViews() {} + // NOTE: This is polled every frame, not called on every input event. This is + // a step towards eventually removing the ScreenManager input mutex. + virtual InputMode PassInputToMapper() const { return InputMode::None; } + ScreenManager *screenManager() { return screenManager_; } const ScreenManager *screenManager() const { return screenManager_; } void setScreenManager(ScreenManager *sm) { screenManager_ = sm; } @@ -136,7 +163,7 @@ enum { LAYER_TRANSPARENT = 2, }; -typedef void(*PostRenderCallback)(UIContext *ui, void *userdata); +typedef void (*PostRenderCallback)(UIContext *ui, void *userdata); class ScreenManager { public: @@ -173,7 +200,7 @@ public: // Instant touch, separate from the update() mechanism. void touch(const TouchInput &touch); - bool key(const KeyInput &key); + void key(const KeyInput &key); void axis(const AxisInput *axes, size_t count); void sendMessage(UIMessage message, const char *value); @@ -192,7 +219,9 @@ public: // Will delete any existing overlay screen. void SetBackgroundOverlayScreens(Screen *backgroundScreen, Screen *overlayScreen); - std::recursive_mutex inputLock_; + InputMode PassInputToMapper() const { + return passInputToMapper_; + } private: void pop(); @@ -225,4 +254,9 @@ private: std::vector nextStack_; std::unordered_map lastAxis_; + + std::mutex eventQueueLock_; + std::deque eventQueue_; + + InputMode passInputToMapper_ = InputMode::None; }; diff --git a/Common/UI/UIScreen.cpp b/Common/UI/UIScreen.cpp index 501272ddfa..e5d8fef940 100644 --- a/Common/UI/UIScreen.cpp +++ b/Common/UI/UIScreen.cpp @@ -33,8 +33,6 @@ void UIScreen::DoRecreateViews() { return; } - std::lock_guard guard(screenManager()->inputLock_); - UI::PersistMap persisted; bool persisting = root_ != nullptr; if (persisting) { @@ -66,10 +64,20 @@ void UIScreen::DoRecreateViews() { WipeRequesterToken(); } -void UIScreen::touch(const TouchInput &touch) { +bool UIScreen::touch(const TouchInput &touch) { + if (ClickDebug && root_ && (touch.flags & TouchInputFlags::DOWN)) { + INFO_LOG(Log::System, "Touch down!"); + std::vector views; + root_->Query(touch.x, touch.y, views); + for (auto view : views) { + INFO_LOG(Log::System, "%s", view->DescribeLog().c_str()); + } + } + if (!ignoreInput_ && root_) { UI::TouchEvent(touch, root_); } + return true; } void UIScreen::axis(const AxisInput &axis) { @@ -86,59 +94,6 @@ bool UIScreen::key(const KeyInput &key) { } } -bool UIScreen::UnsyncTouch(const TouchInput &touch) { - if (ClickDebug && root_ && (touch.flags & TouchInputFlags::DOWN)) { - INFO_LOG(Log::System, "Touch down!"); - std::vector views; - root_->Query(touch.x, touch.y, views); - for (auto view : views) { - INFO_LOG(Log::System, "%s", view->DescribeLog().c_str()); - } - } - - QueuedEvent ev{}; - ev.type = QueuedEventType::TOUCH; - ev.touch = touch; - std::lock_guard guard(eventQueueLock_); - eventQueue_.push_back(ev); - return false; -} - -void UIScreen::UnsyncAxis(const AxisInput *axes, size_t count) { - QueuedEvent ev{}; - ev.type = QueuedEventType::AXIS; - std::lock_guard guard(eventQueueLock_); - for (size_t i = 0; i < count; i++) { - ev.axis = axes[i]; - eventQueue_.push_back(ev); - } -} - -bool UIScreen::UnsyncKey(const KeyInput &key) { - bool retval = false; - if (root_) { - // TODO: Make key events async too. The return value is troublesome, though. - switch (UI::UnsyncKeyEvent(key, root_)) { - case UI::KeyEventResult::ACCEPT: - retval = true; - break; - case UI::KeyEventResult::PASS_THROUGH: - retval = false; - break; - case UI::KeyEventResult::IGNORE_KEY: - return false; - } - } - - QueuedEvent ev{}; - ev.type = QueuedEventType::KEY; - ev.key = key; - - std::lock_guard guard(eventQueueLock_); - eventQueue_.push_back(ev); - return retval; -} - void UIScreen::update() { DeviceOrientation orientation = GetDeviceOrientation(); if (orientation != lastOrientation_) { @@ -148,41 +103,6 @@ void UIScreen::update() { DoRecreateViews(); - while (true) { - QueuedEvent ev{}; - { - std::lock_guard guard(eventQueueLock_); - if (!eventQueue_.empty()) { - ev = eventQueue_.front(); - eventQueue_.pop_front(); - } else { - break; - } - } - if (ignoreInput_) { - continue; - } - switch (ev.type) { - case QueuedEventType::KEY: - key(ev.key); - break; - case QueuedEventType::TOUCH: - if (ClickDebug && (ev.touch.flags & TouchInputFlags::DOWN)) { - INFO_LOG(Log::System, "Touch down!"); - std::vector views; - root_->Query(ev.touch.x, ev.touch.y, views); - for (auto view : views) { - INFO_LOG(Log::System, "%s", view->DescribeLog().c_str()); - } - } - touch(ev.touch); - break; - case QueuedEventType::AXIS: - axis(ev.axis); - break; - } - } - if (root_) { DialogResult result = UpdateViewHierarchy(root_); if (result != DR_NONE) { diff --git a/Common/UI/UIScreen.h b/Common/UI/UIScreen.h index 946cb75901..de162797d6 100644 --- a/Common/UI/UIScreen.h +++ b/Common/UI/UIScreen.h @@ -17,21 +17,6 @@ namespace Draw { class DrawContext; } -enum class QueuedEventType : u8 { - KEY, - AXIS, - TOUCH, -}; - -struct QueuedEvent { - QueuedEventType type; - union { - TouchInput touch; - KeyInput key; - AxisInput axis; - }; -}; - class UIScreen : public Screen { public: UIScreen(); @@ -42,13 +27,9 @@ public: void deviceLost() override; void deviceRestored(Draw::DrawContext *draw) override; - virtual void touch(const TouchInput &touch); - virtual bool key(const KeyInput &key); - virtual void axis(const AxisInput &axis); - - bool UnsyncTouch(const TouchInput &touch) override; - bool UnsyncKey(const KeyInput &key) override; - void UnsyncAxis(const AxisInput *axes, size_t count) override; + bool touch(const TouchInput &touch) override; + bool key(const KeyInput &key) override; + void axis(const AxisInput &axis) override; TouchInput transformTouch(const TouchInput &touch) override; @@ -92,10 +73,6 @@ protected: bool recreateViews_ = true; DeviceOrientation lastOrientation_ = DeviceOrientation::Landscape; - -private: - std::mutex eventQueueLock_; - std::deque eventQueue_; }; class UIDialogScreen : public UIScreen { diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index d39a36f848..6f96a37ba0 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -339,7 +339,7 @@ float GetTargetScore(const Point2D &originPos, int originIndex, const View *orig const float vertOverlap = VerticalOverlap(origin->GetBounds(), destination->GetBounds()); if (horizOverlap == 1.0f && vertOverlap == 1.0f) { if (direction != FocusMove::PREV_PAGE && direction != FocusMove::NEXT_PAGE) { - INFO_LOG(Log::UI, "Contain overlap: %s, %s", origin->Tag().c_str(), destination->Tag().c_str()); + // INFO_LOG(Log::UI, "Contain overlap: %s, %s", origin->Tag().c_str(), destination->Tag().c_str()); return 0.0f; } } diff --git a/Core/RetroAchievements.cpp b/Core/RetroAchievements.cpp index 0b9f1125ba..4366d87359 100644 --- a/Core/RetroAchievements.cpp +++ b/Core/RetroAchievements.cpp @@ -615,7 +615,7 @@ static void load_integration_callback(int result, const char *error_message, rc_ } case RC_MISSING_VALUE: // This is fine, proceeding to login. - g_OSD.Show(OSDType::MESSAGE_WARNING, ac->T("RAIntegration is enabled, but %1 was not found.")); + g_OSD.Show(OSDType::MESSAGE_WARNING, ApplySafeSubstitutions(ac->T("RAIntegration is enabled, but %1 was not found."), RAINTEGRATION_FILENAME)); break; case RC_ABORTED: // This is fine(-ish), proceeding to login. diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index 3e730cfa94..d4993ee9b4 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -514,25 +514,9 @@ void AnalogCalibrationScreen::update() { UIScreen::update(); } -bool AnalogCalibrationScreen::key(const KeyInput &key) { - bool retval = UIScreen::key(key); - - // Allow testing auto-rotation. If it collides with UI keys, too bad. - g_controlMapper.Key(key); - - if (UI::IsEscapeKey(key)) { - TriggerFinish(DR_BACK); - return retval; - } - return retval; -} - void AnalogCalibrationScreen::axis(const AxisInput &axis) { // We DON'T call UIScreen::Axis here! Otherwise it'll try to move the UI focus around. // UIScreen::axis(axis); - - // Instead we just send the input directly to the mapper, that we'll visualize. - g_controlMapper.Axis(&axis, 1); } std::string_view AnalogCalibrationScreen::GetTitle() const { diff --git a/UI/ControlMappingScreen.h b/UI/ControlMappingScreen.h index 0125e4ed07..da71b0af37 100644 --- a/UI/ControlMappingScreen.h +++ b/UI/ControlMappingScreen.h @@ -130,11 +130,14 @@ public: AnalogCalibrationScreen(const Path &gamePath); ~AnalogCalibrationScreen(); - bool key(const KeyInput &key) override; void axis(const AxisInput &axis) override; void update() override; + InputMode PassInputToMapper() const { + return InputMode::Other | InputMode::Keyboard | InputMode::Mouse; + } + const char *tag() const override { return "AnalogSetup"; } protected: diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 78ec366383..b9432c0dd2 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -629,7 +629,7 @@ void FrameDumpTestScreen::update() { } } -void TouchTestScreen::touch(const TouchInput &touch) { +bool TouchTestScreen::touch(const TouchInput &touch) { UIBaseDialogScreen::touch(touch); if (touch.flags & TouchInputFlags::DOWN) { bool found = false; @@ -678,6 +678,7 @@ void TouchTestScreen::touch(const TouchInput &touch) { WARN_LOG(Log::System, "Touch release without touch down"); } } + return true; } // TODO: Move this screen out into its own file. diff --git a/UI/DevScreens.h b/UI/DevScreens.h index ebe12887d3..3636eccf09 100644 --- a/UI/DevScreens.h +++ b/UI/DevScreens.h @@ -169,7 +169,7 @@ public: } } - void touch(const TouchInput &touch) override; + bool touch(const TouchInput &touch) override; void DrawForeground(UIContext &dc) override; bool key(const KeyInput &key) override; diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 5b075b6065..bf49fc0be1 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -21,6 +21,7 @@ using namespace std::placeholders; +#include "Common/System/NativeApp.h" #include "Common/Render/TextureAtlas.h" #include "Common/GPU/OpenGL/GLFeatures.h" #include "Common/File/FileUtil.h" @@ -757,11 +758,334 @@ static void ShowFpsLimitNotice() { void EmuScreen::OnVKey(VirtKey virtualKeyCode, bool down) { if (!IsOnTop()) return; + queuedVirtKeys_.push_back(std::make_pair(virtualKeyCode, down)); +} - auto sc = GetI18NCategory(I18NCat::SCREEN); +void EmuScreen::ProcessQueuedVKeys() { + for (auto iter : queuedVirtKeys_) { + ProcessVKey(iter.first, iter.second); + } + queuedVirtKeys_.clear(); +} + +// Synchronized processing of virtkeys. +void EmuScreen::ProcessVKey(VirtKey virtKey, bool down) { auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS); + auto sc = GetI18NCategory(I18NCat::SCREEN); - switch (virtualKeyCode) { + switch (virtKey) { + case VIRTKEY_PAUSE: + // Note: We don't check NetworkWarnUserIfOnlineAndCantSpeed, because we can keep + // running in the background of the menu. + if (down) { + pauseTrigger_ = true; + } + break; + + case VIRTKEY_SCREENSHOT: + if (down) { + TakeUserScreenshot(); + } + break; + + case VIRTKEY_TOGGLE_DEBUGGER: + if (down) { + g_Config.bShowImDebugger = !g_Config.bShowImDebugger; + } + break; + case VIRTKEY_TOGGLE_TILT: + if (down) { + g_Config.bTiltInputEnabled = !g_Config.bTiltInputEnabled; + if (!g_Config.bTiltInputEnabled) { + // Reset whatever got tilted. + switch (g_Config.iTiltInputType) { + case TILT_ANALOG: + __CtrlSetAnalogXY(0, 0, 0); + break; + case TILT_ACTION_BUTTON: + __CtrlUpdateButtons(0, CTRL_CROSS | CTRL_CIRCLE | CTRL_SQUARE | CTRL_TRIANGLE); + break; + case TILT_DPAD: + __CtrlUpdateButtons(0, CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT); + break; + case TILT_TRIGGER_BUTTONS: + __CtrlUpdateButtons(0, CTRL_LTRIGGER | CTRL_RTRIGGER); + break; + } + } + } + break; + case VIRTKEY_OPENCHAT: + if (down) { + if (g_Config.bEnableNetworkChat && !g_Config.bShowImDebugger) { + UI::EventParams e{}; + g_controlMapper.ForceReleaseVKey(VIRTKEY_OPENCHAT); + OpenChat(true); + } + } + break; + + case VIRTKEY_AXIS_SWAP_TOGGLE: + if (down) { + g_controlMapper.ToggleSwapAxes(); + g_OSD.Show(OSDType::MESSAGE_INFO, mc->T("AxisSwap")); // best string we have. + } + break; + + case VIRTKEY_DEVMENU: + if (down) { + UI::EventParams e{}; + OnDevMenu.Trigger(e); + } + break; + + case VIRTKEY_TOGGLE_MOUSE: + if (down) { + g_Config.bMouseControl = !g_Config.bMouseControl; + } + break; + + case VIRTKEY_TEXTURE_DUMP: + if (down) { + g_Config.bSaveNewTextures = !g_Config.bSaveNewTextures; + if (g_Config.bSaveNewTextures) { + g_OSD.Show(OSDType::MESSAGE_SUCCESS, sc->T("saveNewTextures_true", "Textures will now be saved to your storage"), 2.0, "savetexturechanged"); + } else { + g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("saveNewTextures_false", "Texture saving was disabled"), 2.0, "savetexturechanged"); + } + System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); + } + break; + + case VIRTKEY_TEXTURE_REPLACE: + if (down) { + g_Config.bReplaceTextures = !g_Config.bReplaceTextures; + if (g_Config.bReplaceTextures) { + g_OSD.Show(OSDType::MESSAGE_SUCCESS, sc->T("replaceTextures_true", "Texture replacement enabled"), 2.0, "replacetexturechanged"); + } else { + g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("replaceTextures_false", "Textures are no longer being replaced"), 2.0, "replacetexturechanged"); + } + System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); + } + break; + + case VIRTKEY_MUTE_TOGGLE: + if (down) { + g_Config.bEnableSound = !g_Config.bEnableSound; + } + break; + + case VIRTKEY_SCREEN_ROTATION_VERTICAL: + if (down) { + DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); + config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL; + } + break; + case VIRTKEY_SCREEN_ROTATION_VERTICAL180: + if (down) { + DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); + config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL180; + } + break; + case VIRTKEY_SCREEN_ROTATION_HORIZONTAL: + if (down) { + DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); + config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL; + } + break; + case VIRTKEY_SCREEN_ROTATION_HORIZONTAL180: + if (down) { + DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); + config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL180; + } + break; + + case VIRTKEY_TOGGLE_WLAN: + if (down) { + // Let's not allow the user to toggle wlan while connected, could get confusing. + if (!g_netInited) { + auto n = GetI18NCategory(I18NCat::NETWORKING); + auto di = GetI18NCategory(I18NCat::DIALOG); + g_Config.bEnableWlan = !g_Config.bEnableWlan; + // Try to avoid adding more strings so we piece together a message from existing ones. + g_OSD.Show(OSDType::MESSAGE_INFO, StringFromFormat( + "%s: %s", n->T_cstr("Enable networking"), g_Config.bEnableWlan ? di->T_cstr("Enabled") : di->T_cstr("Disabled")), 2.0, "toggle_wlan"); + } + } + break; + + case VIRTKEY_TOGGLE_FULLSCREEN: + if (down) { + // TODO: Limit to platforms that can support fullscreen. + g_Config.bFullScreen = !g_Config.bFullScreen; + System_ApplyFullscreenState(); + } + break; + + case VIRTKEY_TOGGLE_TOUCH_CONTROLS: + if (down) { + if (g_Config.bShowTouchControls) { + // This just messes with opacity if enabled, so you can touch the screen again to bring them back. + if (GamepadGetOpacity() < 0.01f) { + GamepadTouch(); + } else { + GamepadResetTouch(); + } + } else { + // If touch controls are disabled though, they'll get enabled. + g_Config.bShowTouchControls = true; + RecreateViews(); + GamepadTouch(); + } + } + break; + + case VIRTKEY_REWIND: + if (down) { + if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) { + if (SaveState::CanRewind()) { + SaveState::Rewind(&ShowMessageAfterSaveStateAction); + } else { + g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("norewind", "No rewind save states available"), 2.0); + } + } + } + break; + + case VIRTKEY_PAUSE_NO_MENU: + if (down) { + if (!NetworkWarnUserIfOnlineAndCantSpeed()) { + // We re-use debug break/resume to implement pause/resume without a menu. + if (coreState == CORE_STEPPING_CPU) { // should we check reason? + Core_Resume(); + } else { + Core_Break(BreakReason::UIPause); + } + } + } + break; + + case VIRTKEY_EXIT_APP: + if (down) { + if (!bootPending_) { + std::string confirmExitMessage = GetConfirmExitMessage(); + if (!confirmExitMessage.empty()) { + auto di = GetI18NCategory(I18NCat::DIALOG); + auto mm = GetI18NCategory(I18NCat::MAINMENU); + confirmExitMessage += '\n'; + confirmExitMessage += di->T("Are you sure you want to exit?"); + screenManager()->push(new UI::MessagePopupScreen(di->T("Exit"), confirmExitMessage, di->T("Yes"), di->T("No"), [](bool result) { + if (result) { + System_ExitApp(); + } + })); + } else { + System_ExitApp(); + } + } + } + break; + + case VIRTKEY_FRAME_ADVANCE: + if (down) { + // Can't do this reliably in an async fashion, so we just set a variable. + // Is this used by anyone? There's no user-friendly way to resume, other than PAUSE_NO_MENU or the debugger. + if (!NetworkWarnUserIfOnlineAndCantSpeed()) { + if (Core_IsStepping()) { + Core_Resume(); + frameStep_ = true; + } else { + Core_Break(BreakReason::FrameAdvance); + } + } + } + break; + + case VIRTKEY_SPEED_TOGGLE: + if (down) { + if (!NetworkWarnUserIfOnlineAndCantSpeed()) { + // Cycle through enabled speeds. + if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) { + PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1; + } else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit2 >= 0) { + PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2; + } else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 || PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) { + PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL; + } + + ShowFpsLimitNotice(); + } + } + break; + + case VIRTKEY_RESET_EMULATION: + if (down) { + System_PostUIMessage(UIMessage::REQUEST_GAME_RESET); + } + break; + +#ifndef MOBILE_DEVICE + case VIRTKEY_RECORD: + if (down) { + if (g_Config.bDumpFrames == g_Config.bDumpAudio) { + g_Config.bDumpFrames = !g_Config.bDumpFrames; + g_Config.bDumpAudio = !g_Config.bDumpAudio; + } else { + // This hotkey should always toggle both audio and video together. + // So let's make sure that's the only outcome even if video OR audio was already being dumped. + if (g_Config.bDumpFrames) { + AVIDump::Stop(); + AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); + g_Config.bDumpAudio = true; + } else { + WAVDump::Reset(); + g_Config.bDumpFrames = true; + } + } + } + break; +#endif + + case VIRTKEY_SAVE_STATE: + if (down) { + if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) { + SaveState::SaveSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, &ShowMessageAfterSaveStateAction); + } + } + break; + case VIRTKEY_LOAD_STATE: + if (down) { + if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) { + if (g_Config.bConfirmLoadState) { + std::string prefix = SaveState::GetGamePrefix(g_paramSFO); + int slot = g_Config.iCurrentStateSlot; + screenManager()->push(new LoadStateConfirmScreen(prefix, slot, [prefix, slot](bool result) { + if (result) { + SaveState::LoadSlot(prefix, slot, &ShowMessageAfterSaveStateAction); + } + })); + } else { + SaveState::LoadSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, &ShowMessageAfterSaveStateAction); + } + } + } + break; + case VIRTKEY_PREVIOUS_SLOT: + if (down) { + if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) { + SaveState::PrevSlot(); + System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT); + } + } + break; + case VIRTKEY_NEXT_SLOT: + if (down) { + if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) { + SaveState::NextSlot(); + System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT); + } + } + break; case VIRTKEY_FASTFORWARD: if (down && !NetworkWarnUserIfOnlineAndCantSpeed() && !bootPending_) { /* @@ -806,294 +1130,7 @@ void EmuScreen::OnVKey(VirtKey virtualKeyCode, bool down) { case VIRTKEY_RAPID_FIRE: __CtrlSetRapidFire(down, g_Config.iRapidFireInterval); break; - default: - // To make sure we're not in an async context. - if (down) { - queuedVirtKeys_.push_back(virtualKeyCode); - } - break; - } -} -void EmuScreen::ProcessQueuedVKeys() { - for (auto iter : queuedVirtKeys_) { - ProcessVKey(iter); - } - queuedVirtKeys_.clear(); -} - -// Synchronized processing of virtkeys. -void EmuScreen::ProcessVKey(VirtKey virtKey) { - auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS); - auto sc = GetI18NCategory(I18NCat::SCREEN); - - switch (virtKey) { - case VIRTKEY_PAUSE: - // Note: We don't check NetworkWarnUserIfOnlineAndCantSpeed, because we can keep - // running in the background of the menu. - pauseTrigger_ = true; - break; - - case VIRTKEY_SCREENSHOT: - TakeUserScreenshot(); - break; - - case VIRTKEY_TOGGLE_DEBUGGER: - g_Config.bShowImDebugger = !g_Config.bShowImDebugger; - break; - case VIRTKEY_TOGGLE_TILT: - g_Config.bTiltInputEnabled = !g_Config.bTiltInputEnabled; - if (!g_Config.bTiltInputEnabled) { - // Reset whatever got tilted. - switch (g_Config.iTiltInputType) { - case TILT_ANALOG: - __CtrlSetAnalogXY(0, 0, 0); - break; - case TILT_ACTION_BUTTON: - __CtrlUpdateButtons(0, CTRL_CROSS | CTRL_CIRCLE | CTRL_SQUARE | CTRL_TRIANGLE); - break; - case TILT_DPAD: - __CtrlUpdateButtons(0, CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT); - break; - case TILT_TRIGGER_BUTTONS: - __CtrlUpdateButtons(0, CTRL_LTRIGGER | CTRL_RTRIGGER); - break; - } - } - break; - case VIRTKEY_OPENCHAT: - if (g_Config.bEnableNetworkChat && !g_Config.bShowImDebugger) { - UI::EventParams e{}; - g_controlMapper.ForceReleaseVKey(VIRTKEY_OPENCHAT); - OpenChat(true); - } - break; - - case VIRTKEY_AXIS_SWAP_TOGGLE: - g_controlMapper.ToggleSwapAxes(); - g_OSD.Show(OSDType::MESSAGE_INFO, mc->T("AxisSwap")); // best string we have. - break; - - case VIRTKEY_DEVMENU: - { - UI::EventParams e{}; - OnDevMenu.Trigger(e); - } - break; - - case VIRTKEY_TOGGLE_MOUSE: - g_Config.bMouseControl = !g_Config.bMouseControl; - break; - - case VIRTKEY_TEXTURE_DUMP: - g_Config.bSaveNewTextures = !g_Config.bSaveNewTextures; - if (g_Config.bSaveNewTextures) { - g_OSD.Show(OSDType::MESSAGE_SUCCESS, sc->T("saveNewTextures_true", "Textures will now be saved to your storage"), 2.0, "savetexturechanged"); - } else { - g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("saveNewTextures_false", "Texture saving was disabled"), 2.0, "savetexturechanged"); - } - System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); - break; - - case VIRTKEY_TEXTURE_REPLACE: - g_Config.bReplaceTextures = !g_Config.bReplaceTextures; - if (g_Config.bReplaceTextures) { - g_OSD.Show(OSDType::MESSAGE_SUCCESS, sc->T("replaceTextures_true", "Texture replacement enabled"), 2.0, "replacetexturechanged"); - } else { - g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("replaceTextures_false", "Textures are no longer being replaced"), 2.0, "replacetexturechanged"); - } - System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED); - break; - - case VIRTKEY_MUTE_TOGGLE: - g_Config.bEnableSound = !g_Config.bEnableSound; - break; - - case VIRTKEY_SCREEN_ROTATION_VERTICAL: - { - DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); - config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL; - break; - } - case VIRTKEY_SCREEN_ROTATION_VERTICAL180: - { - DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); - config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL180; - break; - } - case VIRTKEY_SCREEN_ROTATION_HORIZONTAL: - { - DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); - config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL; - break; - } - case VIRTKEY_SCREEN_ROTATION_HORIZONTAL180: - { - DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); - config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL180; - break; - } - - case VIRTKEY_TOGGLE_WLAN: - // Let's not allow the user to toggle wlan while connected, could get confusing. - if (!g_netInited) { - auto n = GetI18NCategory(I18NCat::NETWORKING); - auto di = GetI18NCategory(I18NCat::DIALOG); - g_Config.bEnableWlan = !g_Config.bEnableWlan; - // Try to avoid adding more strings so we piece together a message from existing ones. - g_OSD.Show(OSDType::MESSAGE_INFO, StringFromFormat( - "%s: %s", n->T_cstr("Enable networking"), g_Config.bEnableWlan ? di->T_cstr("Enabled") : di->T_cstr("Disabled")), 2.0, "toggle_wlan"); - } - break; - - case VIRTKEY_TOGGLE_FULLSCREEN: - // TODO: Limit to platforms that can support fullscreen. - g_Config.bFullScreen = !g_Config.bFullScreen; - System_ApplyFullscreenState(); - break; - - case VIRTKEY_TOGGLE_TOUCH_CONTROLS: - if (g_Config.bShowTouchControls) { - // This just messes with opacity if enabled, so you can touch the screen again to bring them back. - if (GamepadGetOpacity() < 0.01f) { - GamepadTouch(); - } else { - GamepadResetTouch(); - } - } else { - // If touch controls are disabled though, they'll get enabled. - g_Config.bShowTouchControls = true; - RecreateViews(); - GamepadTouch(); - } - break; - - case VIRTKEY_REWIND: - if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) { - if (SaveState::CanRewind()) { - SaveState::Rewind(&ShowMessageAfterSaveStateAction); - } else { - g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("norewind", "No rewind save states available"), 2.0); - } - } - break; - - case VIRTKEY_PAUSE_NO_MENU: - if (!NetworkWarnUserIfOnlineAndCantSpeed()) { - // We re-use debug break/resume to implement pause/resume without a menu. - if (coreState == CORE_STEPPING_CPU) { // should we check reason? - Core_Resume(); - } else { - Core_Break(BreakReason::UIPause); - } - } - break; - - case VIRTKEY_EXIT_APP: - { - if (!bootPending_) { - std::string confirmExitMessage = GetConfirmExitMessage(); - if (!confirmExitMessage.empty()) { - auto di = GetI18NCategory(I18NCat::DIALOG); - auto mm = GetI18NCategory(I18NCat::MAINMENU); - confirmExitMessage += '\n'; - confirmExitMessage += di->T("Are you sure you want to exit?"); - screenManager()->push(new UI::MessagePopupScreen(di->T("Exit"), confirmExitMessage, di->T("Yes"), di->T("No"), [](bool result) { - if (result) { - System_ExitApp(); - } - })); - } else { - System_ExitApp(); - } - } - break; - } - - case VIRTKEY_FRAME_ADVANCE: - // Can't do this reliably in an async fashion, so we just set a variable. - // Is this used by anyone? There's no user-friendly way to resume, other than PAUSE_NO_MENU or the debugger. - if (!NetworkWarnUserIfOnlineAndCantSpeed()) { - if (Core_IsStepping()) { - Core_Resume(); - frameStep_ = true; - } else { - Core_Break(BreakReason::FrameAdvance); - } - } - break; - - case VIRTKEY_SPEED_TOGGLE: - if (!NetworkWarnUserIfOnlineAndCantSpeed()) { - // Cycle through enabled speeds. - if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) { - PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1; - } else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit2 >= 0) { - PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2; - } else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 || PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) { - PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL; - } - - ShowFpsLimitNotice(); - } - break; - - case VIRTKEY_RESET_EMULATION: - System_PostUIMessage(UIMessage::REQUEST_GAME_RESET); - break; - -#ifndef MOBILE_DEVICE - case VIRTKEY_RECORD: - if (g_Config.bDumpFrames == g_Config.bDumpAudio) { - g_Config.bDumpFrames = !g_Config.bDumpFrames; - g_Config.bDumpAudio = !g_Config.bDumpAudio; - } else { - // This hotkey should always toggle both audio and video together. - // So let's make sure that's the only outcome even if video OR audio was already being dumped. - if (g_Config.bDumpFrames) { - AVIDump::Stop(); - AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); - g_Config.bDumpAudio = true; - } else { - WAVDump::Reset(); - g_Config.bDumpFrames = true; - } - } - break; -#endif - - case VIRTKEY_SAVE_STATE: - if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) { - SaveState::SaveSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, &ShowMessageAfterSaveStateAction); - } - break; - case VIRTKEY_LOAD_STATE: - if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) { - if (g_Config.bConfirmLoadState) { - std::string prefix = SaveState::GetGamePrefix(g_paramSFO); - int slot = g_Config.iCurrentStateSlot; - screenManager()->push(new LoadStateConfirmScreen(prefix, slot, [prefix, slot](bool result) { - if (result) { - SaveState::LoadSlot(prefix, slot, &ShowMessageAfterSaveStateAction); - } - })); - } else { - SaveState::LoadSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, &ShowMessageAfterSaveStateAction); - } - } - break; - case VIRTKEY_PREVIOUS_SLOT: - if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) { - SaveState::PrevSlot(); - System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT); - } - break; - case VIRTKEY_NEXT_SLOT: - if (!Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) { - SaveState::NextSlot(); - System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT); - } - break; default: break; } @@ -1134,82 +1171,24 @@ void EmuScreen::OnVKeyAnalog(VirtKey virtualKeyCode, float value) { limitMode = PSP_CoreParameter().analogFpsLimit == 60 ? FPSLimit::NORMAL : FPSLimit::ANALOG; } -bool EmuScreen::AllowKeyboardNavigation() const { - return true; -} - -bool EmuScreen::UnsyncKey(const KeyInput &key) { - System_Notify(SystemNotification::ACTIVITY); - - // Update imgui modifier flags - if (key.flags & (KeyInputFlags::DOWN | KeyInputFlags::UP)) { - bool down = (key.flags & KeyInputFlags::DOWN) != 0; - switch (key.keyCode) { - case NKCODE_CTRL_LEFT: keyCtrlLeft_ = down; break; - case NKCODE_CTRL_RIGHT: keyCtrlRight_ = down; break; - case NKCODE_SHIFT_LEFT: keyShiftLeft_ = down; break; - case NKCODE_SHIFT_RIGHT: keyShiftRight_ = down; break; - case NKCODE_ALT_LEFT: keyAltLeft_ = down; break; - case NKCODE_ALT_RIGHT: keyAltRight_ = down; break; - default: break; - } - } - +InputMode EmuScreen::PassInputToMapper() const { const bool chatMenuOpen = chatMenu_ && chatMenu_->GetVisibility() == UI::V_VISIBLE; + if (chatMenuOpen) { + return InputMode::None; + } - if (chatMenuOpen || (g_Config.bShowImDebugger && imguiInited_)) { - // Note: Allow some Vkeys through, so we can toggle the imgui for example (since we actually block the control mapper otherwise in imgui mode). - // We need to manually implement it here :/ - if (g_Config.bShowImDebugger && imguiInited_) { - if (key.flags & (KeyInputFlags::UP | KeyInputFlags::DOWN)) { - InputMapping mapping(key.deviceId, key.keyCode); - std::vector pspButtons; - bool mappingFound = KeyMap::InputMappingToPspButton(mapping, &pspButtons); - if (mappingFound) { - for (auto b : pspButtons) { - if (b == VIRTKEY_TOGGLE_DEBUGGER || b == VIRTKEY_PAUSE) { - return g_controlMapper.Key(key); - } - } - } - } - UI::EnableFocusMovement(false); - // Enable gamepad controls while running imgui (but ignore mouse/keyboard). - switch (key.deviceId) { - case DEVICE_ID_KEYBOARD: - if (!ImGui::GetIO().WantCaptureKeyboard) { - g_controlMapper.Key(key); - } - break; - case DEVICE_ID_MOUSE: - if (!ImGui::GetIO().WantCaptureMouse) { - g_controlMapper.Key(key); - } - break; - default: - g_controlMapper.Key(key); - break; - } - } else { - // Let up-events through to the controlMapper_ so input doesn't get stuck. - if (key.flags & KeyInputFlags::UP) { - g_controlMapper.Key(key); - } + InputMode modes = InputMode::Keyboard | InputMode::Mouse | InputMode::Other | InputMode::ImDebuggerToggle; + if (g_Config.bShowImDebugger && imguiInited_) { + if (ImGui::GetIO().WantCaptureKeyboard) { + modes &= ~InputMode::Keyboard; } - - return UIScreen::UnsyncKey(key); - } - return g_controlMapper.Key(key); -} - -void EmuScreen::UnsyncAxis(const AxisInput *axes, size_t count) { - System_Notify(SystemNotification::ACTIVITY); - - if (UI::IsFocusMovementEnabled()) { - return UIScreen::UnsyncAxis(axes, count); + if (ImGui::GetIO().WantCaptureMouse) { + modes &= ~InputMode::Mouse; + } + return modes; } - return g_controlMapper.Axis(axes, count); + return modes; } bool EmuScreen::key(const KeyInput &key) { @@ -1231,7 +1210,7 @@ bool EmuScreen::key(const KeyInput &key) { return retval; } -void EmuScreen::touch(const TouchInput &touch) { +bool EmuScreen::touch(const TouchInput &touch) { System_Notify(SystemNotification::ACTIVITY); bool ignoreGamepad = false; @@ -1253,7 +1232,7 @@ void EmuScreen::touch(const TouchInput &touch) { if (g_Config.bShowImDebugger && imguiInited_) { ImGui_ImplPlatform_TouchEvent(touch); if (!ImGui::GetIO().WantCaptureMouse) { - UIScreen::touch(touch); + return UIScreen::touch(touch); } } else if (g_Config.bMouseControl && !(touch.flags & TouchInputFlags::UP) && (touch.flags & TouchInputFlags::MOUSE)) { // don't do anything as the mouse pointer is hidden in this case. @@ -1271,8 +1250,9 @@ void EmuScreen::touch(const TouchInput &touch) { } } } - UIScreen::touch(touch); + return UIScreen::touch(touch); } + return false; } // TODO: Shouldn't actually need bounds for this, Anchor can center too. @@ -1943,9 +1923,15 @@ void EmuScreen::runImDebugger() { // Update keyboard modifiers. auto &io = ImGui::GetIO(); - io.AddKeyEvent(ImGuiMod_Ctrl, keyCtrlLeft_ || keyCtrlRight_); - io.AddKeyEvent(ImGuiMod_Shift, keyShiftLeft_ || keyShiftRight_); - io.AddKeyEvent(ImGuiMod_Alt, keyAltLeft_ || keyAltRight_); + + KeyModifier modifiers = NativeGetKeyModifiers(); + + const bool keyCtrl = (modifiers & KeyModifier::LCTRL) || (modifiers & KeyModifier::RCTRL); + const bool keyShift = (modifiers & KeyModifier::LSHIFT) || (modifiers & KeyModifier::RSHIFT); + const bool keyAlt = (modifiers & KeyModifier::LALT) || (modifiers & KeyModifier::RALT); + io.AddKeyEvent(ImGuiMod_Ctrl, keyCtrl); + io.AddKeyEvent(ImGuiMod_Shift, keyShift); + io.AddKeyEvent(ImGuiMod_Alt, keyAlt); // io.AddKeyEvent(ImGuiMod_Super, e.key.super); ImGuiID dockID = ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode); diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index dd84f1d116..3fe396453e 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -52,14 +52,11 @@ public: void resized() override; ScreenRenderRole renderRole(bool isTop) const override; - // Note: Unlike your average boring UIScreen, here we override the Unsync* functions - // to get minimal latency and full control. We forward to UIScreen when needed. - bool UnsyncKey(const KeyInput &key) override; - void UnsyncAxis(const AxisInput *axes, size_t count) override; + InputMode PassInputToMapper() const override; // We also need to do some special handling of queued UI events to handle closing the chat window. bool key(const KeyInput &key) override; - void touch(const TouchInput &key) override; + bool touch(const TouchInput &key) override; void deviceLost() override; void deviceRestored(Draw::DrawContext *draw) override; @@ -69,8 +66,6 @@ public: } protected: - bool AllowKeyboardNavigation() const override; - void darken(); void focusChanged(ScreenFocusChange focusChange) override; ScreenRenderFlags PreRender(ScreenRenderMode mode) override; @@ -102,7 +97,7 @@ private: bool checkPowerDown(); void ProcessQueuedVKeys(); - void ProcessVKey(VirtKey vkey); + void ProcessVKey(VirtKey vkey, bool down); bool ShouldRunEmulation(ScreenRenderMode mode) const; @@ -148,7 +143,8 @@ private: ImCommand imCmd_{}; // needed to buffer commands in case imgui wasn't created yet. bool imguiInited_ = false; - // For ImGui modifier tracking + // For ImGui modifier tracking. It works a bit weirdly and it's easier to track it here + // than to use our global modifier tracking that attaches modifiers to each key event. bool keyCtrlLeft_ = false; bool keyCtrlRight_ = false; bool keyShiftLeft_ = false; @@ -158,7 +154,7 @@ private: bool lastImguiEnabled_ = false; - std::vector queuedVirtKeys_; + std::vector> queuedVirtKeys_; ImGuiContext *ctx_ = nullptr; diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 8eadd8eed8..e1c0278b58 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -419,10 +419,12 @@ bool LogoScreen::key(const KeyInput &key) { return false; } -void LogoScreen::touch(const TouchInput &touch) { +bool LogoScreen::touch(const TouchInput &touch) { if (touch.flags & TouchInputFlags::DOWN) { Next(); + return true; } + return false; } void LogoScreen::DrawForeground(UIContext &dc) { diff --git a/UI/MiscScreens.h b/UI/MiscScreens.h index 14b8e4a3b1..9e9fc8cd0d 100644 --- a/UI/MiscScreens.h +++ b/UI/MiscScreens.h @@ -101,7 +101,7 @@ public: LogoScreen(AfterLogoScreen afterLogoScreen = AfterLogoScreen::DEFAULT); bool key(const KeyInput &key) override; - void touch(const TouchInput &touch) override; + bool touch(const TouchInput &touch) override; void update() override; void DrawForeground(UIContext &ui) override; void sendMessage(UIMessage message, const char *value) override; diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index d4265aa738..23eb404068 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -1369,23 +1369,17 @@ static void ProcessWheelRelease(InputKeyCode keyCode, double now, bool keyPress) } } -enum class Modifier { - NONE = 0, - LCTRL = 1, - RCTRL = 2, - LSHIFT = 4, - RSHIFT = 8, - LALT = 16, - RALT = 32, - LMETA = 64, - RMETA = 128, -}; -ENUM_CLASS_BITOPS(Modifier); -static Modifier g_modifiersPressed{}; +KeyModifier g_modifiersPressed{}; + +KeyModifier NativeGetKeyModifiers() { + return g_modifiersPressed; +} bool NativeKey(const KeyInput &key) { double now = time_now_d(); + System_Notify(SystemNotification::ACTIVITY); + // VR actions if ((IsVREnabled() || g_Config.bForceVR) && !UpdateVRKeys(key)) { return false; @@ -1433,44 +1427,92 @@ bool NativeKey(const KeyInput &key) { } #endif - if (!g_screenManager) { - return false; - } + HLEPlugins::SetKey(key.keyCode, (key.flags & KeyInputFlags::DOWN) ? 1 : 0); // Handle releases of mousewheel keys. if ((key.flags & KeyInputFlags::DOWN) && key.deviceId == DEVICE_ID_MOUSE && (key.keyCode == NKCODE_EXT_MOUSEWHEEL_UP || key.keyCode == NKCODE_EXT_MOUSEWHEEL_DOWN)) { ProcessWheelRelease(key.keyCode, now, true); } - HLEPlugins::SetKey(key.keyCode, (key.flags & KeyInputFlags::DOWN) ? 1 : 0); + if (!g_screenManager) { + return false; + } - // Track and update modifiers. + // Filtering, detailed rules needed for good imgui behavior without having to ask the screen about what to do. + InputMode inputMode = g_screenManager->PassInputToMapper(); + bool passKeyThrough = false; + if (inputMode != InputMode::None) { + if ((inputMode & InputMode::ImDebuggerToggle) && (key.flags & (KeyInputFlags::UP | KeyInputFlags::DOWN))) { + InputMapping mapping(key.deviceId, key.keyCode); + std::vector pspButtons; + bool mappingFound = KeyMap::InputMappingToPspButton(mapping, &pspButtons); + if (mappingFound) { + for (auto b : pspButtons) { + if (b == VIRTKEY_TOGGLE_DEBUGGER || b == VIRTKEY_PAUSE) { + // TRUE + passKeyThrough = true; + } + } + } + } + if (key.deviceId == DEVICE_ID_MOUSE) { + if (inputMode & InputMode::Mouse) { + passKeyThrough = true; + } + } else if (key.deviceId == DEVICE_ID_KEYBOARD) { + if (inputMode & InputMode::Keyboard) { + passKeyThrough = true; + } + } else if (inputMode & InputMode::Other) { + // yes this is different + passKeyThrough = true; + } + } else { + // Pass through only up events. + if (key.flags & KeyInputFlags::UP) { + passKeyThrough = true; + } + } + + if (passKeyThrough) { + g_controlMapper.Key(key); + } + + // Ignore volume keys and stuff here - though we do send them through to the control mapper, so they can be mapped to PSP buttons. + switch (key.keyCode) { + case NKCODE_VOLUME_DOWN: + case NKCODE_VOLUME_UP: + case NKCODE_VOLUME_MUTE: + return false; + default: + break; + } // Track modifier keys. if (key.flags & KeyInputFlags::DOWN) { switch (key.keyCode) { - case NKCODE_CTRL_LEFT: g_modifiersPressed |= Modifier::LCTRL; break; - case NKCODE_CTRL_RIGHT: g_modifiersPressed |= Modifier::RCTRL; break; - case NKCODE_SHIFT_LEFT: g_modifiersPressed |= Modifier::LSHIFT; break; - case NKCODE_SHIFT_RIGHT: g_modifiersPressed |= Modifier::RSHIFT; break; - case NKCODE_ALT_LEFT: g_modifiersPressed |= Modifier::LALT; break; - case NKCODE_ALT_RIGHT: g_modifiersPressed |= Modifier::RALT; break; - case NKCODE_META_LEFT: g_modifiersPressed |= Modifier::LMETA; break; - case NKCODE_META_RIGHT: g_modifiersPressed |= Modifier::RMETA; break; + case NKCODE_CTRL_LEFT: g_modifiersPressed |= KeyModifier::LCTRL; break; + case NKCODE_CTRL_RIGHT: g_modifiersPressed |= KeyModifier::RCTRL; break; + case NKCODE_SHIFT_LEFT: g_modifiersPressed |= KeyModifier::LSHIFT; break; + case NKCODE_SHIFT_RIGHT: g_modifiersPressed |= KeyModifier::RSHIFT; break; + case NKCODE_ALT_LEFT: g_modifiersPressed |= KeyModifier::LALT; break; + case NKCODE_ALT_RIGHT: g_modifiersPressed |= KeyModifier::RALT; break; + case NKCODE_META_LEFT: g_modifiersPressed |= KeyModifier::LMETA; break; + case NKCODE_META_RIGHT: g_modifiersPressed |= KeyModifier::RMETA; break; default: break; } } if (key.flags & KeyInputFlags::UP) { switch (key.keyCode) { - case NKCODE_CTRL_LEFT: g_modifiersPressed &= ~Modifier::LCTRL; break; - case NKCODE_CTRL_RIGHT: g_modifiersPressed &= ~Modifier::RCTRL; break; - case NKCODE_SHIFT_LEFT: g_modifiersPressed &= ~Modifier::LSHIFT; break; - case NKCODE_SHIFT_RIGHT: g_modifiersPressed &= ~Modifier::RSHIFT; break; - case NKCODE_ALT_LEFT: g_modifiersPressed &= ~Modifier::LALT; break; - case NKCODE_ALT_RIGHT: g_modifiersPressed &= ~Modifier::RALT; break; - case NKCODE_META_LEFT: g_modifiersPressed &= ~Modifier::LMETA; break; - case NKCODE_META_RIGHT: g_modifiersPressed &= ~Modifier::RMETA; break; + case NKCODE_CTRL_LEFT: g_modifiersPressed &= ~KeyModifier::LCTRL; break; + case NKCODE_CTRL_RIGHT: g_modifiersPressed &= ~KeyModifier::RCTRL; break; + case NKCODE_SHIFT_LEFT: g_modifiersPressed &= ~KeyModifier::LSHIFT; break; + case NKCODE_SHIFT_RIGHT: g_modifiersPressed &= ~KeyModifier::RSHIFT; break; + case NKCODE_ALT_LEFT: g_modifiersPressed &= ~KeyModifier::LALT; break; + case NKCODE_ALT_RIGHT: g_modifiersPressed &= ~KeyModifier::RALT; break; + case NKCODE_META_LEFT: g_modifiersPressed &= ~KeyModifier::LMETA; break; + case NKCODE_META_RIGHT: g_modifiersPressed &= ~KeyModifier::RMETA; break; default: break; } @@ -1478,24 +1520,44 @@ bool NativeKey(const KeyInput &key) { KeyInputFlags modifierFlags{}; - if (g_modifiersPressed & (Modifier::LCTRL | Modifier::RCTRL)) { + if (g_modifiersPressed & (KeyModifier::LCTRL | KeyModifier::RCTRL)) { modifierFlags |= KeyInputFlags::MOD_CTRL; } - if (g_modifiersPressed & (Modifier::LSHIFT | Modifier::RSHIFT)) { + if (g_modifiersPressed & (KeyModifier::LSHIFT | KeyModifier::RSHIFT)) { modifierFlags |= KeyInputFlags::MOD_SHIFT; } - if (g_modifiersPressed & (Modifier::LALT | Modifier::RALT)) { + if (g_modifiersPressed & (KeyModifier::LALT | KeyModifier::RALT)) { modifierFlags |= KeyInputFlags::MOD_ALT; } - if (g_modifiersPressed & (Modifier::LMETA | Modifier::RMETA)) { + if (g_modifiersPressed & (KeyModifier::LMETA | KeyModifier::RMETA)) { modifierFlags |= KeyInputFlags::MOD_META; } KeyInput modKey = key; modKey.flags |= modifierFlags; + bool retval = false; + + UI::KeyEventResult kev = UI::KeyEventToFocusMoves(key); + if (!(key.flags & KeyInputFlags::IS_REPEAT)) { + // If a repeat, we follow what KeyEventToFocusMoves set it to. + // Otherwise we signal that we used the key, always. + kev = UI::KeyEventResult::ACCEPT; + } + + switch (kev) { + case UI::KeyEventResult::ACCEPT: + retval = true; + break; + case UI::KeyEventResult::PASS_THROUGH: + retval = false; + break; + case UI::KeyEventResult::IGNORE_KEY: + return false; + } + // Dispatch the key event. - bool retval = g_screenManager->key(modKey); + g_screenManager->key(modKey); // The Mode key can have weird consequences on some devices, see #17245. if (key.keyCode == NKCODE_BUTTON_MODE) { @@ -1512,11 +1574,17 @@ void NativeAxis(const AxisInput *axes, size_t count) { return; } + System_Notify(SystemNotification::ACTIVITY); + if (!g_screenManager) { // Too early. return; } + if (g_screenManager->PassInputToMapper() & (InputMode::Other | InputMode::ImDebuggerToggle)) { + g_controlMapper.Axis(axes, count); + } + g_screenManager->axis(axes, count); for (size_t i = 0; i < count; i++) { diff --git a/UI/OnScreenDisplay.cpp b/UI/OnScreenDisplay.cpp index 271855fd7a..79ec0d3357 100644 --- a/UI/OnScreenDisplay.cpp +++ b/UI/OnScreenDisplay.cpp @@ -389,7 +389,7 @@ bool OnScreenMessagesView::Dismiss(float x, float y) { return dismissed; } -bool OSDOverlayScreen::UnsyncTouch(const TouchInput &touch) { +bool OSDOverlayScreen::touch(const TouchInput &touch) { // Don't really need to forward. // UIScreen::UnsyncTouch(touch); if ((touch.flags & TouchInputFlags::DOWN) && osmView_) { diff --git a/UI/OnScreenDisplay.h b/UI/OnScreenDisplay.h index 7ed2f78d91..7206a01651 100644 --- a/UI/OnScreenDisplay.h +++ b/UI/OnScreenDisplay.h @@ -38,7 +38,7 @@ class OSDOverlayScreen : public UIScreen { public: const char *tag() const override { return "OSDOverlayScreen"; } - bool UnsyncTouch(const TouchInput &touch) override; + bool touch(const TouchInput &touch) override; void CreateViews() override; void DrawForeground(UIContext &ui) override; diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 2d11eefe00..22cafec1a3 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -335,6 +335,10 @@ void GamePauseScreen::update() { UpdateUIState(UISTATE_PAUSEMENU); UIScreen::update(); + if (g_controlMapper.PollPauseTrigger()) { + TriggerFinish(DR_BACK); + } + if (finishNextFrame_) { TriggerFinish(finishNextFrameResult_); finishNextFrame_ = false; @@ -376,20 +380,6 @@ GamePauseScreen::~GamePauseScreen() { __DisplaySetWasPaused(); } -bool GamePauseScreen::UnsyncKey(const KeyInput &key) { - bool retval = UIScreen::UnsyncKey(key); - retval = g_controlMapper.Key(key) || retval; - if (g_controlMapper.PollPauseTrigger()) { - TriggerFinish(DR_BACK); - } - return retval; -} - -void GamePauseScreen::UnsyncAxis(const AxisInput *axes, size_t count) { - UIScreen::UnsyncAxis(axes, count); - g_controlMapper.Axis(axes, count); -} - void GamePauseScreen::OnVKey(VirtKey virtualKeyCode, bool down) { // Simple de-bounce using createdTime_, just to be safe. if (down && virtualKeyCode == VIRTKEY_PAUSE && time_now_d() > createdTime_ + 0.1) { diff --git a/UI/PauseScreen.h b/UI/PauseScreen.h index e7115c4458..5e8452f330 100644 --- a/UI/PauseScreen.h +++ b/UI/PauseScreen.h @@ -38,6 +38,10 @@ public: const char *tag() const override { return "GamePause"; } + InputMode PassInputToMapper() const override { + return InputMode::ImDebuggerToggle; + } + protected: void CreateViews() override; void update() override; @@ -45,10 +49,6 @@ protected: ViewLayoutMode LayoutMode() const override { return ViewLayoutMode::ApplyInsets; } - // For processing of certain mapped keys. - bool UnsyncKey(const KeyInput &key) override; - void UnsyncAxis(const AxisInput *axes, size_t count) override; - void OnVKey(VirtKey virtualKeyCode, bool down) override; private: diff --git a/b-ios.sh b/b-ios.sh index bebe192e63..10ac7e3322 100755 --- a/b-ios.sh +++ b/b-ios.sh @@ -36,7 +36,7 @@ echo ' cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchains/ios.cmake -GXcode .. # Build PPSSPP using xcode #xcodebuild clean build -project PPSSPP.xcodeproj CODE_SIGNING_ALLOWED=NO -sdk iphoneos -configuration Release -xcodebuild -project PPSSPP.xcodeproj -scheme PPSSPP -sdk iphoneos -configuration Release clean build archive -archivePath ./build/PPSSPP.xcarchive CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO #CODE_SIGN_IDENTITY="iPhone Distribution: Your NAME / Company (TeamID)" #PROVISIONING_PROFILE="xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +xcodebuild -project PPSSPP.xcodeproj -scheme PPSSPP -sdk iphoneos -configuration Release -quiet clean build archive -archivePath ./build/PPSSPP.xcarchive CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO #CODE_SIGN_IDENTITY="iPhone Distribution: Your NAME / Company (TeamID)" #PROVISIONING_PROFILE="xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Export IPA file from xcarchive (probably only works with signed build) #xcodebuild -exportArchive -archivePath ./build/PPSSPP.xcarchive -exportPath ./build -exportOptionsPlist exportOptions.plist # This folder only exist when building with xcodebuild