// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include <cmath> #include <memory> #include <SDL.h> #include <SDL_gamecontroller.h> #include "common/assert.h" #include "common/logging/log.h" #include "common/string_util.h" #include "input_core/devices/sdl_gamepad.h" #include "input_core/devices/gamecontrollerdb.h" bool SDLGamepad::SDLInitialized = false; SDLGamepad::SDLGamepad() { } SDLGamepad::SDLGamepad(int number_, _SDL_GameController* gamepad_) : number(number_), gamepad(gamepad_) { } SDLGamepad::~SDLGamepad() { CloseDevice(); } bool SDLGamepad::InitDevice(int number, const std::map<std::string, std::vector<Service::HID::PadState>>& keyMap) { if (!SDLGamepad::SDLInitialized && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_GAMECONTROLLER) failed"); return false; } SDL_GameControllerEventState(SDL_IGNORE); SDLGamepad::SDLInitialized = true; LoadGameControllerDB(); if (SDL_IsGameController(number)) { gamepad = SDL_GameControllerOpen(number); if (gamepad == nullptr) { LOG_INFO(Input, "Controller found but unable to open connection."); return false; } } key_mapping = keyMap; for (const auto& entry : key_mapping) { keys_pressed[entry.first] = false; } return true; } void SDLGamepad::ProcessInput() { if (gamepad == nullptr) return; SDL_GameControllerUpdate(); for (const auto& entry : key_mapping) { SDL_GameControllerButton button = SDL_GameControllerGetButtonFromString(gamepadinput_to_sdlname_mapping[static_cast<GamepadInputs>(stoi(entry.first))].c_str()); if (button != SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_INVALID) { Uint8 pressed = SDL_GameControllerGetButton(gamepad, button); if (pressed == 1 && keys_pressed[entry.first] == false) { for (const auto& padstate : entry.second) { KeyMap::PressKey(padstate, 1.0); keys_pressed[entry.first] = true; } } else if (pressed == 0 && keys_pressed[entry.first] == true) { for (const auto& padstate : entry.second) { KeyMap::ReleaseKey(padstate); keys_pressed[entry.first] = false; } } } else { // Try axis if button isn't valid SDL_GameControllerAxis axis = SDL_GameControllerGetAxisFromString(gamepadinput_to_sdlname_mapping[static_cast<GamepadInputs>(stoi(entry.first))].c_str()); if (axis != SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_INVALID) { Sint16 value = SDL_GameControllerGetAxis(gamepad, axis); for (const auto& padstate : entry.second) { // TODO: calculate deadzone by radial field rather than axial field. (sqrt(x^2 + y^2) > deadzone) // dont process if in deadzone. Replace later with settings for deadzone. if (abs(value) < 0.2 * 32767.0) KeyMap::ReleaseKey(padstate); else KeyMap::PressKey(padstate, (float)value / 32767.0); } } } } } bool SDLGamepad::CloseDevice() { if (gamepad != nullptr) { SDL_GameControllerClose(gamepad); } return true; } std::vector<std::shared_ptr<IDevice>> SDLGamepad::GetAllDevices() { std::vector<std::shared_ptr<IDevice>> devices; if (!SDLGamepad::SDLInitialized && SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_GAMECONTROLLER) failed"); return devices; } LoadGameControllerDB(); SDL_GameControllerEventState(SDL_IGNORE); for (int i = 0; i < 8; i++) { SDL_GameController* gamecontroller; if (SDL_IsGameController(i)) { gamecontroller = SDL_GameControllerOpen(i); if (gamecontroller != nullptr) { devices.push_back(std::make_shared<SDLGamepad>(i, gamecontroller)); } } } return devices; } void SDLGamepad::LoadGameControllerDB() { std::vector<std::string> lines1,lines2,lines3,lines4; Common::SplitString(SDLGameControllerDB::db_file1, '\n', lines1); Common::SplitString(SDLGameControllerDB::db_file2, '\n', lines2); Common::SplitString(SDLGameControllerDB::db_file3, '\n', lines3); Common::SplitString(SDLGameControllerDB::db_file4, '\n', lines4); lines1.insert(lines1.end(), lines2.begin(), lines2.end()); lines1.insert(lines1.end(), lines3.begin(), lines3.end()); lines1.insert(lines1.end(), lines4.begin(), lines4.end()); for (std::string s : lines1) { SDL_GameControllerAddMapping(s.c_str()); } } Settings::InputDeviceMapping SDLGamepad::GetInput() { if (gamepad == nullptr) return Settings::InputDeviceMapping(""); SDL_GameControllerUpdate(); for (int i = 0; i < SDL_GameControllerButton::SDL_CONTROLLER_BUTTON_MAX; i++) { Uint8 pressed = SDL_GameControllerGetButton(gamepad, SDL_GameControllerButton(i)); if (pressed == 0) continue; auto buttonName = SDL_GameControllerGetStringForButton(SDL_GameControllerButton(i)); for (const auto& mapping : gamepadinput_to_sdlname_mapping) { if (mapping.second == buttonName) { return Settings::InputDeviceMapping("SDL/" + std::to_string(number) + "/" + "Gamepad/" + std::to_string(static_cast<int>(mapping.first))); } } } for (int i = 0; i < SDL_GameControllerAxis::SDL_CONTROLLER_AXIS_MAX; i++) { Sint16 value = SDL_GameControllerGetAxis(gamepad, SDL_GameControllerAxis(i)); // TODO: calculate deadzone by radial field rather than axial field. (sqrt(x^2 + y^2) > deadzone) // dont process if in deadzone. Replace later with settings for deadzone. if (abs(value) < 0.2 * 32767.0) continue; std::string modifier; if (value > 0) modifier = "+"; else modifier = "-"; std::string axisName = SDL_GameControllerGetStringForAxis(SDL_GameControllerAxis(i)); for (const auto& mapping : gamepadinput_to_sdlname_mapping) { if (mapping.second == axisName) { if ((mapping.first == GamepadInputs::LeftXMinus || mapping.first == GamepadInputs::LeftYMinus || mapping.first == GamepadInputs::RightXMinus || mapping.first == GamepadInputs::RightYMinus) && modifier == "+") { continue; } else if ((mapping.first == GamepadInputs::LeftXPlus || mapping.first == GamepadInputs::LeftYPlus || mapping.first == GamepadInputs::RightXPlus || mapping.first == GamepadInputs::RightYPlus) && modifier == "-") { continue; } return Settings::InputDeviceMapping("SDL/" + std::to_string(this->number) + "/" + "Gamepad/" + std::to_string(static_cast<int>(mapping.first))); } } } return Settings::InputDeviceMapping(""); } void SDLGamepad::Clear() { }