#include "ControlDeck.h" #include "Window.h" #include "Controller.h" #include "DummyController.h" #include #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(i); sdl->Open(); physicalDevices.push_back(sdl); } } physicalDevices.push_back(std::make_shared("Auto", "Auto", true)); physicalDevices.push_back(std::make_shared()); #else physicalDevices.push_back(std::make_shared("Auto", "Auto", true)); auto gamepad = std::make_shared(); gamepad->Open(); physicalDevices.push_back(gamepad); for (int32_t i = 0; i < 4; i++) { auto controller = std::make_shared((WPADChan) i); controller->Open(); physicalDevices.push_back(controller); } #endif physicalDevices.push_back(std::make_shared("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(physicalDevices.size()) - 1); } LoadControllerSettings(); } void ControlDeck::SetPhysicalDevice(int32_t slot, int32_t deviceSlot) { const std::shared_ptr 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 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 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 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 Config = Window::GetInstance()->GetConfig(); for (size_t i = 0; i < virtualDevices.size(); i++) { std::shared_ptr 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 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 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); } }