/* LilyPad - Pad plugin for PS2 Emulator * Copyright (C) 2002-2017 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 . */ #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, int padtype, Device *dev, Binding *b) { fprintf(stderr, "delete binding %d:%d\n", port, slot); Binding *bindings = dev->pads[port][slot][padtype].bindings; int i = b - bindings; memmove(bindings + i, bindings + i + 1, sizeof(Binding) * (dev->pads[port][slot][padtype].numBindings - i - 1)); dev->pads[port][slot][padtype].numBindings--; } void DeleteBinding(int port, int slot, Device *dev, ForceFeedbackBinding *b) { int padtype = config.padConfigs[port][slot].type; ForceFeedbackBinding *bindings = dev->pads[port][slot][padtype].ffBindings; int i = b - bindings; memmove(bindings + i, bindings + i + 1, sizeof(Binding) * (dev->pads[port][slot][padtype].numFFBindings - i - 1)); dev->pads[port][slot][padtype].numFFBindings--; } int BindCommand(Device *dev, unsigned int uid, unsigned int port, unsigned int slot, unsigned int padtype, int command, int sensitivity, int rapidFire, int deadZone) { // Checks needed because I use this directly when loading bindings. if (port > 1 || slot > 3 || padtype >= numPadTypes) 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] + padtype; 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->rapidFire = rapidFire; 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, padtype, 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; for (int padtype2 = 0; padtype2 < numPadTypes; padtype2++) { PadBindings *p = dev->pads[port2][slot2] + padtype2; 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, padtype2, 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}, }; 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 Hack", config.closeHack); 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 padtype = 0; padtype < numPadTypes; padtype++) { for (int j = 0; j < dev->pads[port][slot][padtype].numBindings; j++) { Binding *b = dev->pads[port][slot][padtype].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, %i", c->uid, port, b->command, b->sensitivity, b->rapidFire, slot, b->deadZone, padtype); cfg.WriteStr(id, temp, temp2); } for (int j = 0; j < dev->pads[port][slot][padtype].numFFBindings; j++) { ForceFeedbackBinding *b = dev->pads[port][slot][padtype].ffBindings + j; ForceFeedbackEffectType *eff = &dev->ffEffectTypes[b->effectIndex]; wsprintfW(temp, L"FF Binding %i", ffBindingCount++); wsprintfW(temp2, L"%s %i, %i, %i, %i", eff->effectID, port, b->motor, slot, padtype); 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.closeHack = (u8)cfg.ReadInt(L"General Settings", L"Close Hack"); 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"); } } bool oldIni = false; 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, rapidFire, slot = 0, deadZone = 0, padtype = 0; int w = 0; char string[1000]; while (temp2[w]) { string[w] = (char)temp2[w]; w++; } string[w] = 0; int len = sscanf(string, " %u , %i , %i , %i , %i , %i , %i , %i", &uid, &port, &command, &sensitivity, &rapidFire, &slot, &deadZone, &padtype); if (len >= 5 && type) { VirtualControl *c = dev->GetVirtualControl(uid); if (!c) c = dev->AddVirtualControl(uid, -1); if (c) { if (len < 8) { // If ini file is imported from older version, make sure bindings aren't applied to "Unplugged" padtype. oldIni = true; if (config.padConfigs[port][slot].type != 0) { padtype = config.padConfigs[port][slot].type; } else { padtype = 1; } } BindCommand(dev, uid, port, slot, padtype, command, sensitivity, rapidFire, 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, padtype; 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, " %20s %i , %i , %i , %i", effect, &port, &motor, &slot, &padtype) == 5) { char *s; if (oldIni) { // Make sure bindings aren't applied to "Unplugged" padtype and FF settings are read from old location. if (config.padConfigs[port][slot].type != 0) { padtype = config.padConfigs[port][slot].type; } else { padtype = 1; } s = strchr(strchr(strchr(string, ',') + 1, ',') + 1, ','); } else { s = strchr(strchr(strchr(strchr(string, ',') + 1, ',') + 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; inumFFAxes; 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); }