Merge pull request #21857 from hrydgard/more-input-refactor

Rework input queueing for UI, remove Unsync* events
This commit is contained in:
Henrik Rydgård
2026-06-25 14:58:25 +02:00
committed by GitHub
26 changed files with 647 additions and 659 deletions
-3
View File
@@ -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'
+16
View File
@@ -3,6 +3,8 @@
#include <functional>
#include <string>
#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.
+3 -3
View File
@@ -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) {
+1 -1
View File
@@ -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;
+1 -22
View File
@@ -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);
}
+1 -2
View File
@@ -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);
+80 -44
View File
@@ -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<std::recursive_mutex> 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<QueuedEvent> events;
{
std::lock_guard<std::mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::mutex> guard(eventQueueLock_);
eventQueue_.push_back(ev);
}
bool ScreenManager::key(const KeyInput &key) {
std::lock_guard<std::recursive_mutex> 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<std::mutex> guard(eventQueueLock_);
eventQueue_.push_back(ev);
}
void ScreenManager::axis(const AxisInput *axes, size_t count) {
std::lock_guard<std::recursive_mutex> guard(inputLock_);
if (!stack_.empty()) {
stack_.back().screen->UnsyncAxis(axes, count);
QueuedEvent ev{};
ev.type = QueuedEventType::AXIS;
std::lock_guard<std::mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> guard(inputLock_);
// Another dialog may have been pushed before the render, so search for it.
Screen *caller = dialogParent(dialogFinished_);
bool erased = false;
+42 -8
View File
@@ -16,6 +16,7 @@
#include <cstdint>
#include <mutex>
#include <string>
#include <deque>
#include <unordered_map>
#include <vector>
@@ -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<Layer> nextStack_;
std::unordered_map<int64_t, int> lastAxis_;
std::mutex eventQueueLock_;
std::deque<QueuedEvent> eventQueue_;
InputMode passInputToMapper_ = InputMode::None;
};
+11 -91
View File
@@ -33,8 +33,6 @@ void UIScreen::DoRecreateViews() {
return;
}
std::lock_guard<std::recursive_mutex> 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<UI::View *> 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<UI::View *> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<UI::View *> 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) {
+3 -26
View File
@@ -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<QueuedEvent> eventQueue_;
};
class UIDialogScreen : public UIScreen {
+1 -1
View File
@@ -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;
}
}
+1 -1
View File
@@ -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.
-16
View File
@@ -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 {
+4 -1
View File
@@ -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:
+2 -1
View File
@@ -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.
+1 -1
View File
@@ -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;
+352 -366
View File
@@ -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<int> 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);
+6 -10
View File
@@ -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<VirtKey> queuedVirtKeys_;
std::vector<std::pair<VirtKey, bool>> queuedVirtKeys_;
ImGuiContext *ctx_ = nullptr;
+3 -1
View File
@@ -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) {
+1 -1
View File
@@ -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;
+107 -39
View File
@@ -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<int> 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++) {
+1 -1
View File
@@ -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_) {
+1 -1
View File
@@ -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;
+4 -14
View File
@@ -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) {
+4 -4
View File
@@ -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:
+1 -1
View File
@@ -36,7 +36,7 @@ echo '<?xml version="1.0" encoding="UTF-8"?>
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