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/Windows/DinputDevice.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "stdafx.h"18#include <limits.h>19#include <algorithm>20#include <mmsystem.h>21#include <XInput.h>2223#include "Common/Input/InputState.h"24#include "Common/Input/KeyCodes.h"25#include "Common/LogReporting.h"26#include "Common/StringUtils.h"27#include "Common/System/NativeApp.h"28#include "Core/Config.h"29#include "Core/HLE/sceCtrl.h"30#include "Core/KeyMap.h"31#include "Windows/DinputDevice.h"32#pragma comment(lib,"dinput8.lib")3334//initialize static members of DinputDevice35unsigned int DinputDevice::pInstances = 0;36LPDIRECTINPUT8 DinputDevice::pDI = NULL;37std::vector<DIDEVICEINSTANCE> DinputDevice::devices;38bool DinputDevice::needsCheck_ = true;3940// In order from 0. There can be 128, but most controllers do not have that many.41static const InputKeyCode dinput_buttons[] = {42NKCODE_BUTTON_1,43NKCODE_BUTTON_2,44NKCODE_BUTTON_3,45NKCODE_BUTTON_4,46NKCODE_BUTTON_5,47NKCODE_BUTTON_6,48NKCODE_BUTTON_7,49NKCODE_BUTTON_8,50NKCODE_BUTTON_9,51NKCODE_BUTTON_10,52NKCODE_BUTTON_11,53NKCODE_BUTTON_12,54NKCODE_BUTTON_13,55NKCODE_BUTTON_14,56NKCODE_BUTTON_15,57NKCODE_BUTTON_16,58};5960#define DIFF (JOY_POVRIGHT - JOY_POVFORWARD) / 261#define JOY_POVFORWARD_RIGHT JOY_POVFORWARD + DIFF62#define JOY_POVRIGHT_BACKWARD JOY_POVRIGHT + DIFF63#define JOY_POVBACKWARD_LEFT JOY_POVBACKWARD + DIFF64#define JOY_POVLEFT_FORWARD JOY_POVLEFT + DIFF6566struct XINPUT_DEVICE_NODE {67DWORD dwVidPid;68XINPUT_DEVICE_NODE* pNext;69};70XINPUT_DEVICE_NODE* g_pXInputDeviceList = NULL;7172bool IsXInputDevice( const GUID* pGuidProductFromDirectInput ) {73XINPUT_DEVICE_NODE* pNode = g_pXInputDeviceList;74while( pNode )75{76if( pNode->dwVidPid == pGuidProductFromDirectInput->Data1 )77return true;78pNode = pNode->pNext;79}8081return false;82}8384LPDIRECTINPUT8 DinputDevice::getPDI()85{86if (pDI == NULL)87{88if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&pDI, NULL)))89{90pDI = NULL;91}92}93return pDI;94}9596BOOL CALLBACK DinputDevice::DevicesCallback(97LPCDIDEVICEINSTANCE lpddi,98LPVOID pvRef99)100{101//check if a device with the same Instance guid is already saved102auto res = std::find_if(devices.begin(), devices.end(),103[lpddi](const DIDEVICEINSTANCE &to_consider){104return lpddi->guidInstance == to_consider.guidInstance;105});106if (res == devices.end()) //not yet in the devices list107{108// Ignore if device supports XInput109if (!IsXInputDevice(&lpddi->guidProduct)) {110devices.push_back(*lpddi);111}112}113return DIENUM_CONTINUE;114}115116void DinputDevice::getDevices(bool refresh)117{118if (refresh)119{120getPDI()->EnumDevices(DI8DEVCLASS_GAMECTRL, &DinputDevice::DevicesCallback, NULL, DIEDFL_ATTACHEDONLY);121}122}123124DinputDevice::DinputDevice(int devnum) {125pInstances++;126pDevNum = devnum;127pJoystick = NULL;128memset(lastButtons_, 0, sizeof(lastButtons_));129memset(lastPOV_, 0, sizeof(lastPOV_));130last_lX_ = 0;131last_lY_ = 0;132last_lZ_ = 0;133last_lRx_ = 0;134last_lRy_ = 0;135last_lRz_ = 0;136137if (getPDI() == NULL)138{139return;140}141142if (devnum >= MAX_NUM_PADS)143{144return;145}146147getDevices(needsCheck_);148if ( (devnum >= (int)devices.size()) || FAILED(getPDI()->CreateDevice(devices.at(devnum).guidInstance, &pJoystick, NULL)))149{150return;151}152153wchar_t guid[64];154if (StringFromGUID2(devices.at(devnum).guidProduct, guid, ARRAY_SIZE(guid)) != 0) {155KeyMap::NotifyPadConnected(DEVICE_ID_PAD_0 + pDevNum, StringFromFormat("%S: %S", devices.at(devnum).tszProductName, guid));156}157158if (FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick2))) {159pJoystick->Release();160pJoystick = NULL;161return;162}163164DIPROPRANGE diprg;165diprg.diph.dwSize = sizeof(DIPROPRANGE);166diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);167diprg.diph.dwHow = DIPH_DEVICE;168diprg.diph.dwObj = 0;169diprg.lMin = -10000;170diprg.lMax = 10000;171172analog = FAILED(pJoystick->SetProperty(DIPROP_RANGE, &diprg.diph)) ? false : true;173174// Other devices suffer if the deadzone is not set.175// TODO: The dead zone will be made configurable in the Control dialog.176DIPROPDWORD dipw;177dipw.diph.dwSize = sizeof(DIPROPDWORD);178dipw.diph.dwHeaderSize = sizeof(DIPROPHEADER);179dipw.diph.dwHow = DIPH_DEVICE;180dipw.diph.dwObj = 0;181// dwData 10000 is deadzone(0% - 100%), multiply by config scalar182dipw.dwData = 0;183184analog |= FAILED(pJoystick->SetProperty(DIPROP_DEADZONE, &dipw.diph)) ? false : true;185}186187DinputDevice::~DinputDevice() {188if (pJoystick) {189pJoystick->Release();190pJoystick = NULL;191}192193pInstances--;194195//the whole instance counter is obviously highly thread-unsafe196//but I don't think creation and destruction operations will be197//happening at the same time and other values like pDI are198//unsafe as well anyway199if (pInstances == 0 && pDI) {200pDI->Release();201pDI = NULL;202}203}204205void SendNativeAxis(InputDeviceID deviceId, int value, int &lastValue, InputAxis axisId) {206if (value != lastValue) {207AxisInput axis;208axis.deviceId = deviceId;209axis.axisId = axisId;210axis.value = (float)value * (1.0f / 10000.0f); // Convert axis to normalised float211NativeAxis(&axis, 1);212}213lastValue = value;214}215216static LONG *ValueForAxisId(DIJOYSTATE2 &js, int axisId) {217switch (axisId) {218case JOYSTICK_AXIS_X: return &js.lX;219case JOYSTICK_AXIS_Y: return &js.lY;220case JOYSTICK_AXIS_Z: return &js.lZ;221case JOYSTICK_AXIS_RX: return &js.lRx;222case JOYSTICK_AXIS_RY: return &js.lRy;223case JOYSTICK_AXIS_RZ: return &js.lRz;224default: return nullptr;225}226}227228int DinputDevice::UpdateState() {229if (!pJoystick) return -1;230231DIJOYSTATE2 js;232233if (FAILED(pJoystick->Poll())) {234if(pJoystick->Acquire() == DIERR_INPUTLOST)235return -1;236}237238if(FAILED(pJoystick->GetDeviceState(sizeof(DIJOYSTATE2), &js)))239return -1;240241ApplyButtons(js);242243if (analog) {244// TODO: Use the batched interface.245AxisInput axis;246axis.deviceId = DEVICE_ID_PAD_0 + pDevNum;247248SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X);249SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y);250SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lZ, last_lZ_, JOYSTICK_AXIS_Z);251SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRx, last_lRx_, JOYSTICK_AXIS_RX);252SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRy, last_lRy_, JOYSTICK_AXIS_RY);253SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lRz, last_lRz_, JOYSTICK_AXIS_RZ);254}255256//check if the values have changed from last time and skip polling the rest of the dinput devices if they did257//this doesn't seem to quite work if only the axis have changed258if ((memcmp(js.rgbButtons, pPrevState.rgbButtons, sizeof(BYTE) * 128) != 0)259|| (memcmp(js.rgdwPOV, pPrevState.rgdwPOV, sizeof(DWORD) * 4) != 0)260|| js.lVX != 0 || js.lVY != 0 || js.lVZ != 0 || js.lVRx != 0 || js.lVRy != 0 || js.lVRz != 0)261{262pPrevState = js;263return UPDATESTATE_SKIP_PAD;264}265return -1;266}267268void DinputDevice::ApplyButtons(DIJOYSTATE2 &state) {269BYTE *buttons = state.rgbButtons;270u32 downMask = 0x80;271272for (int i = 0; i < ARRAY_SIZE(dinput_buttons); ++i) {273if (state.rgbButtons[i] == lastButtons_[i]) {274continue;275}276277bool down = (state.rgbButtons[i] & downMask) == downMask;278KeyInput key;279key.deviceId = DEVICE_ID_PAD_0 + pDevNum;280key.flags = down ? KEY_DOWN : KEY_UP;281key.keyCode = dinput_buttons[i];282NativeKey(key);283284lastButtons_[i] = state.rgbButtons[i];285}286287// Now the POV hat, which can technically go in any degree but usually does not.288if (LOWORD(state.rgdwPOV[0]) != lastPOV_[0]) {289KeyInput dpad[4]{};290for (int i = 0; i < 4; ++i) {291dpad[i].deviceId = DEVICE_ID_PAD_0 + pDevNum;292dpad[i].flags = KEY_UP;293}294dpad[0].keyCode = NKCODE_DPAD_UP;295dpad[1].keyCode = NKCODE_DPAD_LEFT;296dpad[2].keyCode = NKCODE_DPAD_DOWN;297dpad[3].keyCode = NKCODE_DPAD_RIGHT;298299if (LOWORD(state.rgdwPOV[0]) != JOY_POVCENTERED) {300// These are the edges, so we use or.301if (state.rgdwPOV[0] >= JOY_POVLEFT_FORWARD || state.rgdwPOV[0] <= JOY_POVFORWARD_RIGHT) {302dpad[0].flags = KEY_DOWN;303}304if (state.rgdwPOV[0] >= JOY_POVBACKWARD_LEFT && state.rgdwPOV[0] <= JOY_POVLEFT_FORWARD) {305dpad[1].flags = KEY_DOWN;306}307if (state.rgdwPOV[0] >= JOY_POVRIGHT_BACKWARD && state.rgdwPOV[0] <= JOY_POVBACKWARD_LEFT) {308dpad[2].flags = KEY_DOWN;309}310if (state.rgdwPOV[0] >= JOY_POVFORWARD_RIGHT && state.rgdwPOV[0] <= JOY_POVRIGHT_BACKWARD) {311dpad[3].flags = KEY_DOWN;312}313}314315NativeKey(dpad[0]);316NativeKey(dpad[1]);317NativeKey(dpad[2]);318NativeKey(dpad[3]);319320lastPOV_[0] = LOWORD(state.rgdwPOV[0]);321}322}323324size_t DinputDevice::getNumPads()325{326getDevices(needsCheck_);327needsCheck_ = false;328return devices.size();329}330331332