Files
ppsspp/ios/Controls.mm
T
Henrik RydgårdandClaude Opus 5.5 cfcf8cc17c iOS: Remove the iCade support
It has done nothing since 2017. iCade input came through iCadeReaderView,
a hidden UIKeyInput view that decoded the controller's keystrokes, and
f0ba66fcd6 ("add support for smart keyboard") commented out the code
that created it. Nothing has made one since, so the view controller's
iCadeEventDelegate methods and the ICadeTracker behind them were never
called.

iCade controllers present themselves to iOS as Bluetooth keyboards, so
their keys still arrive as hardware keyboard presses, which can be
mapped like any other keys. Modern controllers go through
GameController.framework anyway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:10:37 +02:00

457 lines
17 KiB
Plaintext

#include "Controls.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Log.h"
#include "Common/TimeUtil.h"
#include "Common/Input/InputState.h"
#include "Common/System/NativeApp.h"
#include "Common/System/Display.h"
#include "Core/KeyMap.h"
static void controllerButtonPressed(BOOL pressed, InputKeyCode keyCode) {
KeyInput key;
key.deviceId = DEVICE_ID_PAD_0;
key.flags = pressed ? KeyInputFlags::DOWN : KeyInputFlags::UP;
key.keyCode = keyCode;
NativeKey(key);
}
static void analogTriggerPressed(InputAxis axis, float value) {
AxisInput axisInput;
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.axisId = axis;
axisInput.value = value;
NativeAxis(&axisInput, 1);
}
bool InitController(GCController *controller) {
GCExtendedGamepad *extendedProfile = controller.extendedGamepad;
if (extendedProfile == nil) {
return false;
}
if (@available(iOS 14.0, tvOS 14.0, *)) {
for (GCControllerElement* element in controller.physicalInputProfile.allElements) {
element.preferredSystemGestureState = GCSystemGestureStateDisabled;
}
}
const std::string controllerName = [controller.vendorName ?: @"Gamepad" UTF8String];
KeyMap::NotifyPadConnected(DEVICE_ID_PAD_0, controllerName);
extendedProfile.buttonA.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_2); // Cross
};
extendedProfile.buttonB.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_3); // Circle
};
extendedProfile.buttonX.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_4); // Square
};
extendedProfile.buttonY.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_1); // Triangle
};
extendedProfile.leftShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_L1); // LTrigger
};
extendedProfile.rightShoulder.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_R1); // RTrigger
};
extendedProfile.dpad.up.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_DPAD_UP);
};
extendedProfile.dpad.down.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_DPAD_DOWN);
};
extendedProfile.dpad.left.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_DPAD_LEFT);
};
extendedProfile.dpad.right.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_DPAD_RIGHT);
};
extendedProfile.leftTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
INFO_LOG(Log::System, "ltrigger: %f %d", value, (int)pressed);
analogTriggerPressed(JOYSTICK_AXIS_LTRIGGER, value);
};
extendedProfile.rightTrigger.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
INFO_LOG(Log::System, "rtrigger: %f %d", value, (int)pressed);
analogTriggerPressed(JOYSTICK_AXIS_RTRIGGER, value);
};
// The optional buttons are nil on controllers that don't have them.
if (extendedProfile.leftThumbstickButton != nil) {
extendedProfile.leftThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_THUMBL);
};
}
if (extendedProfile.rightThumbstickButton != nil) {
extendedProfile.rightThumbstickButton.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_THUMBR);
};
}
if (extendedProfile.buttonOptions != nil) {
extendedProfile.buttonOptions.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_SELECT);
};
}
if (extendedProfile.buttonMenu != nil) {
extendedProfile.buttonMenu.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_BUTTON_START);
};
}
if (@available(iOS 14.0, *)) {
if (extendedProfile.buttonHome != nil) {
extendedProfile.buttonHome.valueChangedHandler = ^(GCControllerButtonInput *button, float value, BOOL pressed) {
controllerButtonPressed(pressed, NKCODE_HOME);
};
}
}
extendedProfile.leftThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
AxisInput axisInput;
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.axisId = JOYSTICK_AXIS_X;
axisInput.value = value;
NativeAxis(&axisInput, 1);
};
extendedProfile.leftThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
AxisInput axisInput;
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.axisId = JOYSTICK_AXIS_Y;
axisInput.value = -value;
NativeAxis(&axisInput, 1);
};
// Map right thumbstick as another analog stick, particularly useful for controllers
// like the DualShock 3/4 when connected to an iOS device
extendedProfile.rightThumbstick.xAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
AxisInput axisInput;
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.axisId = JOYSTICK_AXIS_Z;
axisInput.value = value;
NativeAxis(&axisInput, 1);
};
extendedProfile.rightThumbstick.yAxis.valueChangedHandler = ^(GCControllerAxisInput *axis, float value) {
AxisInput axisInput;
axisInput.deviceId = DEVICE_ID_PAD_0;
axisInput.axisId = JOYSTICK_AXIS_RZ;
axisInput.value = -value;
NativeAxis(&axisInput, 1);
};
return true;
}
void ShutdownController(GCController *controller) {
if (@available(iOS 14.0, tvOS 14.0, *)) {
for (GCControllerElement* element in controller.physicalInputProfile.allElements) {
element.preferredSystemGestureState = GCSystemGestureStateEnabled;
}
}
static const InputKeyCode keycodes[] = {
NKCODE_BUTTON_1,
NKCODE_BUTTON_2,
NKCODE_BUTTON_3,
NKCODE_BUTTON_4,
NKCODE_BUTTON_L1,
NKCODE_BUTTON_R1,
NKCODE_DPAD_UP,
NKCODE_DPAD_DOWN,
NKCODE_DPAD_LEFT,
NKCODE_DPAD_RIGHT,
NKCODE_BUTTON_THUMBL,
NKCODE_BUTTON_THUMBR,
NKCODE_BUTTON_SELECT,
NKCODE_BUTTON_START,
NKCODE_HOME
};
for (InputKeyCode keyCode : keycodes) {
KeyInput key;
key.deviceId = DEVICE_ID_PAD_0;
key.flags = KeyInputFlags::UP;
key.keyCode = keyCode;
NativeKey(key);
}
static const InputAxis axes[] = {
JOYSTICK_AXIS_X,
JOYSTICK_AXIS_Y,
JOYSTICK_AXIS_Z,
JOYSTICK_AXIS_RZ,
JOYSTICK_AXIS_LTRIGGER,
JOYSTICK_AXIS_RTRIGGER
};
AxisInput axisInput;
axisInput.deviceId = DEVICE_ID_PAD_0;
for (InputAxis axis : axes) {
axisInput.axisId = axis;
axisInput.value = 0.0f;
NativeAxis(&axisInput, 1);
}
KeyMap::NotifyPadDisconnected(DEVICE_ID_PAD_0);
}
void TouchTracker::SendTouchEvent(CGPoint point, UIView *view, int code, int pointerId) {
// From points to dp. The view covers the whole display.
CGSize size = view.bounds.size;
if (size.width <= 0.0 || size.height <= 0.0) {
return;
}
TouchInput input;
input.x = point.x * g_display.dp_xres / size.width;
input.y = point.y * g_display.dp_yres / size.height;
switch (code) {
case 1: input.flags = TouchInputFlags::DOWN; break;
case 2: input.flags = TouchInputFlags::UP; break;
default: input.flags = TouchInputFlags::MOVE; break;
}
input.id = pointerId;
NativeTouch(input);
}
int TouchTracker::ToTouchID(UITouch *uiTouch, bool allowAllocate) {
// Find the id for the touch.
for (int localId = 0; localId < (int)ARRAY_SIZE(touches_); ++localId) {
if (touches_[localId] == uiTouch) {
return localId;
}
}
// Allocate a new one, perhaps?
if (allowAllocate) {
for (int localId = 0; localId < (int)ARRAY_SIZE(touches_); ++localId) {
if (touches_[localId] == 0) {
touches_[localId] = uiTouch;
return localId;
}
}
// None were free. Ignore?
return 0;
}
return -1;
}
void TouchTracker::Began(NSSet *touches, UIView *view) {
for (UITouch* touch in touches) {
CGPoint point = [touch locationInView:view];
int touchId = ToTouchID(touch, true);
SendTouchEvent(point, view, 1, touchId);
}
}
void TouchTracker::Moved(NSSet *touches, UIView *view) {
for (UITouch* touch in touches) {
CGPoint point = [touch locationInView:view];
int touchId = ToTouchID(touch, true);
SendTouchEvent(point, view, 0, touchId);
}
}
void TouchTracker::Ended(NSSet *touches, UIView *view) {
for (UITouch* touch in touches) {
CGPoint point = [touch locationInView:view];
int touchId = ToTouchID(touch, false);
if (touchId >= 0) {
SendTouchEvent(point, view, 2, touchId);
touches_[touchId] = nullptr;
}
}
}
void TouchTracker::Cancelled(NSSet *touches, UIView *view) {
for (UITouch* touch in touches) {
CGPoint point = [touch locationInView:view];
int touchId = ToTouchID(touch, false);
if (touchId >= 0) {
SendTouchEvent(point, view, 2, touchId);
touches_[touchId] = nullptr;
}
}
}
void ProcessAccelerometerData(CMAccelerometerData *accData) {
CMAcceleration acc = accData.acceleration;
// INFO_LOG(SYSTEM, "%f %f %f", acc.x, acc.y, acc.z);
// Might need to change these for portrait or inverse landscape
NativeAccelerometer(-acc.x, -acc.y, -acc.z);
}
void SendKeyboardChars(std::string_view str) {
UTF8 chars(str);
while (!chars.end()) {
uint32_t codePoint = chars.next();
KeyInput input{};
input.deviceId = DEVICE_ID_KEYBOARD;
input.flags = KeyInputFlags::CHAR;
input.unicodeChar = codePoint;
NativeKey(input);
}
}
static void SendPresses(NSSet<UIPress *> *presses, KeyInputFlags flags) {
// UIPress.key, and with it hardware keyboard support, needs 13.4.
if (@available(iOS 13.4, *)) {
for (UIPress *press in presses) {
if (!press.key) {
// I guess we could support remotes and stuff.
continue;
}
InputKeyCode code = HIDUsageToInputKeyCode(press.key.keyCode);
if (code != NKCODE_UNKNOWN) {
KeyInput input{};
input.deviceId = DEVICE_ID_KEYBOARD;
input.keyCode = code;
input.flags = flags;
NativeKey(input);
}
if ((flags & KeyInputFlags::DOWN) && press.key.characters) {
std::string chars([press.key.characters UTF8String]);
SendKeyboardChars(chars);
}
}
}
}
void KeyboardPressesBegan(NSSet<UIPress *> *presses, UIPressesEvent *event) {
SendPresses(presses, KeyInputFlags::DOWN);
}
void KeyboardPressesEnded(NSSet<UIPress *> *presses, UIPressesEvent *event) {
SendPresses(presses, KeyInputFlags::UP);
}
InputKeyCode HIDUsageToInputKeyCode(UIKeyboardHIDUsage usage) {
switch (usage) {
case UIKeyboardHIDUsageKeyboardA: return NKCODE_A;
case UIKeyboardHIDUsageKeyboardB: return NKCODE_B;
case UIKeyboardHIDUsageKeyboardC: return NKCODE_C;
case UIKeyboardHIDUsageKeyboardD: return NKCODE_D;
case UIKeyboardHIDUsageKeyboardE: return NKCODE_E;
case UIKeyboardHIDUsageKeyboardF: return NKCODE_F;
case UIKeyboardHIDUsageKeyboardG: return NKCODE_G;
case UIKeyboardHIDUsageKeyboardH: return NKCODE_H;
case UIKeyboardHIDUsageKeyboardI: return NKCODE_I;
case UIKeyboardHIDUsageKeyboardJ: return NKCODE_J;
case UIKeyboardHIDUsageKeyboardK: return NKCODE_K;
case UIKeyboardHIDUsageKeyboardL: return NKCODE_L;
case UIKeyboardHIDUsageKeyboardM: return NKCODE_M;
case UIKeyboardHIDUsageKeyboardN: return NKCODE_N;
case UIKeyboardHIDUsageKeyboardO: return NKCODE_O;
case UIKeyboardHIDUsageKeyboardP: return NKCODE_P;
case UIKeyboardHIDUsageKeyboardQ: return NKCODE_Q;
case UIKeyboardHIDUsageKeyboardR: return NKCODE_R;
case UIKeyboardHIDUsageKeyboardS: return NKCODE_S;
case UIKeyboardHIDUsageKeyboardT: return NKCODE_T;
case UIKeyboardHIDUsageKeyboardU: return NKCODE_U;
case UIKeyboardHIDUsageKeyboardV: return NKCODE_V;
case UIKeyboardHIDUsageKeyboardW: return NKCODE_W;
case UIKeyboardHIDUsageKeyboardX: return NKCODE_X;
case UIKeyboardHIDUsageKeyboardY: return NKCODE_Y;
case UIKeyboardHIDUsageKeyboardZ: return NKCODE_Z;
case UIKeyboardHIDUsageKeyboard1: return NKCODE_1;
case UIKeyboardHIDUsageKeyboard2: return NKCODE_2;
case UIKeyboardHIDUsageKeyboard3: return NKCODE_3;
case UIKeyboardHIDUsageKeyboard4: return NKCODE_4;
case UIKeyboardHIDUsageKeyboard5: return NKCODE_5;
case UIKeyboardHIDUsageKeyboard6: return NKCODE_6;
case UIKeyboardHIDUsageKeyboard7: return NKCODE_7;
case UIKeyboardHIDUsageKeyboard8: return NKCODE_8;
case UIKeyboardHIDUsageKeyboard9: return NKCODE_9;
case UIKeyboardHIDUsageKeyboard0: return NKCODE_0;
case UIKeyboardHIDUsageKeyboardReturnOrEnter: return NKCODE_NUMPAD_ENTER;
case UIKeyboardHIDUsageKeyboardEscape: return NKCODE_ESCAPE;
case UIKeyboardHIDUsageKeyboardDeleteOrBackspace: return NKCODE_DEL; // really.
case UIKeyboardHIDUsageKeyboardTab: return NKCODE_TAB;
case UIKeyboardHIDUsageKeyboardSpacebar: return NKCODE_SPACE;
case UIKeyboardHIDUsageKeyboardHyphen: return NKCODE_MINUS;
case UIKeyboardHIDUsageKeyboardEqualSign: return NKCODE_EQUALS;
case UIKeyboardHIDUsageKeyboardOpenBracket: return NKCODE_LEFT_BRACKET;
case UIKeyboardHIDUsageKeyboardCloseBracket: return NKCODE_RIGHT_BRACKET;
case UIKeyboardHIDUsageKeyboardBackslash: return NKCODE_BACKSLASH;
case UIKeyboardHIDUsageKeyboardNonUSPound: return NKCODE_POUND;
case UIKeyboardHIDUsageKeyboardSemicolon: return NKCODE_SEMICOLON;
case UIKeyboardHIDUsageKeyboardQuote: return NKCODE_APOSTROPHE;
case UIKeyboardHIDUsageKeyboardGraveAccentAndTilde: return NKCODE_GRAVE;
case UIKeyboardHIDUsageKeyboardComma: return NKCODE_COMMA;
case UIKeyboardHIDUsageKeyboardPeriod: return NKCODE_PERIOD;
case UIKeyboardHIDUsageKeyboardSlash: return NKCODE_SLASH;
case UIKeyboardHIDUsageKeyboardCapsLock: return NKCODE_CAPS_LOCK;
case UIKeyboardHIDUsageKeyboardF1: return NKCODE_F1;
case UIKeyboardHIDUsageKeyboardF2: return NKCODE_F2;
case UIKeyboardHIDUsageKeyboardF3: return NKCODE_F3;
case UIKeyboardHIDUsageKeyboardF4: return NKCODE_F4;
case UIKeyboardHIDUsageKeyboardF5: return NKCODE_F5;
case UIKeyboardHIDUsageKeyboardF6: return NKCODE_F6;
case UIKeyboardHIDUsageKeyboardF7: return NKCODE_F7;
case UIKeyboardHIDUsageKeyboardF8: return NKCODE_F8;
case UIKeyboardHIDUsageKeyboardF9: return NKCODE_F9;
case UIKeyboardHIDUsageKeyboardF10: return NKCODE_F10;
case UIKeyboardHIDUsageKeyboardF11: return NKCODE_F11;
case UIKeyboardHIDUsageKeyboardF12: return NKCODE_F12;
case UIKeyboardHIDUsageKeyboardScrollLock: return NKCODE_SCROLL_LOCK;
case UIKeyboardHIDUsageKeyboardInsert: return NKCODE_INSERT;
case UIKeyboardHIDUsageKeyboardHome: return NKCODE_HOME;
case UIKeyboardHIDUsageKeyboardPageUp: return NKCODE_PAGE_UP;
case UIKeyboardHIDUsageKeyboardDeleteForward: return NKCODE_DEL;
case UIKeyboardHIDUsageKeyboardEnd: return NKCODE_MOVE_END;
case UIKeyboardHIDUsageKeyboardPageDown: return NKCODE_PAGE_DOWN;
case UIKeyboardHIDUsageKeyboardRightArrow: return NKCODE_DPAD_RIGHT;
case UIKeyboardHIDUsageKeyboardLeftArrow: return NKCODE_DPAD_LEFT;
case UIKeyboardHIDUsageKeyboardDownArrow: return NKCODE_DPAD_DOWN;
case UIKeyboardHIDUsageKeyboardUpArrow: return NKCODE_DPAD_UP;
case UIKeyboardHIDUsageKeypadNumLock: return NKCODE_NUM_LOCK;
case UIKeyboardHIDUsageKeypadSlash: return NKCODE_NUMPAD_DIVIDE;
case UIKeyboardHIDUsageKeypadAsterisk: return NKCODE_NUMPAD_MULTIPLY;
case UIKeyboardHIDUsageKeypadHyphen: return NKCODE_NUMPAD_SUBTRACT;
case UIKeyboardHIDUsageKeypadPlus: return NKCODE_NUMPAD_ADD;
case UIKeyboardHIDUsageKeypadEnter: return NKCODE_NUMPAD_ENTER;
case UIKeyboardHIDUsageKeypad1: return NKCODE_NUMPAD_1;
case UIKeyboardHIDUsageKeypad2: return NKCODE_NUMPAD_2;
case UIKeyboardHIDUsageKeypad3: return NKCODE_NUMPAD_3;
case UIKeyboardHIDUsageKeypad4: return NKCODE_NUMPAD_4;
case UIKeyboardHIDUsageKeypad5: return NKCODE_NUMPAD_5;
case UIKeyboardHIDUsageKeypad6: return NKCODE_NUMPAD_6;
case UIKeyboardHIDUsageKeypad7: return NKCODE_NUMPAD_7;
case UIKeyboardHIDUsageKeypad8: return NKCODE_NUMPAD_8;
case UIKeyboardHIDUsageKeypad9: return NKCODE_NUMPAD_9;
case UIKeyboardHIDUsageKeypad0: return NKCODE_NUMPAD_0;
case UIKeyboardHIDUsageKeypadPeriod: return NKCODE_NUMPAD_COMMA;
case UIKeyboardHIDUsageKeyboardNonUSBackslash: return NKCODE_BACKSLASH;
case UIKeyboardHIDUsageKeyboardApplication: return NKCODE_APP_SWITCH;
case UIKeyboardHIDUsageKeyboardPower: return NKCODE_POWER;
case UIKeyboardHIDUsageKeypadEqualSign: return NKCODE_NUMPAD_EQUALS;
case UIKeyboardHIDUsageKeyboardMenu: return NKCODE_MENU;
case UIKeyboardHIDUsageKeyboardMute: return NKCODE_MUTE;
case UIKeyboardHIDUsageKeyboardVolumeUp: return NKCODE_VOLUME_UP;
case UIKeyboardHIDUsageKeypadComma: return NKCODE_NUMPAD_COMMA;
case UIKeyboardHIDUsageKeyboardLeftShift: return NKCODE_SHIFT_LEFT;
case UIKeyboardHIDUsageKeyboardRightShift: return NKCODE_SHIFT_RIGHT;
case UIKeyboardHIDUsageKeyboardLeftControl: return NKCODE_CTRL_LEFT;
case UIKeyboardHIDUsageKeyboardRightControl: return NKCODE_CTRL_RIGHT;
case UIKeyboardHIDUsageKeyboardLeftAlt: return NKCODE_ALT_LEFT;
case UIKeyboardHIDUsageKeyboardRightAlt: return NKCODE_ALT_RIGHT;
case UIKeyboardHIDUsageKeyboardLeftGUI: return NKCODE_META_LEFT;
case UIKeyboardHIDUsageKeyboardRightGUI: return NKCODE_META_RIGHT;
default: return NKCODE_UNKNOWN;
}
}