mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-03 03:05:18 +02:00
GetStringErrorMsg had the strerror_r result test backwards. The XSI variant returns 0 on success, so every successful lookup returned "Unknown error"; and under glibc with _GNU_SOURCE the GNU variant is selected instead, which returns the message by pointer and typically leaves the buffer untouched, so it returned an empty string. Either way GetLastErrorMsg() was useless on Linux, Android and macOS. Pick the right handling by overload resolution rather than guessing which signature we got. KeyMap's "no gamepad button mapped to cancel" fallback pushed into confirmKeys instead of cancelKeys - and pushed the confirm button. So unmapping cancel left no gamepad way out of menus, and duplicated an entry in the confirm list. ControlMapper::AddListener mutated listeners_ without taking mutex_, while RemoveListener takes it and the input thread iterates the vector under it. Opening a screen while an axis is moving could reallocate it mid-iteration. The comment about piggybacking on a screenmanager mutex was stale - there isn't one. Config's two std::stof calls on PostShaderSetting values ran on user-editable ini text with no try/catch, so a malformed entry called std::terminate during startup config load. Use the same checked sscanf that LoadGameConfig already uses. (CmdLine.cpp and Compatibility.cpp have the same pattern; not touched here.) The screenshot downscale path leaked its final buffer on every downscaled shot, which savestate thumbnails hit on every save at 3x and above. HandleUploadPost is registered unconditionally, so closing the Upload screen left an unauthenticated file-write endpoint live for as long as anything else kept the server up. Check the flag in the handler. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCPmm7FoQUoqrbMdhfqhQ2
1061 lines
31 KiB
C++
1061 lines
31 KiB
C++
// Copyright (c) 2013- PPSSPP Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0 or later versions.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official git repository and contact information can be found at
|
|
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
|
|
|
|
#include <algorithm>
|
|
#include <set>
|
|
#include <unordered_map>
|
|
#include <mutex>
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include "Common/System/NativeApp.h"
|
|
#include "Common/System/System.h"
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/Data/Text/I18n.h"
|
|
#include "Common/Data/Format/IniFile.h"
|
|
#include "Common/Input/InputState.h"
|
|
#include "Common/VR/PPSSPPVR.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/TimeUtil.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Core/HLE/sceUtility.h"
|
|
#include "Core/HLE/sceCtrl.h" // psp keys
|
|
#include "Core/Config.h"
|
|
#include "Core/KeyMap.h"
|
|
#include "Core/KeyMapDefaults.h"
|
|
|
|
namespace KeyMap {
|
|
|
|
// We actually need to lock g_controllerMap since it can be modified! Crashes will probably be rare though,
|
|
// but I've seen one. Let's just protect it with a mutex.
|
|
std::recursive_mutex g_controllerMapLock;
|
|
KeyMapping g_controllerMap;
|
|
|
|
// Incremented on modification, so we know when to update menus.
|
|
int g_controllerMapGeneration = 0;
|
|
std::set<std::string> g_seenPads;
|
|
std::map<InputDeviceID, std::string> g_padNames;
|
|
std::set<InputDeviceID> g_seenDeviceIds;
|
|
|
|
AxisType GetAxisType(InputAxis input) {
|
|
switch (input) {
|
|
case JOYSTICK_AXIS_GAS:
|
|
case JOYSTICK_AXIS_BRAKE:
|
|
case JOYSTICK_AXIS_LTRIGGER:
|
|
case JOYSTICK_AXIS_RTRIGGER:
|
|
return AxisType::TRIGGER;
|
|
case JOYSTICK_AXIS_X:
|
|
case JOYSTICK_AXIS_Y:
|
|
case JOYSTICK_AXIS_Z:
|
|
case JOYSTICK_AXIS_RX:
|
|
case JOYSTICK_AXIS_RY:
|
|
case JOYSTICK_AXIS_RZ:
|
|
return AxisType::STICK;
|
|
default:
|
|
return AxisType::OTHER;
|
|
}
|
|
}
|
|
|
|
// Utility for UI navigation
|
|
void SingleInputMappingFromPspButton(int btn, std::vector<InputMapping> *mappings, bool ignoreMouse) {
|
|
std::vector<MultiInputMapping> multiMappings;
|
|
InputMappingsFromPspButton(btn, &multiMappings, ignoreMouse);
|
|
mappings->clear();
|
|
for (auto &mapping : multiMappings) {
|
|
// Ignore combos
|
|
if (mapping.mappings.size() > 1) {
|
|
continue;
|
|
}
|
|
if (!mapping.empty()) {
|
|
mappings->push_back(mapping.mappings[0]);
|
|
} else {
|
|
WARN_LOG(Log::Common, "Encountered empty mapping in multi-mapping for button %d", btn);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void InsertIntoVector(std::vector<InputMapping> *vec, const InputMapping &mapping) {
|
|
if (std::find(vec->begin(), vec->end(), mapping) == vec->end())
|
|
vec->push_back(mapping);
|
|
}
|
|
|
|
// Used to avoid multiple main buttons both being mapped to cancel or confirm.
|
|
bool HasMainButtonMapping(const std::vector<InputMapping> &mappings) {
|
|
for (auto mapping : mappings) {
|
|
switch (mapping.keyCode) {
|
|
case NKCODE_BUTTON_A:
|
|
case NKCODE_BUTTON_B:
|
|
case NKCODE_BUTTON_C:
|
|
case NKCODE_BUTTON_X:
|
|
case NKCODE_BUTTON_Y:
|
|
case NKCODE_BUTTON_Z:
|
|
case NKCODE_BUTTON_1:
|
|
case NKCODE_BUTTON_2:
|
|
case NKCODE_BUTTON_3:
|
|
case NKCODE_BUTTON_4:
|
|
case NKCODE_BUTTON_5:
|
|
case NKCODE_BUTTON_6:
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void RemoveKeyboardLetterKeys(std::vector<InputMapping> &mappings) {
|
|
std::vector<InputMapping> result;
|
|
for (auto &mapping : mappings) {
|
|
if (mapping.deviceId == DEVICE_ID_KEYBOARD) {
|
|
if (mapping.keyCode >= NKCODE_A && mapping.keyCode <= NKCODE_Z) {
|
|
continue;
|
|
}
|
|
}
|
|
result.push_back(mapping);
|
|
}
|
|
mappings = result;
|
|
}
|
|
|
|
// TODO: This is such a mess...
|
|
void UpdateNativeMenuKeys() {
|
|
std::vector<InputMapping> confirmKeys, cancelKeys;
|
|
std::vector<InputMapping> tabLeft, tabRight;
|
|
std::vector<InputMapping> upKeys, downKeys, leftKeys, rightKeys;
|
|
std::vector<InputMapping> infoKeys;
|
|
|
|
// Mouse mapping might be problematic in UI, so let's ignore mouse for UI
|
|
const bool confirmWithCross = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS;
|
|
|
|
const int confirmKey = confirmWithCross ? CTRL_CROSS : CTRL_CIRCLE;
|
|
const int cancelKey = confirmWithCross ? CTRL_CIRCLE : CTRL_CROSS;
|
|
|
|
SingleInputMappingFromPspButton(confirmKey, &confirmKeys, true);
|
|
SingleInputMappingFromPspButton(cancelKey, &cancelKeys, true);
|
|
SingleInputMappingFromPspButton(CTRL_TRIANGLE, &infoKeys, true);
|
|
SingleInputMappingFromPspButton(CTRL_LTRIGGER, &tabLeft, true);
|
|
SingleInputMappingFromPspButton(CTRL_RTRIGGER, &tabRight, true);
|
|
SingleInputMappingFromPspButton(CTRL_UP, &upKeys, true);
|
|
SingleInputMappingFromPspButton(CTRL_DOWN, &downKeys, true);
|
|
SingleInputMappingFromPspButton(CTRL_LEFT, &leftKeys, true);
|
|
SingleInputMappingFromPspButton(CTRL_RIGHT, &rightKeys, true);
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
// Hardcode DPAD on Android
|
|
upKeys.push_back(InputMapping(DEVICE_ID_ANY, NKCODE_DPAD_UP));
|
|
downKeys.push_back(InputMapping(DEVICE_ID_ANY, NKCODE_DPAD_DOWN));
|
|
leftKeys.push_back(InputMapping(DEVICE_ID_ANY, NKCODE_DPAD_LEFT));
|
|
rightKeys.push_back(InputMapping(DEVICE_ID_ANY, NKCODE_DPAD_RIGHT));
|
|
#endif
|
|
|
|
// Push several hard-coded keys before submitting to native.
|
|
const InputMapping hardcodedConfirmKeys[] = {
|
|
InputMapping(DEVICE_ID_KEYBOARD, NKCODE_SPACE),
|
|
InputMapping(DEVICE_ID_KEYBOARD, NKCODE_ENTER),
|
|
InputMapping(DEVICE_ID_KEYBOARD, NKCODE_NUMPAD_ENTER),
|
|
InputMapping(DEVICE_ID_PAD_0, NKCODE_DPAD_CENTER), // A number of old obscure Android devices need this.
|
|
};
|
|
|
|
// If they're not already bound, add them in.
|
|
for (size_t i = 0; i < ARRAY_SIZE(hardcodedConfirmKeys); i++) {
|
|
InsertIntoVector(&confirmKeys, hardcodedConfirmKeys[i]);
|
|
}
|
|
if (!HasMainButtonMapping(confirmKeys)) {
|
|
confirmKeys.push_back(InputMapping(DEVICE_ID_ANY, confirmWithCross ? NKCODE_BUTTON_A : NKCODE_BUTTON_B));
|
|
}
|
|
|
|
const InputMapping hardcodedCancelKeys[] = {
|
|
InputMapping(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE),
|
|
InputMapping(DEVICE_ID_ANY, NKCODE_BACK),
|
|
InputMapping(DEVICE_ID_MOUSE, NKCODE_EXT_MOUSEBUTTON_4),
|
|
};
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(hardcodedCancelKeys); i++) {
|
|
InsertIntoVector(&cancelKeys, hardcodedCancelKeys[i]);
|
|
}
|
|
if (!HasMainButtonMapping(cancelKeys)) {
|
|
// This used to push the confirm button into confirmKeys - wrong list, wrong button.
|
|
cancelKeys.push_back(InputMapping(DEVICE_ID_ANY, confirmWithCross ? NKCODE_BUTTON_B : NKCODE_BUTTON_A));
|
|
}
|
|
|
|
const InputMapping hardcodedInfoKeys[] = {
|
|
InputMapping(DEVICE_ID_KEYBOARD, NKCODE_S),
|
|
InputMapping(DEVICE_ID_KEYBOARD, NKCODE_NUMPAD_ADD),
|
|
InputMapping(DEVICE_ID_PAD_0, NKCODE_BUTTON_Y), // Also triangle
|
|
};
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(hardcodedInfoKeys); i++) {
|
|
InsertIntoVector(&infoKeys, hardcodedInfoKeys[i]);
|
|
}
|
|
|
|
/*
|
|
for (size_t i = 0; i < confirmKeys.size(); i++) {
|
|
INFO_LOG(Log::System, "Confirm key: %s", MultiInputMapping(confirmKeys[i]).ToVisualString().c_str());
|
|
}
|
|
for (size_t i = 0; i < cancelKeys.size(); i++) {
|
|
INFO_LOG(Log::System, "Cancel key: %s", MultiInputMapping(cancelKeys[i]).ToVisualString().c_str());
|
|
}
|
|
*/
|
|
|
|
// Remove keyboard letter keys from some arrays, as they will collide with text input for instant search.
|
|
RemoveKeyboardLetterKeys(tabLeft);
|
|
RemoveKeyboardLetterKeys(tabRight);
|
|
RemoveKeyboardLetterKeys(confirmKeys);
|
|
RemoveKeyboardLetterKeys(cancelKeys);
|
|
RemoveKeyboardLetterKeys(infoKeys);
|
|
|
|
SetDPadKeys(upKeys, downKeys, leftKeys, rightKeys);
|
|
SetConfirmCancelKeys(confirmKeys, cancelKeys);
|
|
SetTabLeftRightKeys(tabLeft, tabRight);
|
|
SetInfoKeys(infoKeys);
|
|
|
|
std::unordered_map<InputDeviceID, int> flipYByDeviceId;
|
|
for (InputDeviceID deviceId : g_seenDeviceIds) {
|
|
auto analogs = MappedAxesForDevice(deviceId);
|
|
flipYByDeviceId[deviceId] = analogs.leftY.direction;
|
|
}
|
|
SetAnalogFlipY(flipYByDeviceId);
|
|
}
|
|
|
|
static const KeyMap_IntStrPair key_names[] = {
|
|
{NKCODE_A, "A"},
|
|
{NKCODE_B, "B"},
|
|
{NKCODE_C, "C"},
|
|
{NKCODE_D, "D"},
|
|
{NKCODE_E, "E"},
|
|
{NKCODE_F, "F"},
|
|
{NKCODE_G, "G"},
|
|
{NKCODE_H, "H"},
|
|
{NKCODE_I, "I"},
|
|
{NKCODE_J, "J"},
|
|
{NKCODE_K, "K"},
|
|
{NKCODE_L, "L"},
|
|
{NKCODE_M, "M"},
|
|
{NKCODE_N, "N"},
|
|
{NKCODE_O, "O"},
|
|
{NKCODE_P, "P"},
|
|
{NKCODE_Q, "Q"},
|
|
{NKCODE_R, "R"},
|
|
{NKCODE_S, "S"},
|
|
{NKCODE_T, "T"},
|
|
{NKCODE_U, "U"},
|
|
{NKCODE_V, "V"},
|
|
{NKCODE_W, "W"},
|
|
{NKCODE_X, "X"},
|
|
{NKCODE_Y, "Y"},
|
|
{NKCODE_Z, "Z"},
|
|
|
|
{NKCODE_0, "0"},
|
|
{NKCODE_1, "1"},
|
|
{NKCODE_2, "2"},
|
|
{NKCODE_3, "3"},
|
|
{NKCODE_4, "4"},
|
|
{NKCODE_5, "5"},
|
|
{NKCODE_6, "6"},
|
|
{NKCODE_7, "7"},
|
|
{NKCODE_8, "8"},
|
|
{NKCODE_9, "9"},
|
|
|
|
{NKCODE_F1, "F1"},
|
|
{NKCODE_F2, "F2"},
|
|
{NKCODE_F3, "F3"},
|
|
{NKCODE_F4, "F4"},
|
|
{NKCODE_F5, "F5"},
|
|
{NKCODE_F6, "F6"},
|
|
{NKCODE_F7, "F7"},
|
|
{NKCODE_F8, "F8"},
|
|
{NKCODE_F9, "F9"},
|
|
{NKCODE_F10, "F10"},
|
|
{NKCODE_F11, "F11"},
|
|
{NKCODE_F12, "F12"},
|
|
|
|
{NKCODE_GRAVE, "`"},
|
|
{NKCODE_SLASH, "/"},
|
|
{NKCODE_BACKSLASH, "\\"},
|
|
{NKCODE_SEMICOLON, ";"},
|
|
{NKCODE_COMMA, ","},
|
|
{NKCODE_PERIOD, "."},
|
|
{NKCODE_LEFT_BRACKET, "["},
|
|
{NKCODE_RIGHT_BRACKET, "]"},
|
|
{NKCODE_APOSTROPHE, "'"},
|
|
{NKCODE_MINUS, "-"},
|
|
{NKCODE_PLUS, "+"},
|
|
{NKCODE_PRINTSCREEN, "Print"},
|
|
{NKCODE_SCROLL_LOCK, "ScrLock"},
|
|
{NKCODE_BREAK, "Pause"},
|
|
|
|
{NKCODE_BACK, "Back"},
|
|
{NKCODE_TAB, "Tab"},
|
|
{NKCODE_ENTER, "Enter"},
|
|
{NKCODE_SHIFT_LEFT, "LShift"},
|
|
{NKCODE_SHIFT_RIGHT, "RShift"},
|
|
{NKCODE_CTRL_LEFT, "LCtrl"},
|
|
{NKCODE_CTRL_RIGHT, "RCtrl"},
|
|
{NKCODE_ALT_LEFT, "LAlt"},
|
|
{NKCODE_ALT_RIGHT, "RAlt"},
|
|
{NKCODE_META_LEFT, "LMeta"},
|
|
{NKCODE_META_RIGHT, "RMeta"},
|
|
{NKCODE_SPACE, "Space"},
|
|
{NKCODE_WINDOW, "Windows"},
|
|
{NKCODE_DEL, "Backspace"},
|
|
{NKCODE_FORWARD_DEL, "Delete"},
|
|
{NKCODE_MOVE_HOME, "Home"},
|
|
{NKCODE_MOVE_END, "End"},
|
|
{NKCODE_ESCAPE, "Esc"},
|
|
{NKCODE_CAPS_LOCK, "CapsLock"},
|
|
|
|
{NKCODE_VOLUME_UP, "Vol +"},
|
|
{NKCODE_VOLUME_DOWN, "Vol -"},
|
|
{NKCODE_HOME, "Home"},
|
|
{NKCODE_INSERT, "Ins"},
|
|
{NKCODE_PAGE_UP, "PgUp"},
|
|
{NKCODE_PAGE_DOWN, "PgDn"},
|
|
{NKCODE_CLEAR, "Clear"}, // 5 when numlock off
|
|
{NKCODE_CALL, "Call"},
|
|
{NKCODE_ENDCALL, "End Call"},
|
|
|
|
{NKCODE_DPAD_LEFT, "Left"},
|
|
{NKCODE_DPAD_UP, "Up"},
|
|
{NKCODE_DPAD_RIGHT, "Right"},
|
|
{NKCODE_DPAD_DOWN, "Down"},
|
|
|
|
{NKCODE_BUTTON_L1, "L1"},
|
|
{NKCODE_BUTTON_L2, "L2"},
|
|
{NKCODE_BUTTON_R1, "R1"},
|
|
{NKCODE_BUTTON_R2, "R2"},
|
|
|
|
{NKCODE_BUTTON_A, "[A]"},
|
|
{NKCODE_BUTTON_B, "[B]"},
|
|
{NKCODE_BUTTON_C, "[C]"},
|
|
{NKCODE_BUTTON_X, "[X]"},
|
|
{NKCODE_BUTTON_Y, "[Y]"},
|
|
{NKCODE_BUTTON_Z, "[Z]"},
|
|
{NKCODE_BUTTON_1, "b1"},
|
|
{NKCODE_BUTTON_2, "b2"},
|
|
{NKCODE_BUTTON_3, "b3"},
|
|
{NKCODE_BUTTON_4, "b4"},
|
|
{NKCODE_BUTTON_5, "b5"},
|
|
{NKCODE_BUTTON_6, "b6"},
|
|
{NKCODE_BUTTON_7, "b7"},
|
|
{NKCODE_BUTTON_8, "b8"},
|
|
{NKCODE_BUTTON_9, "b9"},
|
|
{NKCODE_BUTTON_10, "b10"},
|
|
{NKCODE_BUTTON_11, "b11"},
|
|
{NKCODE_BUTTON_12, "b12"},
|
|
{NKCODE_BUTTON_13, "b13"},
|
|
{NKCODE_BUTTON_14, "b14"},
|
|
{NKCODE_BUTTON_15, "b15"},
|
|
{NKCODE_BUTTON_16, "b16"},
|
|
{NKCODE_BUTTON_START, "Start"},
|
|
{NKCODE_BUTTON_SELECT, "Select"},
|
|
{NKCODE_BUTTON_CIRCLE, "Circle"},
|
|
{NKCODE_BUTTON_CIRCLE_PS3, "Circle3"},
|
|
{NKCODE_BUTTON_CROSS, "Cross"},
|
|
{NKCODE_BUTTON_CROSS_PS3, "Cross3"},
|
|
{NKCODE_BUTTON_TRIANGLE, "Triangle"},
|
|
{NKCODE_BUTTON_SQUARE, "Square"},
|
|
{NKCODE_BUTTON_THUMBL, "ThumbL"},
|
|
{NKCODE_BUTTON_THUMBR, "ThumbR"},
|
|
{NKCODE_BUTTON_MODE, "Mode"},
|
|
|
|
{NKCODE_EXT_PIPE, "|"},
|
|
{NKCODE_NUMPAD_DIVIDE, "Num/"},
|
|
{NKCODE_NUMPAD_MULTIPLY, "Num*"},
|
|
{NKCODE_NUMPAD_ADD, "Num+"},
|
|
{NKCODE_NUMPAD_SUBTRACT, "Num-"},
|
|
{NKCODE_NUMPAD_DOT, "Num."},
|
|
{NKCODE_NUMPAD_COMMA, "Num,"},
|
|
{NKCODE_NUMPAD_ENTER, "NumEnter"},
|
|
{NKCODE_NUMPAD_EQUALS, "Num="},
|
|
{NKCODE_NUMPAD_LEFT_PAREN, "Num("},
|
|
{NKCODE_NUMPAD_RIGHT_PAREN, "Num)"},
|
|
{NKCODE_NUMPAD_0, "Num0"},
|
|
{NKCODE_NUMPAD_1, "Num1"},
|
|
{NKCODE_NUMPAD_2, "Num2"},
|
|
{NKCODE_NUMPAD_3, "Num3"},
|
|
{NKCODE_NUMPAD_4, "Num4"},
|
|
{NKCODE_NUMPAD_5, "Num5"},
|
|
{NKCODE_NUMPAD_6, "Num6"},
|
|
{NKCODE_NUMPAD_7, "Num7"},
|
|
{NKCODE_NUMPAD_8, "Num8"},
|
|
{NKCODE_NUMPAD_9, "Num9"},
|
|
|
|
{NKCODE_LANGUAGE_SWITCH, "Language"},
|
|
{NKCODE_MANNER_MODE, "Manner"},
|
|
{NKCODE_3D_MODE, "3D Mode"},
|
|
{NKCODE_CONTACTS, "Contacts"},
|
|
{NKCODE_CALENDAR, "Calendar"},
|
|
{NKCODE_MUSIC, "Music"},
|
|
{NKCODE_CALCULATOR, "Calc"},
|
|
{NKCODE_ZENKAKU_HANKAKU, "Zenkaku"},
|
|
{NKCODE_EISU, "Eisu"},
|
|
{NKCODE_MUHENKAN, "Muhenkan"},
|
|
{NKCODE_HENKAN, "Henkan"},
|
|
{NKCODE_KATAKANA_HIRAGANA, "Katakana"},
|
|
{NKCODE_YEN, "Yen"},
|
|
{NKCODE_RO, "Ro"},
|
|
{NKCODE_KANA, "Kana"},
|
|
{NKCODE_ASSIST, "Assist"},
|
|
|
|
{NKCODE_EXT_MOUSEBUTTON_1, "MB1"},
|
|
{NKCODE_EXT_MOUSEBUTTON_2, "MB2"},
|
|
{NKCODE_EXT_MOUSEBUTTON_3, "MB3"},
|
|
{NKCODE_EXT_MOUSEBUTTON_4, "MB4"},
|
|
{NKCODE_EXT_MOUSEBUTTON_5, "MB5"},
|
|
{NKCODE_EXT_MOUSEWHEEL_UP, "MWheelU"},
|
|
{NKCODE_EXT_MOUSEWHEEL_DOWN, "MWheelD"},
|
|
|
|
{NKCODE_START_QUESTION, "¿"},
|
|
{NKCODE_LEFTBRACE, "{"},
|
|
{NKCODE_RIGHTBRACE, "}"},
|
|
|
|
{NKCODE_GUIDE, "Guide"},
|
|
{NKCODE_INFO, "Info"},
|
|
};
|
|
|
|
static const KeyMap_IntStrPair axis_names[] = {
|
|
{JOYSTICK_AXIS_X, "X Axis"},
|
|
{JOYSTICK_AXIS_Y, "Y Axis"},
|
|
{JOYSTICK_AXIS_PRESSURE, "Pressure"},
|
|
{JOYSTICK_AXIS_SIZE, "Size"},
|
|
{JOYSTICK_AXIS_TOUCH_MAJOR, "Touch Major"},
|
|
{JOYSTICK_AXIS_TOUCH_MINOR, "Touch Minor"},
|
|
{JOYSTICK_AXIS_TOOL_MAJOR, "Tool Major"},
|
|
{JOYSTICK_AXIS_TOOL_MINOR, "Tool Minor"},
|
|
{JOYSTICK_AXIS_ORIENTATION, "Orient"},
|
|
{JOYSTICK_AXIS_VSCROLL, "Vert Scroll"},
|
|
{JOYSTICK_AXIS_HSCROLL, "Horiz Scroll"},
|
|
{JOYSTICK_AXIS_Z, "Z Axis"}, // Also used as second stick X on many controllers - rename?
|
|
{JOYSTICK_AXIS_RX, "X Rotation"},
|
|
{JOYSTICK_AXIS_RY, "Y Rotation"},
|
|
{JOYSTICK_AXIS_RZ, "Z Rotation"}, // Also used as second stick Y on many controllers - rename?
|
|
{JOYSTICK_AXIS_HAT_X, "X HAT"},
|
|
{JOYSTICK_AXIS_HAT_Y, "Y HAT"},
|
|
{JOYSTICK_AXIS_LTRIGGER, "TriggerL"},
|
|
{JOYSTICK_AXIS_RTRIGGER, "TriggerR"},
|
|
{JOYSTICK_AXIS_THROTTLE, "Throttle"},
|
|
{JOYSTICK_AXIS_RUDDER, "Rudder"},
|
|
{JOYSTICK_AXIS_WHEEL, "Wheel"},
|
|
{JOYSTICK_AXIS_GAS, "Gas"},
|
|
{JOYSTICK_AXIS_BRAKE, "Brake"},
|
|
{JOYSTICK_AXIS_DISTANCE, "Distance"},
|
|
{JOYSTICK_AXIS_TILT, "Tilt"},
|
|
{JOYSTICK_AXIS_MOUSE_REL_X, "MouseDX"},
|
|
{JOYSTICK_AXIS_MOUSE_REL_Y, "MouseDY"},
|
|
{JOYSTICK_AXIS_ACCELEROMETER_X, "AccelX"},
|
|
{JOYSTICK_AXIS_ACCELEROMETER_Y, "AccelY"},
|
|
{JOYSTICK_AXIS_ACCELEROMETER_Z, "AccelZ"},
|
|
};
|
|
|
|
const KeyMap_IntStrPair psp_button_names[] = {
|
|
{CTRL_UP, "Up"},
|
|
{CTRL_DOWN, "Down"},
|
|
{CTRL_LEFT, "Left"},
|
|
{CTRL_RIGHT, "Right"},
|
|
{CTRL_CIRCLE, "Circle"},
|
|
{CTRL_CROSS, "Cross"},
|
|
{CTRL_SQUARE, "Square"},
|
|
{CTRL_TRIANGLE, "Triangle"},
|
|
{CTRL_START, "Start"},
|
|
{CTRL_SELECT, "Select"},
|
|
{CTRL_LTRIGGER, "L"},
|
|
{CTRL_RTRIGGER, "R"},
|
|
|
|
{VIRTKEY_AXIS_Y_MAX, "An.Up"},
|
|
{VIRTKEY_AXIS_Y_MIN, "An.Down"},
|
|
{VIRTKEY_AXIS_X_MIN, "An.Left"},
|
|
{VIRTKEY_AXIS_X_MAX, "An.Right"},
|
|
|
|
{VIRTKEY_ANALOG_ROTATE_CW, "Rotate Analog (CW)"},
|
|
{VIRTKEY_ANALOG_ROTATE_CCW, "Rotate Analog (CCW)"},
|
|
{VIRTKEY_ANALOG_LIGHTLY, "Analog limiter"},
|
|
{VIRTKEY_RAPID_FIRE, "RapidFire"},
|
|
{VIRTKEY_AXIS_SWAP_HOLD, "Axis swap (hold)"},
|
|
{VIRTKEY_AXIS_SWAP_TOGGLE, "Axis swap (toggle)"},
|
|
|
|
{VIRTKEY_FASTFORWARD, "Fast-forward"},
|
|
{VIRTKEY_PAUSE, "Pause"},
|
|
{VIRTKEY_PAUSE_NO_MENU, "Pause (no menu)"},
|
|
|
|
{VIRTKEY_SPEED_TOGGLE, "SpeedToggle"},
|
|
{VIRTKEY_SPEED_CUSTOM1, "Alt speed 1"},
|
|
{VIRTKEY_SPEED_CUSTOM2, "Alt speed 2"},
|
|
{VIRTKEY_SPEED_ANALOG, "Analog speed"},
|
|
{VIRTKEY_RESET_EMULATION, "Reset"},
|
|
{VIRTKEY_FRAME_ADVANCE, "Frame Advance"},
|
|
#if !defined(MOBILE_DEVICE)
|
|
{VIRTKEY_RECORD, "Audio/Video Recording" },
|
|
#endif
|
|
{VIRTKEY_REWIND, "Rewind"},
|
|
{VIRTKEY_SAVE_STATE, "Save State"},
|
|
{VIRTKEY_LOAD_STATE, "Load State"},
|
|
{VIRTKEY_PREVIOUS_SLOT, "Previous Slot"},
|
|
{VIRTKEY_NEXT_SLOT, "Next Slot"},
|
|
#if !defined(MOBILE_DEVICE)
|
|
{VIRTKEY_TOGGLE_FULLSCREEN, "Toggle Fullscreen"},
|
|
#endif
|
|
{VIRTKEY_TOGGLE_DEBUGGER, "Toggle Debugger"},
|
|
{VIRTKEY_TOGGLE_TILT, "Toggle tilt control"},
|
|
|
|
{VIRTKEY_OPENCHAT, "OpenChat" },
|
|
|
|
{VIRTKEY_DEVMENU, "DevMenu"},
|
|
{VIRTKEY_TEXTURE_DUMP, "Texture Dumping"},
|
|
{VIRTKEY_TEXTURE_REPLACE, "Texture Replacement"},
|
|
{VIRTKEY_SCREENSHOT, "Screenshot"},
|
|
{VIRTKEY_MUTE_TOGGLE, "Mute toggle"},
|
|
|
|
#ifdef OPENXR
|
|
{VIRTKEY_VR_CAMERA_ADJUST, "VR camera adjust"},
|
|
{VIRTKEY_VR_CAMERA_RESET, "VR camera reset"},
|
|
#else
|
|
{VIRTKEY_SCREEN_ROTATION_VERTICAL, "Display Portrait"},
|
|
{VIRTKEY_SCREEN_ROTATION_VERTICAL180, "Display Portrait Reversed"},
|
|
{VIRTKEY_SCREEN_ROTATION_HORIZONTAL, "Display Landscape"},
|
|
{VIRTKEY_SCREEN_ROTATION_HORIZONTAL180, "Display Landscape Reversed"},
|
|
#endif
|
|
|
|
{VIRTKEY_TOGGLE_WLAN, "Toggle WLAN"},
|
|
{VIRTKEY_EXIT_APP, "Exit App"},
|
|
|
|
{VIRTKEY_TOGGLE_MOUSE, "Toggle mouse input"},
|
|
{VIRTKEY_TOGGLE_TOUCH_CONTROLS, "Toggle touch controls"},
|
|
|
|
{VIRTKEY_AXIS_RIGHT_Y_MAX, "RightAn.Up"},
|
|
{VIRTKEY_AXIS_RIGHT_Y_MIN, "RightAn.Down"},
|
|
{VIRTKEY_AXIS_RIGHT_X_MIN, "RightAn.Left"},
|
|
{VIRTKEY_AXIS_RIGHT_X_MAX, "RightAn.Right"},
|
|
|
|
{CTRL_HOME, "Home"},
|
|
{CTRL_HOLD, "Hold"},
|
|
{CTRL_WLAN, "Wlan"},
|
|
{CTRL_REMOTE_HOLD, "Remote hold"},
|
|
{CTRL_VOL_UP, "Vol +"},
|
|
{CTRL_VOL_DOWN, "Vol -"},
|
|
{CTRL_SCREEN, "Screen"},
|
|
{CTRL_NOTE, "Note"},
|
|
{CTRL_L2, "Dev-kit L2"},
|
|
{CTRL_L3, "Dev-kit L3"},
|
|
{CTRL_R2, "Dev-kit R2"},
|
|
{CTRL_R3, "Dev-kit R3"},
|
|
};
|
|
|
|
// key here can be other things than InputKeyCode.
|
|
static std::string FindName(int key, const KeyMap_IntStrPair list[], size_t size) {
|
|
for (size_t i = 0; i < size; i++) {
|
|
if (list[i].key == key)
|
|
return list[i].name;
|
|
}
|
|
return StringFromFormat("%02x?", key);
|
|
}
|
|
|
|
std::string GetKeyName(InputKeyCode keyCode) {
|
|
return FindName(keyCode, key_names, ARRAY_SIZE(key_names));
|
|
}
|
|
|
|
std::string GetKeyOrAxisName(const InputMapping &mapping) {
|
|
if (mapping.IsAxis()) {
|
|
int direction;
|
|
int axis = mapping.Axis(&direction);
|
|
std::string temp = GetAxisName(axis);
|
|
if (direction == 1)
|
|
temp += "+";
|
|
else if (direction == -1)
|
|
temp += "-";
|
|
return temp;
|
|
} else {
|
|
return FindName(mapping.keyCode, key_names, ARRAY_SIZE(key_names));
|
|
}
|
|
}
|
|
|
|
std::string GetAxisName(int axisId) {
|
|
return FindName(axisId, axis_names, ARRAY_SIZE(axis_names));
|
|
}
|
|
|
|
std::string GetPspButtonName(int btn) {
|
|
return FindName(btn, psp_button_names, ARRAY_SIZE(psp_button_names));
|
|
}
|
|
|
|
const char* GetPspButtonNameCharPointer(int btn) {
|
|
for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++)
|
|
if (psp_button_names[i].key == btn)
|
|
return psp_button_names[i].name;
|
|
return nullptr;
|
|
}
|
|
|
|
const KeyMap::KeyMap_IntStrPair *GetMappableKeys(size_t *count) {
|
|
*count = ARRAY_SIZE(psp_button_names);
|
|
return psp_button_names;
|
|
}
|
|
|
|
bool InputMappingToPspButton(const InputMapping &mapping, std::vector<int> *pspButtons) {
|
|
bool found = false;
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) {
|
|
for (auto iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
|
|
if (iter2->EqualsSingleMapping(mapping)) {
|
|
if (pspButtons)
|
|
pspButtons->push_back(iter->first);
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
// This is the main workhorse of the ControlMapper.
|
|
bool InputMappingsFromPspButtonNoLock(int btn, std::vector<MultiInputMapping> *mappings, bool ignoreMouse) {
|
|
auto iter = g_controllerMap.find(btn);
|
|
if (iter == g_controllerMap.end()) {
|
|
return false;
|
|
}
|
|
bool mapped = false;
|
|
if (mappings) {
|
|
mappings->clear();
|
|
}
|
|
for (auto &iter2 : iter->second) {
|
|
bool ignore = ignoreMouse && iter2.HasMouse();
|
|
if (!ignore) {
|
|
mapped = true;
|
|
if (mappings) {
|
|
mappings->push_back(iter2);
|
|
}
|
|
}
|
|
}
|
|
return mapped;
|
|
}
|
|
|
|
bool InputMappingsFromPspButton(int btn, std::vector<MultiInputMapping> *mappings, bool ignoreMouse) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
return InputMappingsFromPspButtonNoLock(btn, mappings, ignoreMouse);
|
|
}
|
|
|
|
void LockMappings() {
|
|
g_controllerMapLock.lock();
|
|
}
|
|
|
|
void UnlockMappings() {
|
|
g_controllerMapLock.unlock();
|
|
}
|
|
|
|
bool PspButtonHasMappings(int btn) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
auto iter = g_controllerMap.find(btn);
|
|
if (iter == g_controllerMap.end()) {
|
|
return false;
|
|
}
|
|
return !iter->second.empty();
|
|
}
|
|
|
|
MappedAnalogAxes MappedAxesForDevice(InputDeviceID deviceId) {
|
|
// Find the axisId mapped for a specific virtual button.
|
|
auto findAxisId = [&](int btn) -> MappedAnalogAxis {
|
|
MappedAnalogAxis info{ -1 };
|
|
for (const auto &key : g_controllerMap[btn]) {
|
|
// Only consider single mappings, combos don't make much sense for these.
|
|
if (key.mappings.empty()) continue;
|
|
auto &mapping = key.mappings[0];
|
|
if (mapping.deviceId == deviceId) {
|
|
info.axisId = TranslateKeyCodeToAxis(mapping.keyCode, &info.direction);
|
|
return info;
|
|
}
|
|
}
|
|
return info;
|
|
};
|
|
|
|
// Find the axisId of a pair of opposing buttons.
|
|
auto findAxisIdPair = [&](int minBtn, int maxBtn) -> MappedAnalogAxis {
|
|
MappedAnalogAxis foundMin = findAxisId(minBtn);
|
|
MappedAnalogAxis foundMax = findAxisId(maxBtn);
|
|
if (foundMin.axisId == foundMax.axisId) {
|
|
return foundMax;
|
|
}
|
|
return MappedAnalogAxis{ -1 };
|
|
};
|
|
|
|
MappedAnalogAxes result;
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
result.leftX = findAxisIdPair(VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX);
|
|
result.leftY = findAxisIdPair(VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX);
|
|
result.rightX = findAxisIdPair(VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX);
|
|
result.rightY = findAxisIdPair(VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX);
|
|
return result;
|
|
}
|
|
|
|
void RemoveButtonMapping(int btn) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) {
|
|
if (iter->first == btn) {
|
|
g_controllerMap.erase(iter);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsKeyMapped(InputDeviceID device, int key) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
for (auto &iter : g_controllerMap) {
|
|
for (auto &mappedKey : iter.second) {
|
|
if (mappedKey.mappings.contains(InputMapping(device, key))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ReplaceSingleKeyMapping(int btn, int index, const MultiInputMapping &key) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
// Check for duplicate
|
|
for (int i = 0; i < (int)g_controllerMap[btn].size(); ++i) {
|
|
if (i != index && g_controllerMap[btn][i] == key) {
|
|
g_controllerMap[btn].erase(g_controllerMap[btn].begin()+index);
|
|
g_controllerMapGeneration++;
|
|
|
|
UpdateNativeMenuKeys();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (key.empty()) {
|
|
return false;
|
|
}
|
|
|
|
KeyMap::g_controllerMap[btn][index] = key;
|
|
g_controllerMapGeneration++;
|
|
|
|
for (auto &mapping : key.mappings) {
|
|
g_seenDeviceIds.insert(mapping.deviceId);
|
|
}
|
|
UpdateNativeMenuKeys();
|
|
return true;
|
|
}
|
|
|
|
void DeleteNthMapping(int key, int number) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
auto iter = g_controllerMap.find(key);
|
|
if (iter != g_controllerMap.end()) {
|
|
if (number < iter->second.size()) {
|
|
iter->second.erase(iter->second.begin() + number);
|
|
g_controllerMapGeneration++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetInputMapping(int btn, const MultiInputMapping &key, bool replace) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
if (key.empty()) {
|
|
g_controllerMap.erase(btn);
|
|
return;
|
|
}
|
|
if (replace) {
|
|
RemoveButtonMapping(btn);
|
|
g_controllerMap[btn].clear();
|
|
g_controllerMap[btn].push_back(key);
|
|
} else {
|
|
for (auto iter = g_controllerMap[btn].begin(); iter != g_controllerMap[btn].end(); ++iter) {
|
|
if (*iter == key)
|
|
return;
|
|
}
|
|
g_controllerMap[btn].push_back(key);
|
|
}
|
|
g_controllerMapGeneration++;
|
|
|
|
for (auto &mapping : key.mappings) {
|
|
g_seenDeviceIds.insert(mapping.deviceId);
|
|
}
|
|
}
|
|
|
|
void RestoreDefault() {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
g_controllerMap.clear();
|
|
g_controllerMapGeneration++;
|
|
|
|
if (IsVREnabled()) {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_VR_HEADSET, false);
|
|
return;
|
|
}
|
|
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_KEYBOARD, true);
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_XINPUT, false);
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_PAD, false);
|
|
#elif PPSSPP_PLATFORM(ANDROID)
|
|
// Autodetect a few common (and less common) devices
|
|
// Note that here we check the device name, not the controller name. We don't get
|
|
// the controller name until a button has been pressed so can't use it to set defaults.
|
|
std::string name = System_GetProperty(SYSPROP_NAME);
|
|
if (IsNvidiaShield(name)) {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_SHIELD, false);
|
|
} else if (IsXperiaPlay(name)) {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_XPERIA_PLAY, false);
|
|
} else if (IsMOQII7S(name)) {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_MOQI_I7S, false);
|
|
} else if (IsRetroid(name)) {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_RETROID_CONTROLLER, false);
|
|
} else {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_ANDROID_PAD, false);
|
|
}
|
|
#elif PPSSPP_PLATFORM(IOS)
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_IOS_PAD, false);
|
|
#else
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_KEYBOARD, true);
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_PAD, false);
|
|
#endif
|
|
}
|
|
|
|
// TODO: Make the ini format nicer.
|
|
void LoadFromIni(IniFile &file) {
|
|
RestoreDefault();
|
|
if (!file.HasSection("ControlMapping")) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
|
|
Section *controls = file.GetOrCreateSection("ControlMapping");
|
|
for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++) {
|
|
std::string value;
|
|
controls->Get(psp_button_names[i].name, &value);
|
|
|
|
// Erase default mapping
|
|
g_controllerMap.erase(psp_button_names[i].key);
|
|
if (value.empty())
|
|
continue;
|
|
|
|
std::vector<std::string> mappings;
|
|
SplitString(value, ',', mappings);
|
|
|
|
for (size_t j = 0; j < mappings.size(); j++) {
|
|
MultiInputMapping input = MultiInputMapping::FromConfigString(mappings[j]);
|
|
if (input.empty()) {
|
|
continue; // eat empty mappings, however they arose, so they can't keep haunting us.
|
|
}
|
|
SetInputMapping(psp_button_names[i].key, input, false);
|
|
for (auto mapping : input.mappings) {
|
|
g_seenDeviceIds.insert(mapping.deviceId);
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateNativeMenuKeys();
|
|
}
|
|
|
|
void SaveToIni(IniFile &file) {
|
|
Section *controls = file.GetOrCreateSection("ControlMapping");
|
|
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
|
|
for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++) {
|
|
std::vector<MultiInputMapping> keys;
|
|
InputMappingsFromPspButton(psp_button_names[i].key, &keys, false);
|
|
|
|
std::string value;
|
|
for (size_t j = 0; j < keys.size(); j++) {
|
|
value += keys[j].ToConfigString();
|
|
if (j != keys.size() - 1)
|
|
value += ",";
|
|
}
|
|
|
|
controls->Set(psp_button_names[i].name, value, "");
|
|
}
|
|
}
|
|
|
|
void ClearAllMappings() {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
g_controllerMap.clear();
|
|
g_controllerMapGeneration++;
|
|
}
|
|
|
|
bool IsNvidiaShield(std::string_view name) {
|
|
return name == "NVIDIA:SHIELD";
|
|
}
|
|
|
|
bool IsRetroid(std::string_view name) {
|
|
// TODO: Not sure if there are differences between different Retroid devices.
|
|
// The one I have is a "Retroid Pocket 2+".
|
|
return startsWith(name, "Retroid:");
|
|
}
|
|
|
|
bool IsNvidiaShieldTV(std::string_view name) {
|
|
return name == "NVIDIA:SHIELD Android TV";
|
|
}
|
|
|
|
bool IsXperiaPlay(std::string_view name) {
|
|
return name == "Sony Ericsson:R800a" || name == "Sony Ericsson:R800i" || name == "Sony Ericsson:R800x" || name == "Sony Ericsson:R800at" || name == "Sony Ericsson:SO-01D" || name == "Sony Ericsson:zeus";
|
|
}
|
|
|
|
bool IsMOQII7S(std::string_view name) {
|
|
return name == "MOQI:I7S";
|
|
}
|
|
|
|
bool HasBuiltinController(std::string_view name) {
|
|
return IsXperiaPlay(name) || IsNvidiaShield(name) || IsMOQII7S(name) || IsRetroid(name);
|
|
}
|
|
|
|
void NotifyPadConnected(InputDeviceID deviceId, std::string_view name) {
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
g_seenPads.insert(std::string(name));
|
|
g_padNames[deviceId] = name;
|
|
|
|
// Don't notify within the first 5 seconds, to avoid notification spam on startup.
|
|
// Also for some reason we get some strange things on Android... "Virtual"?
|
|
if (time_now_d() >= 5.0 && name != "Virtual") {
|
|
auto co = GetI18NCategory(I18NCat::CONTROLS);
|
|
g_OSD.Show(OSDType::MESSAGE_SUCCESS, ApplySafeSubstitutions("%1: %2", co->T("Game controller connected"), name), "", "I_CONTROLLER", 2.0f, "controller_connected");
|
|
}
|
|
}
|
|
|
|
System_Notify(SystemNotification::PAD_STATE_CHANGED);
|
|
}
|
|
|
|
void NotifyPadDisconnected(InputDeviceID deviceId) {
|
|
{
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
auto iter = g_padNames.find(deviceId);
|
|
if (iter != g_padNames.end()) {
|
|
auto co = GetI18NCategory(I18NCat::CONTROLS);
|
|
g_OSD.Show(OSDType::MESSAGE_WARNING, ApplySafeSubstitutions("%1: %2", co->T("Game controller disconnected"), iter->second), "", "I_CONTROLLER", 2.0f, "controller_connected");
|
|
g_seenPads.erase(iter->second);
|
|
}
|
|
g_padNames.erase(deviceId);
|
|
}
|
|
System_Notify(SystemNotification::PAD_STATE_CHANGED);
|
|
}
|
|
|
|
void ClearControlsWithDeviceId(InputDeviceID deviceId) {
|
|
bool modified = false;
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) {
|
|
auto &mappings = iter->second;
|
|
for (auto mapIter = mappings.begin(); mapIter != mappings.end(); ) {
|
|
bool found = false;
|
|
for (auto &mapping : mapIter->mappings) {
|
|
if (mapping.deviceId == deviceId) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
mapIter = mappings.erase(mapIter);
|
|
modified = true;
|
|
} else {
|
|
++mapIter;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (modified) {
|
|
g_controllerMapGeneration++;
|
|
}
|
|
}
|
|
|
|
void AutoConfForPad(std::string_view name) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
|
|
InputDeviceID deviceId = DEVICE_ID_PAD_0;
|
|
for (auto [padDeviceId, padName] : g_padNames) {
|
|
if (padName == name) {
|
|
// Already configured.
|
|
deviceId = padDeviceId;
|
|
}
|
|
}
|
|
ClearControlsWithDeviceId(deviceId);
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
if (name.find("Xbox") != std::string::npos) {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_ANDROID_XBOX, false);
|
|
} else if (name == "Retro Station Controller") {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_RETROID_CONTROLLER, false);
|
|
} else {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_ANDROID_PAD, false);
|
|
}
|
|
#else
|
|
#if PPSSPP_PLATFORM(WINDOWS)
|
|
const bool platformSupportsXinput = true;
|
|
#else
|
|
const bool platformSupportsXinput = false;
|
|
#endif
|
|
if (platformSupportsXinput && name.find("Xbox") != std::string::npos) {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_XINPUT, false);
|
|
} else {
|
|
SetDefaultKeyMap(DEFAULT_MAPPING_PAD, false);
|
|
}
|
|
#endif
|
|
|
|
// Add a couple of convenient keyboard mappings by default, too.
|
|
#if !defined(MOBILE_DEVICE)
|
|
g_controllerMap[VIRTKEY_PAUSE].push_back(MultiInputMapping(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE)));
|
|
g_controllerMap[VIRTKEY_FASTFORWARD].push_back(MultiInputMapping(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_TAB)));
|
|
#endif
|
|
g_controllerMapGeneration++;
|
|
}
|
|
|
|
const std::set<std::string> &GetSeenPads() {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
return g_seenPads;
|
|
}
|
|
|
|
std::string PadName(InputDeviceID deviceId) {
|
|
std::lock_guard<std::recursive_mutex> guard(g_controllerMapLock);
|
|
auto it = g_padNames.find(deviceId);
|
|
if (it != g_padNames.end())
|
|
return it->second;
|
|
return "";
|
|
}
|
|
|
|
bool HasChanged(int &prevGeneration) {
|
|
if (prevGeneration != g_controllerMapGeneration) {
|
|
prevGeneration = g_controllerMapGeneration;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MultiInputMapping MultiInputMapping::FromConfigString(std::string_view str) {
|
|
MultiInputMapping out;
|
|
std::vector<std::string_view> parts;
|
|
SplitString(str, ':', parts);
|
|
for (auto iter : parts) {
|
|
out.mappings.push_back(InputMapping::FromConfigString(iter));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::string MultiInputMapping::ToConfigString() const {
|
|
std::string out;
|
|
for (auto iter : mappings) {
|
|
out += iter.ToConfigString() + ":";
|
|
}
|
|
out.pop_back(); // remove the last ':'
|
|
return out;
|
|
}
|
|
|
|
std::string MultiInputMapping::ToVisualString() const {
|
|
std::string out;
|
|
for (auto iter : mappings) {
|
|
out += std::string(GetDeviceName(iter.deviceId)) + "." + GetKeyOrAxisName(iter) + " + ";
|
|
}
|
|
if (!out.empty()) {
|
|
// remove the last ' + '
|
|
out.pop_back();
|
|
out.pop_back();
|
|
out.pop_back();
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // KeyMap
|