mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-02-14 14:20:24 -05:00
![David Chavez](/assets/img/avatar_default.png)
* Disable game input when typing into a textfield * Only block keyboard input when typing in textfield * Address PR comments * Fix spacing * Generalize check on input device Co-authored-by: Garrett Cox <garrettjcox@gmail.com>
288 lines
10 KiB
C++
288 lines
10 KiB
C++
#include "ControlDeck.h"
|
|
|
|
#include "Window.h"
|
|
#include "Controller.h"
|
|
#include "DummyController.h"
|
|
#include <Utils/StringHelper.h>
|
|
#include "Cvar.h"
|
|
#include "Lib/ImGui/imgui.h"
|
|
|
|
#ifndef __WIIU__
|
|
#include "KeyboardController.h"
|
|
#include "SDLController.h"
|
|
#else
|
|
#include "WiiUGamepad.h"
|
|
#include "WiiUController.h"
|
|
#endif
|
|
|
|
namespace Ship {
|
|
|
|
void ControlDeck::Init(uint8_t* bits) {
|
|
ScanPhysicalDevices();
|
|
controllerBits = bits;
|
|
}
|
|
|
|
void ControlDeck::ScanPhysicalDevices() {
|
|
|
|
virtualDevices.clear();
|
|
physicalDevices.clear();
|
|
|
|
#ifndef __WIIU__
|
|
for (int32_t i = 0; i < SDL_NumJoysticks(); i++) {
|
|
if (SDL_IsGameController(i)) {
|
|
auto sdl = std::make_shared<SDLController>(i);
|
|
sdl->Open();
|
|
physicalDevices.push_back(sdl);
|
|
}
|
|
}
|
|
|
|
physicalDevices.push_back(std::make_shared<DummyController>("Auto", "Auto", true));
|
|
physicalDevices.push_back(std::make_shared<KeyboardController>());
|
|
#else
|
|
physicalDevices.push_back(std::make_shared<DummyController>("Auto", "Auto", true));
|
|
|
|
auto gamepad = std::make_shared<Ship::WiiUGamepad>();
|
|
gamepad->Open();
|
|
physicalDevices.push_back(gamepad);
|
|
|
|
for (int32_t i = 0; i < 4; i++) {
|
|
auto controller = std::make_shared<Ship::WiiUController>((WPADChan) i);
|
|
controller->Open();
|
|
physicalDevices.push_back(controller);
|
|
}
|
|
#endif
|
|
|
|
physicalDevices.push_back(std::make_shared<DummyController>("Disconnected", "None", false));
|
|
|
|
for (const auto device : physicalDevices) {
|
|
for (int32_t i = 0; i < MAXCONTROLLERS; i++) {
|
|
device->CreateDefaultBinding(i);
|
|
}
|
|
}
|
|
|
|
for (int32_t i = 0; i < MAXCONTROLLERS; i++) {
|
|
virtualDevices.push_back(i == 0 ? 0 : static_cast<int>(physicalDevices.size()) - 1);
|
|
}
|
|
|
|
LoadControllerSettings();
|
|
}
|
|
|
|
void ControlDeck::SetPhysicalDevice(int32_t slot, int32_t deviceSlot) {
|
|
const std::shared_ptr<Controller> backend = physicalDevices[deviceSlot];
|
|
virtualDevices[slot] = deviceSlot;
|
|
*controllerBits |= (backend->Connected()) << slot;
|
|
}
|
|
|
|
void ControlDeck::WriteToPad(OSContPad* pad) const {
|
|
for (size_t i = 0; i < virtualDevices.size(); i++) {
|
|
const std::shared_ptr<Controller> backend = physicalDevices[virtualDevices[i]];
|
|
|
|
// If the controller backend is "Auto" we need to get the real device
|
|
// we search for the real device to read input from it
|
|
if (backend->GetGuid() == "Auto") {
|
|
for (const auto& device : physicalDevices) {
|
|
if(ShouldBlockGameInput(device->GetGuid())) {
|
|
device->Read(nullptr, i);
|
|
continue;
|
|
}
|
|
|
|
device->Read(&pad[i], i);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if(ShouldBlockGameInput(backend->GetGuid())) {
|
|
backend->Read(nullptr, i);
|
|
continue;
|
|
}
|
|
|
|
backend->Read(&pad[i], i);
|
|
}
|
|
}
|
|
|
|
#define NESTED(key, ...) StringHelper::Sprintf("Controllers.%s.Slot_%d." key, device->GetGuid().c_str(), virtualSlot, __VA_ARGS__)
|
|
|
|
void ControlDeck::LoadControllerSettings() {
|
|
std::shared_ptr<Mercury> Config = Window::GetInstance()->GetConfig();
|
|
|
|
for (auto const& val : Config->rjson["Controllers"]["Deck"].items()) {
|
|
int32_t slot = std::stoi(val.key().substr(5));
|
|
|
|
for (size_t dev = 0; dev < physicalDevices.size(); dev++) {
|
|
std::string guid = physicalDevices[dev]->GetGuid();
|
|
if (guid != val.value()) continue;
|
|
|
|
virtualDevices[slot] = dev;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < virtualDevices.size(); i++) {
|
|
std::shared_ptr<Controller> backend = physicalDevices[virtualDevices[i]];
|
|
Config->setString(StringHelper::Sprintf("Controllers.Deck.Slot_%d", (int)i), backend->GetGuid());
|
|
}
|
|
|
|
for (const auto device : physicalDevices) {
|
|
|
|
std::string guid = device->GetGuid();
|
|
|
|
for (int32_t virtualSlot = 0; virtualSlot < MAXCONTROLLERS; virtualSlot++) {
|
|
|
|
if (!(Config->rjson["Controllers"].contains(guid) && Config->rjson["Controllers"][guid].contains(StringHelper::Sprintf("Slot_%d", virtualSlot)))) continue;
|
|
|
|
auto profile = device->getProfile(virtualSlot);
|
|
auto rawProfile = Config->rjson["Controllers"][guid][StringHelper::Sprintf("Slot_%d", virtualSlot)];
|
|
|
|
profile->Mappings.clear();
|
|
profile->AxisDeadzones.clear();
|
|
profile->AxisDeadzones.clear();
|
|
profile->GyroData.clear();
|
|
|
|
profile->Version = Config->getInt(NESTED("Version", ""), DEVICE_PROFILE_VERSION_V0);
|
|
|
|
switch (profile->Version) {
|
|
|
|
case DEVICE_PROFILE_VERSION_V0:
|
|
|
|
// Load up defaults for the things we can't load.
|
|
device->CreateDefaultBinding(virtualSlot);
|
|
|
|
profile->UseRumble = Config->getBool(NESTED("Rumble.Enabled", ""));
|
|
profile->RumbleStrength = Config->getFloat(NESTED("Rumble.Strength", ""));
|
|
profile->UseGyro = Config->getBool(NESTED("Gyro.Enabled", ""));
|
|
|
|
for (auto const& val : rawProfile["Mappings"].items()) {
|
|
device->SetButtonMapping(virtualSlot, std::stoi(val.key().substr(4)), val.value());
|
|
}
|
|
|
|
break;
|
|
|
|
case DEVICE_PROFILE_VERSION_V1:
|
|
profile->UseRumble = Config->getBool(NESTED("Rumble.Enabled", ""));
|
|
profile->RumbleStrength = Config->getFloat(NESTED("Rumble.Strength", ""));
|
|
profile->UseGyro = Config->getBool(NESTED("Gyro.Enabled", ""));
|
|
|
|
for (auto const& val : rawProfile["AxisDeadzones"].items()) {
|
|
profile->AxisDeadzones[std::stoi(val.key())] = val.value();
|
|
}
|
|
|
|
for (auto const& val : rawProfile["AxisMinimumPress"].items()) {
|
|
profile->AxisMinimumPress[std::stoi(val.key())] = val.value();
|
|
}
|
|
|
|
for (auto const& val : rawProfile["GyroData"].items()) {
|
|
profile->GyroData[std::stoi(val.key())] = val.value();
|
|
}
|
|
|
|
for (auto const& val : rawProfile["Mappings"].items()) {
|
|
device->SetButtonMapping(virtualSlot, std::stoi(val.key().substr(4)), val.value());
|
|
}
|
|
|
|
break;
|
|
|
|
// Version is invalid.
|
|
default:
|
|
device->CreateDefaultBinding(virtualSlot);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ControlDeck::SaveControllerSettings() {
|
|
std::shared_ptr<Mercury> Config = Window::GetInstance()->GetConfig();
|
|
|
|
for (size_t i = 0; i < virtualDevices.size(); i++) {
|
|
std::shared_ptr<Controller> backend = physicalDevices[virtualDevices[i]];
|
|
Config->setString(StringHelper::Sprintf("Controllers.Deck.Slot_%d", (int)i), backend->GetGuid());
|
|
}
|
|
|
|
for (const auto& device : physicalDevices) {
|
|
|
|
int32_t virtualSlot = 0;
|
|
std::string guid = device->GetGuid();
|
|
|
|
for (int32_t virtualSlot = 0; virtualSlot < MAXCONTROLLERS; virtualSlot++) {
|
|
auto profile = device->getProfile(virtualSlot);
|
|
|
|
if (!device->Connected()) continue;
|
|
|
|
auto rawProfile = Config->rjson["Controllers"][guid][StringHelper::Sprintf("Slot_%d", virtualSlot)];
|
|
Config->setInt(NESTED("Version", ""), profile->Version);
|
|
Config->setBool(NESTED("Rumble.Enabled", ""), profile->UseRumble);
|
|
Config->setFloat(NESTED("Rumble.Strength", ""), profile->RumbleStrength);
|
|
Config->setBool(NESTED("Gyro.Enabled", ""), profile->UseGyro);
|
|
|
|
for (auto const& val : rawProfile["Mappings"].items()) {
|
|
Config->setInt(NESTED("Mappings.%s", val.key().c_str()), -1);
|
|
}
|
|
|
|
for (auto const& [key, val] : profile->AxisDeadzones) {
|
|
Config->setFloat(NESTED("AxisDeadzones.%d", key), val);
|
|
}
|
|
|
|
for (auto const& [key, val] : profile->AxisMinimumPress) {
|
|
Config->setFloat(NESTED("AxisMinimumPress.%d", key), val);
|
|
}
|
|
|
|
for (auto const& [key, val] : profile->GyroData) {
|
|
Config->setFloat(NESTED("GyroData.%d", key), val);
|
|
}
|
|
|
|
for (auto const& [key, val] : profile->Mappings) {
|
|
Config->setInt(NESTED("Mappings.BTN_%d", val), key);
|
|
}
|
|
|
|
virtualSlot++;
|
|
}
|
|
}
|
|
|
|
Config->save();
|
|
}
|
|
|
|
std::shared_ptr<Controller> ControlDeck::GetPhysicalDevice(int32_t deviceSlot) {
|
|
return physicalDevices[deviceSlot];
|
|
}
|
|
|
|
size_t ControlDeck::GetNumPhysicalDevices() {
|
|
return physicalDevices.size();
|
|
}
|
|
|
|
int32_t ControlDeck::GetVirtualDevice(int32_t slot) {
|
|
return virtualDevices[slot];
|
|
}
|
|
|
|
size_t ControlDeck::GetNumVirtualDevices() {
|
|
return virtualDevices.size();
|
|
}
|
|
|
|
std::shared_ptr<Controller> ControlDeck::GetPhysicalDeviceFromVirtualSlot(int32_t slot) {
|
|
return GetPhysicalDevice(GetVirtualDevice(slot));
|
|
}
|
|
|
|
uint8_t* ControlDeck::GetControllerBits() {
|
|
return controllerBits;
|
|
}
|
|
|
|
void ControlDeck::BlockGameInput() {
|
|
shouldBlockGameInput = true;
|
|
}
|
|
|
|
void ControlDeck::UnblockGameInput() {
|
|
shouldBlockGameInput = false;
|
|
}
|
|
|
|
bool ControlDeck::ShouldBlockGameInput(std::string inputDeviceGuid) const {
|
|
// We block controller input if F1 menu is open and control navigation is on.
|
|
// This is because we don't want controller inputs to affect the game
|
|
bool shouldBlockControllerInput = CVar_GetS32("gOpenMenuBar", 0) && CVar_GetS32("gControlNav", 0);
|
|
|
|
// We block keyboard input if you're currently typing into a textfield.
|
|
// This is because we don't want your keyboard typing to affect the game.
|
|
ImGuiIO io = ImGui::GetIO();
|
|
bool shouldBlockKeyboardInput = io.WantCaptureKeyboard;
|
|
|
|
bool inputDeviceIsKeyboard = inputDeviceGuid == "Keyboard";
|
|
return shouldBlockGameInput || (inputDeviceIsKeyboard ? shouldBlockKeyboardInput : shouldBlockControllerInput);
|
|
}
|
|
}
|