mirror of https://github.com/PCSX2/pcsx2.git
509 lines
16 KiB
C++
509 lines
16 KiB
C++
/* LilyPad - Pad plugin for PS2 Emulator
|
|
* Copyright (C) 2002-2014 PCSX2 Dev Team/ChickenLiver
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the
|
|
* terms of the GNU Lesser General Public License as published by the Free
|
|
* Software Found- ation, either version 3 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* PCSX2 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 for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with PCSX2. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "Global.h"
|
|
|
|
#include "InputManager.h"
|
|
#include "Config.h"
|
|
#include "DeviceEnumerator.h"
|
|
#include "Linux/ConfigHelper.h"
|
|
|
|
GeneralConfig config;
|
|
u8 ps2e = 0;
|
|
|
|
#if 0
|
|
remove 0x10F0 to compute the cmd value
|
|
|
|
#define ID_SENSITIVITY 0x1007
|
|
#define ID_LOCK_BUTTONS 0x10FC
|
|
#define ID_LOCK 0x10FD
|
|
#define ID_LOCK_DIRECTION 0x10FE
|
|
#define ID_MOUSE 0x10FF
|
|
#define ID_SELECT 0x1100
|
|
#define ID_L3 0x1101
|
|
#define ID_R3 0x1102
|
|
#define ID_START 0x1103
|
|
#define ID_DPAD_UP 0x1104
|
|
#define ID_DPAD_RIGHT 0x1105
|
|
#define ID_DPAD_DOWN 0x1106
|
|
#define ID_DPAD_LEFT 0x1107
|
|
#define ID_L2 0x1108
|
|
#define ID_R2 0x1109
|
|
#define ID_L1 0x110A
|
|
#define ID_R1 0x110B
|
|
#define ID_TRIANGLE 0x110C
|
|
#define ID_CIRCLE 0x110D
|
|
#define ID_CROSS 0x110E
|
|
#define ID_SQUARE 0x110F
|
|
#define ID_LSTICK_UP 0x1110
|
|
#define ID_LSTICK_RIGHT 0x1111
|
|
#define ID_LSTICK_DOWN 0x1112
|
|
#define ID_LSTICK_LEFT 0x1113
|
|
#define ID_RSTICK_UP 0x1114
|
|
#define ID_RSTICK_RIGHT 0x1115
|
|
#define ID_RSTICK_DOWN 0x1116
|
|
#define ID_RSTICK_LEFT 0x1117
|
|
#define ID_ANALOG 0x1118
|
|
#define ID_DELETE 0x11FF
|
|
#define ID_DEBUG 0x1200
|
|
#define ID_IGNORE 0x1201
|
|
#define ID_CLEAR 0x1202
|
|
#define ID_REFRESH 0x1202
|
|
#define ID_SAVE 0x1204
|
|
#define ID_LOAD 0x1205
|
|
#define ID_BIG_MOTOR 0x120A
|
|
#define ID_SMALL_MOTOR 0x120B
|
|
#define ID_TEST 0x1300
|
|
#define ID_CONTROLS 0x1301
|
|
#define ID_FF 0x1304
|
|
|
|
#endif
|
|
|
|
struct GeneralSettingsBool {
|
|
const wchar_t *name;
|
|
unsigned int ControlId;
|
|
u8 defaultValue;
|
|
};
|
|
|
|
// XXX: I try to remove only gui stuff
|
|
void DeleteBinding(int port, int slot, Device *dev, Binding *b) {
|
|
fprintf(stderr, "delete binding %d:%d\n", port, slot);
|
|
Binding *bindings = dev->pads[port][slot].bindings;
|
|
int i = b - bindings;
|
|
memmove(bindings+i, bindings+i+1, sizeof(Binding) * (dev->pads[port][slot].numBindings - i - 1));
|
|
dev->pads[port][slot].numBindings--;
|
|
}
|
|
|
|
void DeleteBinding(int port, int slot, Device *dev, ForceFeedbackBinding *b) {
|
|
ForceFeedbackBinding *bindings = dev->pads[port][slot].ffBindings;
|
|
int i = b - bindings;
|
|
memmove(bindings+i, bindings+i+1, sizeof(Binding) * (dev->pads[port][slot].numFFBindings - i - 1));
|
|
dev->pads[port][slot].numFFBindings--;
|
|
}
|
|
|
|
int BindCommand(Device *dev, unsigned int uid, unsigned int port, unsigned int slot, int command, int sensitivity, int turbo, int deadZone) {
|
|
// Checks needed because I use this directly when loading bindings.
|
|
if (port > 1 || slot>3) {
|
|
return -1;
|
|
}
|
|
if (!sensitivity) sensitivity = BASE_SENSITIVITY;
|
|
if ((uid>>16) & (PSHBTN|TGLBTN)) {
|
|
deadZone = 0;
|
|
}
|
|
else if (!deadZone) {
|
|
if ((uid>>16) & PRESSURE_BTN) {
|
|
deadZone = 1;
|
|
}
|
|
else {
|
|
deadZone = DEFAULT_DEADZONE;
|
|
}
|
|
}
|
|
// Relative axes can have negative sensitivity.
|
|
else if (((uid>>16) & 0xFF) == RELAXIS) {
|
|
sensitivity = abs(sensitivity);
|
|
}
|
|
VirtualControl *c = dev->GetVirtualControl(uid);
|
|
if (!c) return -1;
|
|
// Add before deleting. Means I won't scroll up one line when scrolled down to bottom.
|
|
int controlIndex = c - dev->virtualControls;
|
|
int index = 0;
|
|
PadBindings *p = dev->pads[port]+slot;
|
|
p->bindings = (Binding*) realloc(p->bindings, (p->numBindings+1) * sizeof(Binding));
|
|
for (index = p->numBindings; index > 0; index--) {
|
|
if (p->bindings[index-1].controlIndex < controlIndex) break;
|
|
p->bindings[index] = p->bindings[index-1];
|
|
}
|
|
Binding *b = p->bindings+index;
|
|
p->numBindings++;
|
|
b->command = command;
|
|
b->controlIndex = controlIndex;
|
|
b->turbo = turbo;
|
|
b->sensitivity = sensitivity;
|
|
b->deadZone = deadZone;
|
|
// Where it appears in listview.
|
|
//int count = ListBoundCommand(port, slot, dev, b);
|
|
|
|
int newBindingIndex = index;
|
|
index = 0;
|
|
while (index < p->numBindings) {
|
|
if (index == newBindingIndex) {
|
|
index ++;
|
|
continue;
|
|
}
|
|
b = p->bindings + index;
|
|
int nuke = 0;
|
|
if (config.multipleBinding) {
|
|
if (b->controlIndex == controlIndex && b->command == command)
|
|
nuke = 1;
|
|
}
|
|
else {
|
|
int uid2 = dev->virtualControls[b->controlIndex].uid;
|
|
if (b->controlIndex == controlIndex || (!((uid2^uid) & 0xFFFFFF) && ((uid|uid2) & (UID_POV | UID_AXIS))))
|
|
nuke = 1;
|
|
}
|
|
if (!nuke) {
|
|
index++;
|
|
continue;
|
|
}
|
|
if (index < newBindingIndex) {
|
|
newBindingIndex--;
|
|
//count --;
|
|
}
|
|
DeleteBinding(port, slot, dev, b);
|
|
}
|
|
if (!config.multipleBinding) {
|
|
for (int port2=0; port2<2; port2++) {
|
|
for (int slot2=0; slot2<4; slot2++) {
|
|
if (port2==(int)port && slot2 == (int)slot) continue;
|
|
PadBindings *p = dev->pads[port2]+slot2;
|
|
for (int i=0; i < p->numBindings; i++) {
|
|
Binding *b = p->bindings+i;
|
|
int uid2 = dev->virtualControls[b->controlIndex].uid;
|
|
if (b->controlIndex == controlIndex || (!((uid2^uid) & 0xFFFFFF) && ((uid|uid2) & (UID_POV | UID_AXIS)))) {
|
|
DeleteBinding(port2, slot2, dev, b);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//return count;
|
|
return 0;
|
|
}
|
|
|
|
// Ties together config data structure, config files, and general config
|
|
// dialog.
|
|
const GeneralSettingsBool BoolOptionsInfo[] = {
|
|
{L"Force Cursor Hide", 0 /*IDC_FORCE_HIDE*/, 0},
|
|
{L"Mouse Unfocus", 0 /*IDC_MOUSE_UNFOCUS*/, 1},
|
|
{L"Background", 0 /*IDC_BACKGROUND*/, 1},
|
|
{L"Multiple Bindings", 0 /*IDC_MULTIPLE_BINDING*/, 0},
|
|
|
|
{L"DirectInput Game Devices", 0 /*IDC_G_DI*/, 1},
|
|
{L"XInput", 0 /*IDC_G_XI*/, 1},
|
|
{L"DualShock 3", 0 /*IDC_G_DS3*/, 0},
|
|
|
|
{L"Multitap 1", 0 /*IDC_MULTITAP1*/, 0},
|
|
{L"Multitap 2", 0 /*IDC_MULTITAP2*/, 0},
|
|
|
|
{L"Escape Fullscreen Hack", 0 /*IDC_ESCAPE_FULLSCREEN_HACK*/, 1},
|
|
{L"Disable Screen Saver", 0 /*IDC_DISABLE_SCREENSAVER*/, 1},
|
|
{L"Logging", 0 /*IDC_DEBUG_FILE*/, 0},
|
|
|
|
{L"Save State in Title", 0 /*IDC_SAVE_STATE_TITLE*/, 0}, //No longer required, PCSX2 now handles it - avih 2011-05-17
|
|
{L"GH2", 0 /*IDC_GH2_HACK*/, 0},
|
|
{L"Turbo Key Hack", 0 /*IDC_TURBO_KEY_HACK*/, 0},
|
|
};
|
|
|
|
void CALLBACK PADsetSettingsDir( const char *dir )
|
|
{
|
|
CfgHelper::SetSettingsDir(dir);
|
|
}
|
|
|
|
int SaveSettings(wchar_t *file=0) {
|
|
CfgHelper cfg;
|
|
|
|
for (size_t i=0; i<sizeof(BoolOptionsInfo)/sizeof(BoolOptionsInfo[0]); i++) {
|
|
cfg.WriteBool(L"General Settings", BoolOptionsInfo[i].name, config.bools[i]);
|
|
}
|
|
cfg.WriteInt(L"General Settings", L"Close Hacks", config.closeHacks);
|
|
|
|
cfg.WriteInt(L"General Settings", L"Keyboard Mode", config.keyboardApi);
|
|
cfg.WriteInt(L"General Settings", L"Mouse Mode", config.mouseApi);
|
|
|
|
for (int port=0; port<2; port++) {
|
|
for (int slot=0; slot<4; slot++) {
|
|
wchar_t temp[50];
|
|
wsprintf(temp, L"Pad %i %i", port, slot);
|
|
cfg.WriteInt(temp, L"Mode", config.padConfigs[port][slot].type);
|
|
cfg.WriteInt(temp, L"Auto Analog", config.padConfigs[port][slot].autoAnalog);
|
|
}
|
|
}
|
|
|
|
if (!dm)
|
|
return 0;
|
|
|
|
for (int i=0; i<dm->numDevices; i++) {
|
|
wchar_t id[50];
|
|
wchar_t temp[50], temp2[1000];
|
|
wsprintfW(id, L"Device %i", i);
|
|
Device *dev = dm->devices[i];
|
|
wchar_t *name = dev->displayName;
|
|
while (name[0] == '[') {
|
|
wchar_t *name2 = wcschr(name, ']');
|
|
if (!name2) break;
|
|
name = name2+1;
|
|
while (iswspace(name[0])) name++;
|
|
}
|
|
|
|
cfg.WriteStr(id, L"Display Name", name);
|
|
cfg.WriteStr(id, L"Instance ID", dev->instanceID);
|
|
if (dev->productID) {
|
|
cfg.WriteStr(id, L"Product ID", dev->productID);
|
|
}
|
|
cfg.WriteInt(id, L"API", dev->api);
|
|
cfg.WriteInt(id, L"Type", dev->type);
|
|
int ffBindingCount = 0;
|
|
int bindingCount = 0;
|
|
for (int port=0; port<2; port++) {
|
|
for (int slot=0; slot<4; slot++) {
|
|
for (int j=0; j<dev->pads[port][slot].numBindings; j++) {
|
|
Binding *b = dev->pads[port][slot].bindings+j;
|
|
VirtualControl *c = &dev->virtualControls[b->controlIndex];
|
|
wsprintfW(temp, L"Binding %i", bindingCount++);
|
|
wsprintfW(temp2, L"0x%08X, %i, %i, %i, %i, %i, %i", c->uid, port, b->command, b->sensitivity, b->turbo, slot, b->deadZone);
|
|
cfg.WriteStr(id, temp, temp2);
|
|
}
|
|
|
|
for (int j=0; j<dev->pads[port][slot].numFFBindings; j++) {
|
|
ForceFeedbackBinding *b = dev->pads[port][slot].ffBindings+j;
|
|
ForceFeedbackEffectType *eff = &dev->ffEffectTypes[b->effectIndex];
|
|
wsprintfW(temp, L"FF Binding %i", ffBindingCount++);
|
|
wsprintfW(temp2, L"%s %i, %i, %i", eff->effectID, port, b->motor, slot);
|
|
for (int k=0; k<dev->numFFAxes; k++) {
|
|
ForceFeedbackAxis *axis = dev->ffAxes + k;
|
|
AxisEffectInfo *info = b->axes + k;
|
|
//wsprintfW(wcschr(temp2,0), L", %i, %i", axis->id, info->force);
|
|
// Not secure because I'm too lazy to compute the remaining size
|
|
wprintf(wcschr(temp2, 0), L", %i, %i", axis->id, info->force);
|
|
}
|
|
cfg.WriteStr(id, temp, temp2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int LoadSettings(int force, wchar_t *file) {
|
|
if (dm && !force) return 0;
|
|
|
|
// Could just do ClearDevices() instead, but if I ever add any extra stuff,
|
|
// this will still work.
|
|
UnloadConfigs();
|
|
dm = new InputDeviceManager();
|
|
|
|
CfgHelper cfg;
|
|
|
|
for (size_t i=0; i<sizeof(BoolOptionsInfo)/sizeof(BoolOptionsInfo[0]); i++) {
|
|
config.bools[i] = cfg.ReadBool(L"General Settings", BoolOptionsInfo[i].name, BoolOptionsInfo[i].defaultValue);
|
|
}
|
|
|
|
|
|
config.closeHacks = (u8)cfg.ReadInt(L"General Settings", L"Close Hacks");
|
|
if (config.closeHacks&1) config.closeHacks &= ~2;
|
|
|
|
config.keyboardApi = (DeviceAPI)cfg.ReadInt(L"General Settings", L"Keyboard Mode", LNX_KEYBOARD);
|
|
if (!config.keyboardApi) config.keyboardApi = LNX_KEYBOARD;
|
|
config.mouseApi = (DeviceAPI) cfg.ReadInt(L"General Settings", L"Mouse Mode");
|
|
|
|
for (int port=0; port<2; port++) {
|
|
for (int slot=0; slot<4; slot++) {
|
|
wchar_t temp[50];
|
|
wsprintf(temp, L"Pad %i %i", port, slot);
|
|
config.padConfigs[port][slot].type = (PadType) cfg.ReadInt(temp, L"Mode", Dualshock2Pad);
|
|
config.padConfigs[port][slot].autoAnalog = cfg.ReadBool(temp, L"Auto Analog");
|
|
}
|
|
}
|
|
|
|
int i=0;
|
|
int multipleBinding = config.multipleBinding;
|
|
// Disabling multiple binding only prevents new multiple bindings.
|
|
config.multipleBinding = 1;
|
|
while (1) {
|
|
wchar_t id[50];
|
|
wchar_t temp[50], temp2[1000], temp3[1000], temp4[1000];
|
|
wsprintfW(id, L"Device %i", i++);
|
|
if (!cfg.ReadStr(id, L"Display Name", temp2) || !temp2[0] ||
|
|
!cfg.ReadStr(id, L"Instance ID", temp3) || !temp3[0]) {
|
|
if (i >= 100) break;
|
|
continue;
|
|
}
|
|
wchar_t *id2 = 0;
|
|
if (cfg.ReadStr(id, L"Product ID", temp4) && temp4[0])
|
|
id2 = temp4;
|
|
|
|
int api = cfg.ReadInt(id, L"API");
|
|
int type = cfg.ReadInt(id, L"Type");
|
|
if (!api || !type) continue;
|
|
|
|
Device *dev = new Device((DeviceAPI)api, (DeviceType)type, temp2, temp3, id2);
|
|
dev->attached = 0;
|
|
dm->AddDevice(dev);
|
|
int j = 0;
|
|
int last = 0;
|
|
while (1) {
|
|
wsprintfW(temp, L"Binding %i", j++);
|
|
if (!cfg.ReadStr(id, temp, temp2)) {
|
|
if (j >= 100) {
|
|
if (!last) break;
|
|
last = 0;
|
|
}
|
|
continue;
|
|
}
|
|
last = 1;
|
|
unsigned int uid;
|
|
int port, command, sensitivity, turbo, slot = 0, deadZone = 0;
|
|
int w = 0;
|
|
char string[1000];
|
|
while (temp2[w]) {
|
|
string[w] = (char)temp2[w];
|
|
w++;
|
|
}
|
|
string[w] = 0;
|
|
int len = sscanf(string, " %i , %i , %i , %i , %i , %i , %i", &uid, &port, &command, &sensitivity, &turbo, &slot, &deadZone);
|
|
if (len >= 5 && type) {
|
|
VirtualControl *c = dev->GetVirtualControl(uid);
|
|
if (!c) c = dev->AddVirtualControl(uid, -1);
|
|
if (c) {
|
|
BindCommand(dev, uid, port, slot, command, sensitivity, turbo, deadZone);
|
|
}
|
|
}
|
|
}
|
|
j = 0;
|
|
while (1) {
|
|
wsprintfW(temp, L"FF Binding %i", j++);
|
|
if (!cfg.ReadStr(id, temp, temp2)) {
|
|
if (j >= 10) {
|
|
if (!last) break;
|
|
last = 0;
|
|
}
|
|
continue;
|
|
}
|
|
last = 1;
|
|
int port, slot, motor;
|
|
int w = 0;
|
|
char string[1000];
|
|
char effect[1000];
|
|
while (temp2[w]) {
|
|
string[w] = (char)temp2[w];
|
|
w++;
|
|
}
|
|
string[w] = 0;
|
|
// wcstok not in ntdll. More effore than its worth to shave off
|
|
// whitespace without it.
|
|
if (sscanf(string, " %s %i , %i , %i", effect, &port, &motor, &slot) == 4) {
|
|
char *s = strchr(strchr(strchr(string, ',')+1, ',')+1, ',');
|
|
if (!s) continue;
|
|
s++;
|
|
w = 0;
|
|
while (effect[w]) {
|
|
temp2[w] = effect[w];
|
|
w++;
|
|
}
|
|
temp2[w] = 0;
|
|
ForceFeedbackEffectType *eff = dev->GetForcefeedbackEffect(temp2);
|
|
if (!eff) {
|
|
// At the moment, don't record effect types.
|
|
// Only used internally, anyways, so not an issue.
|
|
dev->AddFFEffectType(temp2, temp2, EFFECT_CONSTANT);
|
|
// eff = &dev->ffEffectTypes[dev->numFFEffectTypes-1];
|
|
}
|
|
#if 0
|
|
ForceFeedbackBinding *b;
|
|
CreateEffectBinding(dev, temp2, port, slot, motor, &b);
|
|
if (b) {
|
|
while (1) {
|
|
int axisID = atoi(s);
|
|
if (!(s = strchr(s, ','))) break;
|
|
s++;
|
|
int force = atoi(s);
|
|
int i;
|
|
for (i=0; i<dev->numFFAxes; i++) {
|
|
if (axisID == dev->ffAxes[i].id) break;
|
|
}
|
|
if (i == dev->numFFAxes) {
|
|
dev->AddFFAxis(L"?", axisID);
|
|
}
|
|
b->axes[i].force = force;
|
|
if (!(s = strchr(s, ','))) break;
|
|
s++;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
config.multipleBinding = multipleBinding;
|
|
|
|
//TODO RefreshEnabledDevicesAndDisplay(1);
|
|
RefreshEnabledDevices(1); // XXX For the moment only a subfonction
|
|
|
|
return 0;
|
|
}
|
|
|
|
void UnloadConfigs() {
|
|
if (dm) {
|
|
delete dm;
|
|
dm = 0;
|
|
}
|
|
}
|
|
|
|
void RefreshEnabledDevices(int updateDeviceList) {
|
|
// Clears all device state.
|
|
static int lastXInputState = -1;
|
|
if (updateDeviceList || lastXInputState != config.gameApis.xInput) {
|
|
EnumDevices(config.gameApis.xInput);
|
|
lastXInputState = config.gameApis.xInput;
|
|
}
|
|
|
|
for (int i=0; i<dm->numDevices; i++) {
|
|
Device *dev = dm->devices[i];
|
|
|
|
// XXX windows magic?
|
|
if (!dev->attached && dev->displayName[0] != '[') {
|
|
wchar_t *newName = (wchar_t*) malloc(sizeof(wchar_t) * (wcslen(dev->displayName) + 12));
|
|
wsprintfW(newName, L"[Detached] %s", dev->displayName);
|
|
free(dev->displayName);
|
|
dev->displayName = newName;
|
|
}
|
|
|
|
dm->EnableDevice(i);
|
|
#if 0 // windows magic?
|
|
if ((dev->type == KEYBOARD && dev->api == IGNORE_KEYBOARD) ||
|
|
(dev->type == KEYBOARD && dev->api == config.keyboardApi) ||
|
|
(dev->type == MOUSE && dev->api == config.mouseApi) ||
|
|
(dev->type == OTHER &&
|
|
((dev->api == DI && config.gameApis.directInput) ||
|
|
(dev->api == DS3 && config.gameApis.dualShock3) ||
|
|
(dev->api == XINPUT && config.gameApis.xInput)))) {
|
|
dm->EnableDevice(i);
|
|
if (config.gameApis.dualShock3 && dev->api == DI && dev->displayName &&
|
|
!wcsicmp(dev->displayName, L"DX PLAYSTATION(R)3 Controller")) {
|
|
dm->DisableDevice(i);
|
|
}
|
|
else {
|
|
dm->EnableDevice(i);
|
|
}
|
|
}
|
|
else {
|
|
dm->DisableDevice(i);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void Configure() {
|
|
// Can end up here without PADinit() being called first.
|
|
LoadSettings();
|
|
// Can also end up here after running emulator a bit, and possibly
|
|
// disabling some devices due to focus changes, or releasing mouse.
|
|
RefreshEnabledDevices(0);
|
|
}
|