CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/ControlMapper.cpp
Views: 1401
#include <algorithm>1#include <sstream>23#include "Common/Math/math_util.h"4#include "Common/TimeUtil.h"5#include "Common/StringUtils.h"6#include "Common/Log.h"78#include "Core/HLE/sceCtrl.h"9#include "Core/KeyMap.h"10#include "Core/ControlMapper.h"11#include "Core/Config.h"12#include "Core/CoreParameter.h"13#include "Core/System.h"1415using KeyMap::MultiInputMapping;1617const float AXIS_BIND_THRESHOLD = 0.75f;18const float AXIS_BIND_THRESHOLD_MOUSE = 0.01f;192021// We reduce the threshold of some axes when another axis on the same stick is active.22// This makes it easier to hit diagonals if you bind an analog stick to four face buttons or D-Pad.23static InputAxis GetCoAxis(InputAxis axis) {24switch (axis) {25case JOYSTICK_AXIS_X: return JOYSTICK_AXIS_Y;26case JOYSTICK_AXIS_Y: return JOYSTICK_AXIS_X;2728// This looks weird, but it's simply how XInput axes are mapped.29case JOYSTICK_AXIS_Z: return JOYSTICK_AXIS_RZ;30case JOYSTICK_AXIS_RZ: return JOYSTICK_AXIS_Z;3132// Not sure if these two are used.33case JOYSTICK_AXIS_RX: return JOYSTICK_AXIS_RY;34case JOYSTICK_AXIS_RY: return JOYSTICK_AXIS_RX;3536default:37return JOYSTICK_AXIS_MAX; // invalid38}39}4041float ControlMapper::GetDeviceAxisThreshold(int device, const InputMapping &mapping) {42if (device == DEVICE_ID_MOUSE) {43return AXIS_BIND_THRESHOLD_MOUSE;44}45if (mapping.IsAxis()) {46switch (KeyMap::GetAxisType((InputAxis)mapping.Axis(nullptr))) {47case KeyMap::AxisType::TRIGGER:48return g_Config.fAnalogTriggerThreshold;49case KeyMap::AxisType::STICK:50{51// Co-axis processing, see GetCoAxes comment.52InputAxis axis = (InputAxis)mapping.Axis(nullptr);53InputAxis coAxis = GetCoAxis(axis);54if (coAxis != JOYSTICK_AXIS_MAX) {55float absCoValue = fabsf(rawAxisValue_[(int)coAxis]);56if (absCoValue > 0.0f) {57// Bias down the threshold if the other axis is active.58float biasedThreshold = AXIS_BIND_THRESHOLD * (1.0f - absCoValue * 0.35f);59// INFO_LOG(Log::System, "coValue: %f threshold: %f", absCoValue, biasedThreshold);60return biasedThreshold;61}62}63break;64}65default:66break;67}68}69return AXIS_BIND_THRESHOLD;70}7172static int GetOppositeVKey(int vkey) {73switch (vkey) {74case VIRTKEY_AXIS_X_MIN: return VIRTKEY_AXIS_X_MAX; break;75case VIRTKEY_AXIS_X_MAX: return VIRTKEY_AXIS_X_MIN; break;76case VIRTKEY_AXIS_Y_MIN: return VIRTKEY_AXIS_Y_MAX; break;77case VIRTKEY_AXIS_Y_MAX: return VIRTKEY_AXIS_Y_MIN; break;78case VIRTKEY_AXIS_RIGHT_X_MIN: return VIRTKEY_AXIS_RIGHT_X_MAX; break;79case VIRTKEY_AXIS_RIGHT_X_MAX: return VIRTKEY_AXIS_RIGHT_X_MIN; break;80case VIRTKEY_AXIS_RIGHT_Y_MIN: return VIRTKEY_AXIS_RIGHT_Y_MAX; break;81case VIRTKEY_AXIS_RIGHT_Y_MAX: return VIRTKEY_AXIS_RIGHT_Y_MIN; break;82default:83return 0;84}85}8687static bool IsAxisVKey(int vkey) {88// Little hacky but works, of course.89return GetOppositeVKey(vkey) != 0;90}9192static bool IsUnsignedMapping(int vkey) {93return vkey == VIRTKEY_SPEED_ANALOG;94}9596static bool IsSignedAxis(int axis) {97switch (axis) {98case JOYSTICK_AXIS_X:99case JOYSTICK_AXIS_Y:100case JOYSTICK_AXIS_Z:101case JOYSTICK_AXIS_RX:102case JOYSTICK_AXIS_RY:103case JOYSTICK_AXIS_RZ:104return true;105default:106return false;107}108}109110// This is applied on the circular radius, not directly on the axes.111// TODO: Share logic with tilt?112113static float MapAxisValue(float v) {114const float deadzone = g_Config.fAnalogDeadzone;115const float invDeadzone = g_Config.fAnalogInverseDeadzone;116const float sensitivity = g_Config.fAnalogSensitivity;117const float sign = v >= 0.0f ? 1.0f : -1.0f;118119// Apply deadzone.120v = Clamp((fabsf(v) - deadzone) / (1.0f - deadzone), 0.0f, 1.0f);121122// Apply sensitivity and inverse deadzone.123if (v != 0.0f) {124v = Clamp(invDeadzone + v * (sensitivity - invDeadzone), 0.0f, 1.0f);125}126127return sign * v;128}129130void ConvertAnalogStick(float x, float y, float *outX, float *outY) {131const bool isCircular = g_Config.bAnalogIsCircular;132133float norm = std::max(fabsf(x), fabsf(y));134if (norm == 0.0f) {135*outX = x;136*outY = y;137return;138}139140if (isCircular) {141float newNorm = sqrtf(x * x + y * y);142float factor = newNorm / norm;143x *= factor;144y *= factor;145norm = newNorm;146}147148float mappedNorm = MapAxisValue(norm);149*outX = Clamp(x / norm * mappedNorm, -1.0f, 1.0f);150*outY = Clamp(y / norm * mappedNorm, -1.0f, 1.0f);151}152153void ControlMapper::SetCallbacks(154std::function<void(int, bool)> onVKey,155std::function<void(int, float)> onVKeyAnalog,156std::function<void(uint32_t, uint32_t)> updatePSPButtons,157std::function<void(int, float, float)> setPSPAnalog,158std::function<void(int, float, float)> setRawAnalog) {159onVKey_ = onVKey;160onVKeyAnalog_ = onVKeyAnalog;161updatePSPButtons_ = updatePSPButtons;162setPSPAnalog_ = setPSPAnalog;163setRawAnalog_ = setRawAnalog;164}165166void ControlMapper::SetPSPAxis(int device, int stick, char axis, float value) {167int axisId = axis == 'X' ? 0 : 1;168169float position[2];170position[0] = history_[stick][0];171position[1] = history_[stick][1];172173position[axisId] = value;174175float x = position[0];176float y = position[1];177178if (setRawAnalog_) {179setRawAnalog_(stick, x, y);180}181182// NOTE: We need to use single-axis checks, since the other axis might be from another device,183// so we'll add a little leeway.184bool inDeadZone = fabsf(value) < g_Config.fAnalogDeadzone * 0.7f;185186bool ignore = false;187if (inDeadZone && lastNonDeadzoneDeviceID_[stick] != device) {188// Ignore this event! See issue #15465189ignore = true;190}191192if (!inDeadZone) {193lastNonDeadzoneDeviceID_[stick] = device;194}195196if (!ignore) {197history_[stick][axisId] = value;198199UpdateAnalogOutput(stick);200}201}202203void ControlMapper::UpdateAnalogOutput(int stick) {204float x, y;205ConvertAnalogStick(history_[stick][0], history_[stick][1], &x, &y);206if (virtKeyOn_[VIRTKEY_ANALOG_LIGHTLY - VIRTKEY_FIRST]) {207x *= g_Config.fAnalogLimiterDeadzone;208y *= g_Config.fAnalogLimiterDeadzone;209}210converted_[stick][0] = x;211converted_[stick][1] = y;212setPSPAnalog_(stick, x, y);213}214215void ControlMapper::ForceReleaseVKey(int vkey) {216// Note: This one is called from an onVKey_ handler, which already holds mutex_.217218KeyMap::LockMappings();219std::vector<KeyMap::MultiInputMapping> multiMappings;220if (KeyMap::InputMappingsFromPspButtonNoLock(vkey, &multiMappings, true)) {221double now = time_now_d();222for (const auto &entry : multiMappings) {223for (const auto &mapping : entry.mappings) {224curInput_[mapping] = { 0.0f, now };225// Different logic for signed axes?226UpdatePSPState(mapping, now);227}228}229}230KeyMap::UnlockMappings();231}232233void ControlMapper::ReleaseAll() {234std::vector<AxisInput> axes;235std::vector<KeyInput> keys;236237{238std::lock_guard<std::mutex> guard(mutex_);239240for (const auto &input : curInput_) {241if (input.first.IsAxis()) {242if (input.second.value != 0.0f) {243AxisInput axis;244axis.deviceId = input.first.deviceId;245int dir;246axis.axisId = (InputAxis)input.first.Axis(&dir);247axis.value = 0.0;248axes.push_back(axis);249}250} else {251if (input.second.value != 0.0) {252KeyInput key;253key.deviceId = input.first.deviceId;254key.flags = KEY_UP;255key.keyCode = (InputKeyCode)input.first.keyCode;256keys.push_back(key);257}258}259}260}261262Axis(axes.data(), axes.size());;263for (const auto &key : keys) {264Key(key, nullptr);265}266}267268269static int RotatePSPKeyCode(int x) {270switch (x) {271case CTRL_UP: return CTRL_RIGHT;272case CTRL_RIGHT: return CTRL_DOWN;273case CTRL_DOWN: return CTRL_LEFT;274case CTRL_LEFT: return CTRL_UP;275default:276return x;277}278}279280// Used to decay analog values when clashing with digital ones.281static ControlMapper::InputSample ReduceMagnitude(ControlMapper::InputSample sample, double now) {282float reduction = std::min(std::max(0.0f, (float)(now - sample.timestamp) - 2.0f), 1.0f);283if (reduction > 0.0f) {284sample.value *= (1.0f - reduction);285}286if ((sample.value > 0.0f && sample.value < 0.05f) || (sample.value < 0.0f && sample.value > -0.05f)) {287sample.value = 0.0f;288}289return sample;290}291292float ControlMapper::MapAxisValue(float value, int vkId, const InputMapping &mapping, const InputMapping &changedMapping, bool *oppositeTouched) {293if (IsUnsignedMapping(vkId)) {294// If a signed axis is mapped to an unsigned mapping,295// convert it. This happens when mapping DirectInput triggers to analog speed,296// for example.297int direction;298if (IsSignedAxis(mapping.Axis(&direction))) {299// The value has been split up into two curInput values, so we need to go fetch the other300// and put them back together again. Kind of awkward, but at least makes the regular case simple...301InputMapping other = mapping.FlipDirection();302if (other == changedMapping) {303*oppositeTouched = true;304}305float valueOther = curInput_[other].value;306float signedValue = value - valueOther;307float ranged = (signedValue + 1.0f) * 0.5f;308if (direction == -1) {309ranged = 1.0f - ranged;310}311// NOTICE_LOG(Log::System, "rawValue: %f other: %f signed: %f ranged: %f", iter->second, valueOther, signedValue, ranged);312return ranged;313} else {314return value;315}316} else {317return value;318}319}320321static bool IsSwappableVKey(uint32_t vkey) {322switch (vkey) {323case CTRL_UP:324case CTRL_LEFT:325case CTRL_DOWN:326case CTRL_RIGHT:327case VIRTKEY_AXIS_X_MIN:328case VIRTKEY_AXIS_X_MAX:329case VIRTKEY_AXIS_Y_MIN:330case VIRTKEY_AXIS_Y_MAX:331return true;332default:333return false;334}335}336337void ControlMapper::SwapMappingIfEnabled(uint32_t *vkey) {338if (swapAxes_) {339switch (*vkey) {340case CTRL_UP: *vkey = VIRTKEY_AXIS_Y_MAX; break;341case VIRTKEY_AXIS_Y_MAX: *vkey = CTRL_UP; break;342case CTRL_DOWN: *vkey = VIRTKEY_AXIS_Y_MIN; break;343case VIRTKEY_AXIS_Y_MIN: *vkey = CTRL_DOWN; break;344case CTRL_LEFT: *vkey = VIRTKEY_AXIS_X_MIN; break;345case VIRTKEY_AXIS_X_MIN: *vkey = CTRL_LEFT; break;346case CTRL_RIGHT: *vkey = VIRTKEY_AXIS_X_MAX; break;347case VIRTKEY_AXIS_X_MAX: *vkey = CTRL_RIGHT; break;348}349}350}351352// Can only be called from Key or Axis.353// mutex_ should be locked, and also KeyMap::LockMappings().354// TODO: We should probably make a batched version of this.355bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping, double now) {356// Instead of taking an input key and finding what it outputs, we loop through the OUTPUTS and357// see if the input that corresponds to it has a value. That way we can easily implement all sorts358// of crazy input combos if needed.359360int rotations = 0;361switch (g_Config.iInternalScreenRotation) {362case ROTATION_LOCKED_HORIZONTAL180: rotations = 2; break;363case ROTATION_LOCKED_VERTICAL: rotations = 1; break;364case ROTATION_LOCKED_VERTICAL180: rotations = 3; break;365}366367// For the PSP's digital button inputs, we just go through and put the flags together.368uint32_t buttonMask = 0;369uint32_t changedButtonMask = 0;370std::vector<MultiInputMapping> inputMappings;371for (int i = 0; i < 32; i++) {372uint32_t mask = 1 << i;373if (!(mask & CTRL_MASK_USER)) {374// Not a mappable button bit375continue;376}377378uint32_t mappingBit = mask;379for (int i = 0; i < rotations; i++) {380mappingBit = RotatePSPKeyCode(mappingBit);381}382383SwapMappingIfEnabled(&mappingBit);384if (!KeyMap::InputMappingsFromPspButtonNoLock(mappingBit, &inputMappings, false))385continue;386387// If a mapping could consist of a combo, we could trivially check it here.388for (auto &multiMapping : inputMappings) {389// Check if the changed mapping was involved in this PSP key.390if (multiMapping.mappings.contains(changedMapping)) {391changedButtonMask |= mask;392}393// Check if all inputs are "on".394bool all = true;395double curTime = 0.0;396for (auto mapping : multiMapping.mappings) {397auto iter = curInput_.find(mapping);398if (iter == curInput_.end()) {399all = false;400continue;401}402// Stop reverse ordering from triggering.403if (g_Config.bStrictComboOrder && iter->second.timestamp < curTime) {404all = false;405break;406} else {407curTime = iter->second.timestamp;408}409bool down = iter->second.value > 0.0f && iter->second.value > GetDeviceAxisThreshold(iter->first.deviceId, mapping);410if (!down)411all = false;412}413if (all) {414buttonMask |= mask;415}416}417}418419// We only request changing the buttons where the mapped input was involved.420updatePSPButtons_(buttonMask & changedButtonMask, (~buttonMask) & changedButtonMask);421422bool keyInputUsed = changedButtonMask != 0;423bool updateAnalogSticks = false;424425// OK, handle all the virtual keys next. For these we need to do deltas here and send events.426// Note that virtual keys include the analog directions, as they are driven by them.427for (int i = 0; i < VIRTKEY_COUNT; i++) {428int vkId = i + VIRTKEY_FIRST;429430uint32_t idForMapping = vkId;431SwapMappingIfEnabled(&idForMapping);432433if (!KeyMap::InputMappingsFromPspButtonNoLock(idForMapping, &inputMappings, false))434continue;435436// If a mapping could consist of a combo, we could trivially check it here.437// Save the first device ID so we can pass it into onVKeyDown, which in turn needs it for the analog438// mapping which gets a little hacky.439float threshold = 1.0f;440bool touchedByMapping = false;441float value = 0.0f;442for (auto &multiMapping : inputMappings) {443if (multiMapping.mappings.contains(changedMapping)) {444touchedByMapping = true;445}446447float product = 1.0f; // We multiply the various inputs in a combo mapping with each other.448double curTime = 0.0;449for (auto mapping : multiMapping.mappings) {450auto iter = curInput_.find(mapping);451452if (iter != curInput_.end()) {453// Stop reverse ordering from triggering.454if (g_Config.bStrictComboOrder && iter->second.timestamp < curTime) {455product = 0.0f;456break;457} else {458curTime = iter->second.timestamp;459}460461if (mapping.IsAxis()) {462threshold = GetDeviceAxisThreshold(iter->first.deviceId, mapping);463float value = MapAxisValue(iter->second.value, idForMapping, mapping, changedMapping, &touchedByMapping);464product *= value;465} else {466product *= iter->second.value;467}468} else {469product = 0.0f;470}471}472473value += product;474}475476if (!touchedByMapping) {477continue;478}479480keyInputUsed = true;481482// Small values from analog inputs like gamepad sticks can linger around, which is bad here because we sum483// up before applying deadzone etc. This means that it can be impossible to reach the min/max values with digital input!484// So if non-analog events clash with analog ones mapped to the same input, decay the analog input,485// which will quickly get things back to normal, while if it's intentional to use both at the same time for some reason,486// that still works, though a bit weaker. We could also zero here, but you never know who relies on such strange tricks..487// Note: This is an old problem, it didn't appear with the refactoring.488if (!changedMapping.IsAxis()) {489for (auto &multiMapping : inputMappings) {490for (auto &mapping : multiMapping.mappings) {491if (mapping != changedMapping && curInput_[mapping].value > 0.0f) {492// Note that this takes the time into account now - values will493// decay after a while, not immediately.494curInput_[mapping] = ReduceMagnitude(curInput_[mapping], now);495}496}497}498}499500value = clamp_value(value, 0.0f, 1.0f);501502// Derive bools from the floats using the device's threshold.503// NOTE: This must be before the equality check below.504bool bPrevValue = virtKeys_[i] >= threshold;505bool bValue = value >= threshold;506507if (virtKeys_[i] != value) {508// INFO_LOG(Log::G3D, "vkeyanalog %s : %f", KeyMap::GetVirtKeyName(vkId), value);509onVKeyAnalog(changedMapping.deviceId, vkId, value);510virtKeys_[i] = value;511}512513if (!bPrevValue && bValue) {514// INFO_LOG(Log::G3D, "vkeyon %s", KeyMap::GetVirtKeyName(vkId));515onVKey(vkId, true);516virtKeyOn_[vkId - VIRTKEY_FIRST] = true;517518if (vkId == VIRTKEY_ANALOG_LIGHTLY) {519updateAnalogSticks = true;520}521} else if (bPrevValue && !bValue) {522// INFO_LOG(Log::G3D, "vkeyoff %s", KeyMap::GetVirtKeyName(vkId));523onVKey(vkId, false);524virtKeyOn_[vkId - VIRTKEY_FIRST] = false;525526if (vkId == VIRTKEY_ANALOG_LIGHTLY) {527updateAnalogSticks = true;528}529}530}531532if (updateAnalogSticks) {533// If "lightly" (analog limiter) was toggled, we need to update both computed stick outputs.534UpdateAnalogOutput(0);535UpdateAnalogOutput(1);536}537538return keyInputUsed;539}540541bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) {542if (key.flags & KEY_IS_REPEAT) {543// Claim that we handled this. Prevents volume key repeats from popping up the volume control on Android.544return true;545}546547double now = time_now_d();548InputMapping mapping(key.deviceId, key.keyCode);549550std::lock_guard<std::mutex> guard(mutex_);551552if (key.deviceId < DEVICE_ID_COUNT) {553deviceTimestamps_[(int)key.deviceId] = now;554}555556if (key.flags & KEY_DOWN) {557curInput_[mapping] = { 1.0f, now };558} else if (key.flags & KEY_UP) {559curInput_[mapping] = { 0.0f, now};560}561562// TODO: See if this can be simplified further somehow.563if ((key.flags & KEY_DOWN) && key.keyCode == NKCODE_BACK) {564bool mappingFound = KeyMap::InputMappingToPspButton(mapping, nullptr);565DEBUG_LOG(Log::System, "Key: %d DeviceId: %d", key.keyCode, key.deviceId);566if (!mappingFound || key.deviceId == DEVICE_ID_DEFAULT) {567*pauseTrigger = true;568return true;569}570}571572KeyMap::LockMappings();573bool retval = UpdatePSPState(mapping, now);574KeyMap::UnlockMappings();575return retval;576}577578void ControlMapper::ToggleSwapAxes() {579// Note: The lock is already locked here.580581swapAxes_ = !swapAxes_;582583updatePSPButtons_(0, CTRL_LEFT | CTRL_RIGHT | CTRL_UP | CTRL_DOWN);584585for (uint32_t vkey = VIRTKEY_FIRST; vkey < VIRTKEY_LAST; vkey++) {586if (IsSwappableVKey(vkey)) {587if (virtKeyOn_[vkey - VIRTKEY_FIRST]) {588onVKey_(vkey, false);589virtKeyOn_[vkey - VIRTKEY_FIRST] = false;590}591if (virtKeys_[vkey - VIRTKEY_FIRST] > 0.0f) {592onVKeyAnalog_(vkey, 0.0f);593virtKeys_[vkey - VIRTKEY_FIRST] = 0.0f;594}595}596}597598history_[0][0] = 0.0f;599history_[0][1] = 0.0f;600601UpdateAnalogOutput(0);602UpdateAnalogOutput(1);603}604605void ControlMapper::UpdateCurInputAxis(const InputMapping &mapping, float value, double timestamp) {606InputSample &input = curInput_[mapping];607input.value = value;608if (value >= GetDeviceAxisThreshold(mapping.deviceId, mapping)) {609if (input.timestamp == 0.0) {610input.timestamp = time_now_d();611}612} else {613input.timestamp = 0.0;614}615}616617void ControlMapper::Axis(const AxisInput *axes, size_t count) {618double now = time_now_d();619620std::lock_guard<std::mutex> guard(mutex_);621622KeyMap::LockMappings();623for (size_t i = 0; i < count; i++) {624const AxisInput &axis = axes[i];625626if (axis.deviceId == DEVICE_ID_MOUSE && !g_Config.bMouseControl) {627continue;628}629630size_t deviceIndex = (size_t)axis.deviceId; // this wraps -1 up high, so will get rejected on the next line.631if (deviceIndex < (size_t)DEVICE_ID_COUNT) {632deviceTimestamps_[deviceIndex] = now;633}634rawAxisValue_[axis.axisId] = axis.value; // these are only used for co-axis mapping635if (axis.value >= 0.0f) {636InputMapping mapping(axis.deviceId, axis.axisId, 1);637InputMapping opposite(axis.deviceId, axis.axisId, -1);638UpdateCurInputAxis(mapping, axis.value, now);639UpdateCurInputAxis(opposite, 0.0f, now);640UpdatePSPState(mapping, now);641UpdatePSPState(opposite, now);642} else if (axis.value < 0.0f) {643InputMapping mapping(axis.deviceId, axis.axisId, -1);644InputMapping opposite(axis.deviceId, axis.axisId, 1);645UpdateCurInputAxis(mapping, -axis.value, now);646UpdateCurInputAxis(opposite, 0.0f, now);647UpdatePSPState(mapping, now);648UpdatePSPState(opposite, now);649}650}651KeyMap::UnlockMappings();652}653654void ControlMapper::Update(double now) {655if (autoRotatingAnalogCW_) {656// Clamp to a square657float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * -g_Config.fAnalogAutoRotSpeed)));658float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * -g_Config.fAnalogAutoRotSpeed)));659660setPSPAnalog_(0, x, y);661} else if (autoRotatingAnalogCCW_) {662float x = std::min(1.0f, std::max(-1.0f, 1.42f * (float)cos(now * g_Config.fAnalogAutoRotSpeed)));663float y = std::min(1.0f, std::max(-1.0f, 1.42f * (float)sin(now * g_Config.fAnalogAutoRotSpeed)));664665setPSPAnalog_(0, x, y);666}667}668669void ControlMapper::PSPKey(int deviceId, int pspKeyCode, int flags) {670std::lock_guard<std::mutex> guard(mutex_);671if (pspKeyCode >= VIRTKEY_FIRST) {672int vk = pspKeyCode - VIRTKEY_FIRST;673if (flags & KEY_DOWN) {674virtKeys_[vk] = 1.0f;675onVKey(pspKeyCode, true);676onVKeyAnalog(deviceId, pspKeyCode, 1.0f);677}678if (flags & KEY_UP) {679virtKeys_[vk] = 0.0f;680onVKey(pspKeyCode, false);681onVKeyAnalog(deviceId, pspKeyCode, 0.0f);682}683} else {684// INFO_LOG(Log::System, "pspKey %d %d", pspKeyCode, flags);685if (flags & KEY_DOWN)686updatePSPButtons_(pspKeyCode, 0);687if (flags & KEY_UP)688updatePSPButtons_(0, pspKeyCode);689}690}691692void ControlMapper::onVKeyAnalog(int deviceId, int vkey, float value) {693// Unfortunately, for digital->analog inputs to work sanely, we need to sum up694// with the opposite value too.695int stick = 0;696int axis = 'X';697int oppositeVKey = GetOppositeVKey(vkey);698float sign = 1.0f;699switch (vkey) {700case VIRTKEY_AXIS_X_MIN: sign = -1.0f; break;701case VIRTKEY_AXIS_X_MAX: break;702case VIRTKEY_AXIS_Y_MIN: axis = 'Y'; sign = -1.0f; break;703case VIRTKEY_AXIS_Y_MAX: axis = 'Y'; break;704case VIRTKEY_AXIS_RIGHT_X_MIN: stick = CTRL_STICK_RIGHT; sign = -1.0f; break;705case VIRTKEY_AXIS_RIGHT_X_MAX: stick = CTRL_STICK_RIGHT; break;706case VIRTKEY_AXIS_RIGHT_Y_MIN: stick = CTRL_STICK_RIGHT; axis = 'Y'; sign = -1.0f; break;707case VIRTKEY_AXIS_RIGHT_Y_MAX: stick = CTRL_STICK_RIGHT; axis = 'Y'; break;708default:709if (onVKeyAnalog_)710onVKeyAnalog_(vkey, value);711return;712}713if (oppositeVKey != 0) {714float oppVal = virtKeys_[oppositeVKey - VIRTKEY_FIRST];715if (oppVal != 0.0f) {716value -= oppVal;717// NOTICE_LOG(Log::sceCtrl, "Reducing %f by %f (from %08x : %s)", value, oppVal, oppositeVKey, KeyMap::GetPspButtonName(oppositeVKey).c_str());718}719}720SetPSPAxis(deviceId, stick, axis, sign * value);721}722723void ControlMapper::onVKey(int vkey, bool down) {724switch (vkey) {725case VIRTKEY_ANALOG_ROTATE_CW:726if (down) {727autoRotatingAnalogCW_ = true;728autoRotatingAnalogCCW_ = false;729} else {730autoRotatingAnalogCW_ = false;731setPSPAnalog_(0, 0.0f, 0.0f);732}733break;734case VIRTKEY_ANALOG_ROTATE_CCW:735if (down) {736autoRotatingAnalogCW_ = false;737autoRotatingAnalogCCW_ = true;738} else {739autoRotatingAnalogCCW_ = false;740setPSPAnalog_(0, 0.0f, 0.0f);741}742break;743default:744if (onVKey_)745onVKey_(vkey, down);746break;747}748}749750void ControlMapper::GetDebugString(char *buffer, size_t bufSize) const {751std::stringstream str;752for (auto iter : curInput_) {753char temp[256];754iter.first.FormatDebug(temp, sizeof(temp));755str << temp << ": " << iter.second.value << std::endl;756}757for (int i = 0; i < ARRAY_SIZE(virtKeys_); i++) {758int vkId = VIRTKEY_FIRST + i;759if ((vkId >= VIRTKEY_AXIS_X_MIN && vkId <= VIRTKEY_AXIS_Y_MAX) || vkId == VIRTKEY_ANALOG_LIGHTLY || vkId == VIRTKEY_SPEED_ANALOG) {760str << KeyMap::GetPspButtonName(vkId) << ": " << virtKeys_[i] << std::endl;761}762}763str << "Lstick: " << converted_[0][0] << ", " << converted_[0][1] << std::endl;764truncate_cpy(buffer, bufSize, str.str().c_str());765}766767768