From f79ead8440107e01c4845315d8532e29497b1068 Mon Sep 17 00:00:00 2001 From: Ingo Ruhnke Date: Wed, 6 Aug 2014 04:29:41 +0200 Subject: [PATCH] Moved joystick specific code from JoystickKeyboardController into JoystickManager class --- src/control/joystick_manager.cpp | 513 +++++++++++++++++++++++++++++ src/control/joystick_manager.hpp | 109 ++++++ src/control/joystickkeyboardcontroller.cpp | 506 +++------------------------- src/control/joystickkeyboardcontroller.hpp | 49 +-- src/supertux/menu/joystick_menu.cpp | 86 +++-- src/supertux/menu/joystick_menu.hpp | 5 +- 6 files changed, 726 insertions(+), 542 deletions(-) create mode 100644 src/control/joystick_manager.cpp create mode 100644 src/control/joystick_manager.hpp diff --git a/src/control/joystick_manager.cpp b/src/control/joystick_manager.cpp new file mode 100644 index 000000000..2c11c96bc --- /dev/null +++ b/src/control/joystick_manager.cpp @@ -0,0 +1,513 @@ +// SuperTux +// Copyright (C) 2013 Ingo Ruhnke +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program. If not, see . + +#include "control/joystick_manager.hpp" + +#include +#include + +#include "control/joystickkeyboardcontroller.hpp" +#include "lisp/list_iterator.hpp" +#include "supertux/menu/joystick_menu.hpp" +#include "supertux/menu/menu_storage.hpp" +#include "util/gettext.hpp" +#include "util/log.hpp" +#include "util/writer.hpp" + +JoystickManager::JoystickManager(JoystickKeyboardController* parent) : + parent(parent), + m_use_game_controller(true), + joy_button_map(), + joy_axis_map(), + joy_hat_map(), + dead_zone(4000), + min_joybuttons(), + max_joybuttons(), + max_joyaxis(), + max_joyhats(), + hat_state(0), + jump_with_up_joy(false), + wait_for_joystick(-1), + joysticks(), + game_controllers() +{ + if (!m_use_game_controller) + { + // Default joystick button configuration + bind_joybutton(0, 0, Controller::JUMP); + bind_joybutton(0, 1, Controller::ACTION); + // 6 or more Buttons + if( min_joybuttons > 5 ){ + bind_joybutton(0, 4, Controller::PEEK_LEFT); + bind_joybutton(0, 5, Controller::PEEK_RIGHT); + // 8 or more + if(min_joybuttons > 7) + bind_joybutton(0, min_joybuttons-1, Controller::PAUSE_MENU); + } else { + // map the last 2 buttons to menu and pause + if(min_joybuttons > 2) + bind_joybutton(0, min_joybuttons-1, Controller::PAUSE_MENU); + // map all remaining joystick buttons to MENU_SELECT + for(int i = 2; i < max_joybuttons; ++i) { + if(i != min_joybuttons-1) + bind_joybutton(0, i, Controller::MENU_SELECT); + } + } + + // Default joystick axis configuration + bind_joyaxis(0, -1, Controller::LEFT); + bind_joyaxis(0, 1, Controller::RIGHT); + bind_joyaxis(0, -2, Controller::UP); + bind_joyaxis(0, 2, Controller::DOWN); + } +} + +JoystickManager::~JoystickManager() +{ + for(auto joy : joysticks) + { + SDL_JoystickClose(joy); + } + + for(auto con : game_controllers) + { + SDL_GameControllerClose(con); + } +} + +void +JoystickManager::on_joystick_added(int joystick_index) +{ + std::cout << "joydeviceadded: " << joystick_index << std::endl; + if (m_use_game_controller) + { + if (!SDL_IsGameController(joystick_index)) + { + log_warning << "joystick is not a game controller, ignoring: " << joystick_index << std::endl; + } + else + { + SDL_GameController* game_controller = SDL_GameControllerOpen(joystick_index); + if (!game_controller) + { + log_warning << "failed to open game_controller: " << joystick_index + << ": " << SDL_GetError() << std::endl; + } + else + { + game_controllers.push_back(game_controller); + } + } + } + else + { + SDL_Joystick* joystick = SDL_JoystickOpen(joystick_index); + if (!joystick) + { + log_warning << "failed to open joystick: " << joystick_index + << ": " << SDL_GetError() << std::endl; + } + else + { + joysticks.push_back(joystick); + } + + if(min_joybuttons < 0 || SDL_JoystickNumButtons(joystick) < min_joybuttons) + min_joybuttons = SDL_JoystickNumButtons(joystick); + + if(SDL_JoystickNumButtons(joystick) > max_joybuttons) + max_joybuttons = SDL_JoystickNumButtons(joystick); + + if(SDL_JoystickNumAxes(joystick) > max_joyaxis) + max_joyaxis = SDL_JoystickNumAxes(joystick); + + if(SDL_JoystickNumHats(joystick) > max_joyhats) + max_joyhats = SDL_JoystickNumHats(joystick); + } +} + +void +JoystickManager::on_joystick_removed(int instance_id) +{ + if (m_use_game_controller) + { + for(auto& controller : game_controllers) + { + SDL_Joystick* joy = SDL_GameControllerGetJoystick(controller); + SDL_JoystickID id = SDL_JoystickInstanceID(joy); + if (id == instance_id) + { + SDL_GameControllerClose(controller); + controller = nullptr; + } + } + + game_controllers.erase(std::remove(game_controllers.begin(), game_controllers.end(), nullptr), + game_controllers.end()); + } + else + { + std::cout << "joydeviceremoved: " << static_cast(instance_id) << std::endl; + for(auto& joy : joysticks) + { + SDL_JoystickID id = SDL_JoystickInstanceID(joy); + if (id == instance_id) + { + SDL_JoystickClose(joy); + joy = nullptr; + } + } + + joysticks.erase(std::remove(joysticks.begin(), joysticks.end(), nullptr), + joysticks.end()); + } +} + +void +JoystickManager::process_hat_event(const SDL_JoyHatEvent& jhat) +{ + Uint8 changed = hat_state ^ jhat.value; + + if (wait_for_joystick >= 0) + { + if (changed & SDL_HAT_UP && jhat.value & SDL_HAT_UP) + bind_joyhat(jhat.which, SDL_HAT_UP, Controller::Control(wait_for_joystick)); + + if (changed & SDL_HAT_DOWN && jhat.value & SDL_HAT_DOWN) + bind_joyhat(jhat.which, SDL_HAT_DOWN, Controller::Control(wait_for_joystick)); + + if (changed & SDL_HAT_LEFT && jhat.value & SDL_HAT_LEFT) + bind_joyhat(jhat.which, SDL_HAT_LEFT, Controller::Control(wait_for_joystick)); + + if (changed & SDL_HAT_RIGHT && jhat.value & SDL_HAT_RIGHT) + bind_joyhat(jhat.which, SDL_HAT_RIGHT, Controller::Control(wait_for_joystick)); + + MenuStorage::get_joystick_options_menu()->update(); + wait_for_joystick = -1; + } + else + { + if (changed & SDL_HAT_UP) + { + HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_UP)); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_UP); + } + + if (changed & SDL_HAT_DOWN) + { + HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_DOWN)); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_DOWN); + } + + if (changed & SDL_HAT_LEFT) + { + HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_LEFT)); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_LEFT); + } + + if (changed & SDL_HAT_RIGHT) + { + HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_RIGHT)); + if (it != joy_hat_map.end()) + set_joy_controls(it->second, jhat.value & SDL_HAT_RIGHT); + } + } + + hat_state = jhat.value; +} + +void +JoystickManager::process_axis_event(const SDL_JoyAxisEvent& jaxis) +{ + if (wait_for_joystick >= 0) + { + if (abs(jaxis.value) > dead_zone) { + if (jaxis.value < 0) + bind_joyaxis(jaxis.which, -(jaxis.axis + 1), Controller::Control(wait_for_joystick)); + else + bind_joyaxis(jaxis.which, jaxis.axis + 1, Controller::Control(wait_for_joystick)); + + MenuStorage::get_joystick_options_menu()->update(); + wait_for_joystick = -1; + } + } + else + { + // Split the axis into left and right, so that both can be + // mapped separately (needed for jump/down vs up/down) + int axis = jaxis.axis + 1; + + AxisMap::iterator left = joy_axis_map.find(std::make_pair(jaxis.which, -axis)); + AxisMap::iterator right = joy_axis_map.find(std::make_pair(jaxis.which, axis)); + + if(left == joy_axis_map.end()) { + // std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl; + } else { + if (jaxis.value < -dead_zone) + set_joy_controls(left->second, true); + else + set_joy_controls(left->second, false); + } + + if(right == joy_axis_map.end()) { + // std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl; + } else { + if (jaxis.value > dead_zone) + set_joy_controls(right->second, true); + else + set_joy_controls(right->second, false); + } + } +} + +void +JoystickManager::process_button_event(const SDL_JoyButtonEvent& jbutton) +{ + if(wait_for_joystick >= 0) + { + if(jbutton.state == SDL_PRESSED) + { + bind_joybutton(jbutton.which, jbutton.button, (Controller::Control)wait_for_joystick); + MenuStorage::get_joystick_options_menu()->update(); + parent->reset(); + wait_for_joystick = -1; + } + } + else + { + ButtonMap::iterator i = joy_button_map.find(std::make_pair(jbutton.which, jbutton.button)); + if(i == joy_button_map.end()) { + log_debug << "Unmapped joybutton " << (int)jbutton.button << " pressed" << std::endl; + } else { + set_joy_controls(i->second, (jbutton.state == SDL_PRESSED)); + } + } +} + + +int +JoystickManager::reversemap_joyaxis(Controller::Control c) +{ + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { + if(i->second == c) + return i->first.second; + } + + return 0; +} + +int +JoystickManager::reversemap_joybutton(Controller::Control c) +{ + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) { + if(i->second == c) + return i->first.second; + } + + return -1; +} + +int +JoystickManager::reversemap_joyhat(Controller::Control c) +{ + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { + if(i->second == c) + return i->first.second; + } + + return -1; +} + +void +JoystickManager::print_joystick_mappings() +{ + std::cout << _("Joystick Mappings") << std::endl; + std::cout << "-----------------" << std::endl; + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { + std::cout << "Axis: " << i->first.second << " -> " << i->second << std::endl; + } + + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) { + std::cout << "Button: " << i->first.second << " -> " << i->second << std::endl; + } + + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { + std::cout << "Hat: " << i->first.second << " -> " << i->second << std::endl; + } + std::cout << std::endl; +} + +void +JoystickManager::unbind_joystick_control(Controller::Control control) +{ + // remove all previous mappings for that control + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); /* no ++i */) { + if(i->second == control) + joy_axis_map.erase(i++); + else + ++i; + } + + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); /* no ++i */) { + if(i->second == control) + joy_button_map.erase(i++); + else + ++i; + } + + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); /* no ++i */) { + if(i->second == control) + joy_hat_map.erase(i++); + else + ++i; + } +} + +void +JoystickManager::bind_joyaxis(JoyId joy_id, int axis, Controller::Control control) +{ + // axis isn't the SDL axis number, but axisnumber + 1 with sign + // changed depending on if the positive or negative end is to be + // used (negative axis 0 becomes -1, positive axis 2 becomes +3, + // etc.) + + unbind_joystick_control(control); + + // add new mapping + joy_axis_map[std::make_pair(joy_id, axis)] = control; +} + +void +JoystickManager::bind_joyhat(JoyId joy_id, int dir, Controller::Control c) +{ + unbind_joystick_control(c); + + // add new mapping + joy_hat_map[std::make_pair(joy_id, dir)] = c; +} + +void +JoystickManager::bind_joybutton(JoyId joy_id, int button, Controller::Control control) +{ + unbind_joystick_control(control); + + // add new mapping + joy_button_map[std::make_pair(joy_id, button)] = control; +} + +void +JoystickManager::read(const lisp::Lisp* joystick_lisp) +{ + joystick_lisp->get("dead-zone", dead_zone); + joystick_lisp->get("jump-with-up", jump_with_up_joy); + lisp::ListIterator iter(joystick_lisp); + while(iter.next()) { + if(iter.item() == _("map")) { + int button = -1; + int axis = 0; + int hat = -1; + std::string control; + const lisp::Lisp* map = iter.lisp(); + + map->get("control", control); + int i = 0; + for(i = 0; Controller::controlNames[i] != 0; ++i) { + if(control == Controller::controlNames[i]) + break; + } + if(Controller::controlNames[i] == 0) { + log_info << "Invalid control '" << control << "' in buttonmap" << std::endl; + continue; + } + + bool js_available = joysticks.size() > 0; + + if (map->get("button", button)) { + if(js_available && (button < 0 || button >= max_joybuttons)) { + log_info << "Invalid button '" << button << "' in buttonmap" << std::endl; + continue; + } + bind_joybutton(0, button, Controller::Control(i)); + } + + if (map->get("axis", axis)) { + if (js_available && (axis == 0 || abs(axis) > max_joyaxis)) { + log_info << "Invalid axis '" << axis << "' in axismap" << std::endl; + continue; + } + bind_joyaxis(0, axis, Controller::Control(i)); + } + + if (map->get("hat", hat)) { + if (js_available && + hat != SDL_HAT_UP && + hat != SDL_HAT_DOWN && + hat != SDL_HAT_LEFT && + hat != SDL_HAT_RIGHT) { + log_info << "Invalid axis '" << axis << "' in axismap" << std::endl; + continue; + } else { + bind_joyhat(0, hat, Controller::Control(i)); + } + } + } + } +} + +void +JoystickManager::write(Writer& writer) +{ + writer.write("dead-zone", dead_zone); + writer.write("jump-with-up", jump_with_up_joy); + + for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); + ++i) { + writer.start_list("map"); + writer.write("button", i->first.second); + writer.write("control", Controller::controlNames[i->second]); + writer.end_list("map"); + } + + for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { + writer.start_list("map"); + writer.write("hat", i->first.second); + writer.write("control", Controller::controlNames[i->second]); + writer.end_list("map"); + } + + for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { + writer.start_list("map"); + writer.write("axis", i->first.second); + writer.write("control", Controller::controlNames[i->second]); + writer.end_list("map"); + } +} + +void +JoystickManager::set_joy_controls(Controller::Control id, bool value) +{ + if (jump_with_up_joy && id == Controller::UP) + { + parent->get_main_controller()->set_control(Controller::JUMP, value); + } + + parent->get_main_controller()->set_control(id, value); +} + +/* EOF */ diff --git a/src/control/joystick_manager.hpp b/src/control/joystick_manager.hpp new file mode 100644 index 000000000..c0e11d3de --- /dev/null +++ b/src/control/joystick_manager.hpp @@ -0,0 +1,109 @@ +// SuperTux +// Copyright (C) 2006 Matthias Braun +// 2014 Ingo Ruhnke +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program 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 this program. If not, see . + +#ifndef HEADER_SUPERTUX_CONTROL_JOYSTICK_MANAGER_HPP +#define HEADER_SUPERTUX_CONTROL_JOYSTICK_MANAGER_HPP + +#include +#include + +#include "SDL.h" + +#include "control/controller.hpp" +#include "util/reader_fwd.hpp" +#include "util/writer_fwd.hpp" + +class JoystickKeyboardController; + +class JoystickManager +{ +private: + typedef Uint8 JoyId; + + typedef std::map, Controller::Control> ButtonMap; + typedef std::map, Controller::Control> AxisMap; + typedef std::map, Controller::Control> HatMap; + +private: + JoystickKeyboardController* parent; + bool m_use_game_controller; + + ButtonMap joy_button_map; + AxisMap joy_axis_map; + HatMap joy_hat_map; + + int dead_zone; + + /// the number of buttons all joysticks have + int min_joybuttons; + + /// the max number of buttons a joystick has + int max_joybuttons; + + int max_joyaxis; + int max_joyhats; + + Uint8 hat_state; + +public: + bool jump_with_up_joy; + +public: + int wait_for_joystick; + +public: + std::vector joysticks; + +private: + std::vector game_controllers; + +public: + JoystickManager(JoystickKeyboardController* parent); + ~JoystickManager(); + + void process_hat_event(const SDL_JoyHatEvent& jhat); + void process_axis_event(const SDL_JoyAxisEvent& jaxis); + void process_button_event(const SDL_JoyButtonEvent& jbutton); + + void print_joystick_mappings(); + + int reversemap_joybutton(Controller::Control c); + int reversemap_joyaxis(Controller::Control c); + int reversemap_joyhat(Controller::Control c); + + void unbind_joystick_control(Controller::Control c); + + void bind_joybutton(JoyId joy_id, int button, Controller::Control c); + void bind_joyaxis(JoyId joy_id, int axis, Controller::Control c); + void bind_joyhat(JoyId joy_id, int dir, Controller::Control c); + + void set_joy_controls(Controller::Control id, bool value); + + void on_joystick_added(int joystick_index); + void on_joystick_removed(int instance_id); + + void read(const lisp::Lisp* joystick_lisp); + void write(Writer& writer); + +private: + JoystickManager(const JoystickManager&) = delete; + JoystickManager& operator=(const JoystickManager&) = delete; +}; + +#endif + +/* EOF */ diff --git a/src/control/joystickkeyboardcontroller.cpp b/src/control/joystickkeyboardcontroller.cpp index 85c6b8ee3..f0740e79e 100644 --- a/src/control/joystickkeyboardcontroller.cpp +++ b/src/control/joystickkeyboardcontroller.cpp @@ -19,37 +19,25 @@ #include -#include "lisp/list_iterator.hpp" +#include "control/joystick_manager.hpp" #include "gui/menu_manager.hpp" +#include "lisp/list_iterator.hpp" #include "supertux/console.hpp" #include "supertux/gameconfig.hpp" -#include "supertux/menu/menu_storage.hpp" #include "supertux/menu/joystick_menu.hpp" #include "supertux/menu/keyboard_menu.hpp" +#include "supertux/menu/menu_storage.hpp" #include "util/gettext.hpp" #include "util/writer.hpp" JoystickKeyboardController::JoystickKeyboardController() : - controller(), + controller(new Controller), + joystick_manager(new JoystickManager(this)), keymap(), - joy_button_map(), - joy_axis_map(), - joy_hat_map(), - joysticks(), name(), - dead_zone(), - min_joybuttons(), - max_joybuttons(), - max_joyaxis(), - max_joyhats(), - hat_state(0), - jump_with_up_joy(), jump_with_up_kbd(), - wait_for_key(-1), - wait_for_joystick(-1) + wait_for_key(-1) { - controller = new Controller; - // initialize default keyboard map keymap[SDLK_LEFT] = Controller::LEFT; keymap[SDLK_RIGHT] = Controller::RIGHT; @@ -69,144 +57,45 @@ JoystickKeyboardController::JoystickKeyboardController() : keymap[SDLK_HOME] = Controller::PEEK_UP; keymap[SDLK_END] = Controller::PEEK_DOWN; - jump_with_up_joy = false; jump_with_up_kbd = false; - - updateAvailableJoysticks(); - - dead_zone = 1000; - - // Default joystick button configuration - bind_joybutton(0, 0, Controller::JUMP); - bind_joybutton(0, 1, Controller::ACTION); - // 6 or more Buttons - if( min_joybuttons > 5 ){ - bind_joybutton(0, 4, Controller::PEEK_LEFT); - bind_joybutton(0, 5, Controller::PEEK_RIGHT); - // 8 or more - if(min_joybuttons > 7) - bind_joybutton(0, min_joybuttons-1, Controller::PAUSE_MENU); - } else { - // map the last 2 buttons to menu and pause - if(min_joybuttons > 2) - bind_joybutton(0, min_joybuttons-1, Controller::PAUSE_MENU); - // map all remaining joystick buttons to MENU_SELECT - for(int i = 2; i < max_joybuttons; ++i) { - if(i != min_joybuttons-1) - bind_joybutton(0, i, Controller::MENU_SELECT); - } - } - - // Default joystick axis configuration - bind_joyaxis(0, -1, Controller::LEFT); - bind_joyaxis(0, 1, Controller::RIGHT); - bind_joyaxis(0, -2, Controller::UP); - bind_joyaxis(0, 2, Controller::DOWN); } JoystickKeyboardController::~JoystickKeyboardController() { - for(std::vector::iterator i = joysticks.begin(); - i != joysticks.end(); ++i) { - if(*i != 0) - SDL_JoystickClose(*i); - } - delete controller; -} - -void -JoystickKeyboardController::updateAvailableJoysticks() -{ - for(std::vector::iterator i = joysticks.begin(); - i != joysticks.end(); ++i) { - if(*i != 0) - SDL_JoystickClose(*i); - } - joysticks.clear(); - - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); - SDL_InitSubSystem(SDL_INIT_JOYSTICK); - - int joystick_count = SDL_NumJoysticks(); - min_joybuttons = -1; - max_joybuttons = -1; - max_joyaxis = -1; - max_joyhats = -1; - - if( joystick_count > 0 ){ - for(int i = 0; i < joystick_count; ++i) { - SDL_Joystick* joystick = SDL_JoystickOpen(i); - bool good = true; - if(SDL_JoystickNumButtons(joystick) < 2) { - log_info << _("Joystick ") << i << ": " << SDL_JoystickID(i) << _(" has less than 2 buttons") << std::endl; - good = false; - } - if(SDL_JoystickNumAxes(joystick) < 2 - && SDL_JoystickNumHats(joystick) == 0) { - log_info << _("Joystick ") << i << ": " << SDL_JoystickID(i) << _(" has less than 2 axes and no hat") << std::endl; - good = false; - } - if(!good) { - SDL_JoystickClose(joystick); - continue; - } - - if(min_joybuttons < 0 || SDL_JoystickNumButtons(joystick) < min_joybuttons) - min_joybuttons = SDL_JoystickNumButtons(joystick); - - if(SDL_JoystickNumButtons(joystick) > max_joybuttons) - max_joybuttons = SDL_JoystickNumButtons(joystick); - - if(SDL_JoystickNumAxes(joystick) > max_joyaxis) - max_joyaxis = SDL_JoystickNumAxes(joystick); - - if(SDL_JoystickNumHats(joystick) > max_joyhats) - max_joyhats = SDL_JoystickNumHats(joystick); - - joysticks.push_back(joystick); - } - } - - // some joysticks or SDL seem to produce some bogus events after being opened - Uint32 ticks = SDL_GetTicks(); - while(SDL_GetTicks() - ticks < 200) { - SDL_Event event; - SDL_PollEvent(&event); - } } Controller* JoystickKeyboardController::get_main_controller() { - return controller; + return controller.get(); } void JoystickKeyboardController::read(const Reader& lisp) { const lisp::Lisp* keymap_lisp = lisp.get_lisp("keymap"); - if(keymap_lisp) { + if (keymap_lisp) { keymap.clear(); keymap_lisp->get("jump-with-up", jump_with_up_kbd); lisp::ListIterator iter(keymap_lisp); while(iter.next()) { - if(iter.item() == "map") { + if (iter.item() == "map") { int key = -1; std::string control; const lisp::Lisp* map = iter.lisp(); map->get("key", key); map->get("control", control); -// if(key < SDLK_FIRST || key >= SDLK_LAST) { +// if (key < SDLK_FIRST || key >= SDLK_LAST) { // log_info << "Invalid key '" << key << "' in keymap" << std::endl; // continue; // } int i = 0; for(i = 0; Controller::controlNames[i] != 0; ++i) { - if(control == Controller::controlNames[i]) + if (control == Controller::controlNames[i]) break; } - if(Controller::controlNames[i] == 0) { + if (Controller::controlNames[i] == 0) { log_info << "Invalid control '" << control << "' in keymap" << std::endl; continue; } @@ -216,61 +105,9 @@ JoystickKeyboardController::read(const Reader& lisp) } const lisp::Lisp* joystick_lisp = lisp.get_lisp(_("joystick")); - if(joystick_lisp) { - joystick_lisp->get("dead-zone", dead_zone); - joystick_lisp->get("jump-with-up", jump_with_up_joy); - lisp::ListIterator iter(joystick_lisp); - while(iter.next()) { - if(iter.item() == _("map")) { - int button = -1; - int axis = 0; - int hat = -1; - std::string control; - const lisp::Lisp* map = iter.lisp(); - - map->get("control", control); - int i = 0; - for(i = 0; Controller::controlNames[i] != 0; ++i) { - if(control == Controller::controlNames[i]) - break; - } - if(Controller::controlNames[i] == 0) { - log_info << "Invalid control '" << control << "' in buttonmap" << std::endl; - continue; - } - - bool js_available = joysticks.size() > 0; - - if (map->get("button", button)) { - if(js_available && (button < 0 || button >= max_joybuttons)) { - log_info << "Invalid button '" << button << "' in buttonmap" << std::endl; - continue; - } - bind_joybutton(0, button, Control(i)); - } - - if (map->get("axis", axis)) { - if (js_available && (axis == 0 || abs(axis) > max_joyaxis)) { - log_info << "Invalid axis '" << axis << "' in axismap" << std::endl; - continue; - } - bind_joyaxis(0, axis, Control(i)); - } - - if (map->get("hat", hat)) { - if (js_available && - hat != SDL_HAT_UP && - hat != SDL_HAT_DOWN && - hat != SDL_HAT_LEFT && - hat != SDL_HAT_RIGHT) { - log_info << "Invalid axis '" << axis << "' in axismap" << std::endl; - continue; - } else { - bind_joyhat(0, hat, Control(i)); - } - } - } - } + if (joystick_lisp) + { + joystick_manager->read(joystick_lisp); } } @@ -288,31 +125,7 @@ JoystickKeyboardController::write(Writer& writer) writer.end_list("keymap"); writer.start_list("joystick"); - writer.write("dead-zone", dead_zone); - writer.write("jump-with-up", jump_with_up_joy); - - for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); - ++i) { - writer.start_list("map"); - writer.write("button", i->first.second); - writer.write("control", Controller::controlNames[i->second]); - writer.end_list("map"); - } - - for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { - writer.start_list("map"); - writer.write("hat", i->first.second); - writer.write("control", Controller::controlNames[i->second]); - writer.end_list("map"); - } - - for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { - writer.start_list("map"); - writer.write("axis", i->first.second); - writer.write("control", Controller::controlNames[i->second]); - writer.end_list("map"); - } - + joystick_manager->write(writer); writer.end_list("joystick"); } @@ -329,15 +142,6 @@ JoystickKeyboardController::reset() } void -JoystickKeyboardController::set_joy_controls(Control id, bool value) -{ - if (jump_with_up_joy && id == Controller::UP) - controller->set_control(Controller::JUMP, value); - - controller->set_control(id, value); -} - -void JoystickKeyboardController::process_event(const SDL_Event& event) { switch(event.type) { @@ -351,145 +155,51 @@ JoystickKeyboardController::process_event(const SDL_Event& event) break; case SDL_JOYAXISMOTION: - process_axis_event(event.jaxis); + joystick_manager->process_axis_event(event.jaxis); break; case SDL_JOYHATMOTION: - process_hat_event(event.jhat); + joystick_manager->process_hat_event(event.jhat); break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: - process_button_event(event.jbutton); + joystick_manager->process_button_event(event.jbutton); break; - default: + case SDL_JOYDEVICEADDED: + joystick_manager->on_joystick_added(event.jdevice.which); break; - } -} -void -JoystickKeyboardController::process_button_event(const SDL_JoyButtonEvent& jbutton) -{ - if(wait_for_joystick >= 0) - { - if(jbutton.state == SDL_PRESSED) - { - bind_joybutton(jbutton.which, jbutton.button, (Control)wait_for_joystick); - MenuStorage::get_joystick_options_menu()->update(); - reset(); - wait_for_joystick = -1; - } - } - else - { - ButtonMap::iterator i = joy_button_map.find(std::make_pair(jbutton.which, jbutton.button)); - if(i == joy_button_map.end()) { - log_debug << "Unmapped joybutton " << (int)jbutton.button << " pressed" << std::endl; - } else { - set_joy_controls(i->second, (jbutton.state == SDL_PRESSED)); - } - } -} - -void -JoystickKeyboardController::process_axis_event(const SDL_JoyAxisEvent& jaxis) -{ - if (wait_for_joystick >= 0) - { - if (abs(jaxis.value) > dead_zone) { - if (jaxis.value < 0) - bind_joyaxis(jaxis.which, -(jaxis.axis + 1), Control(wait_for_joystick)); - else - bind_joyaxis(jaxis.which, jaxis.axis + 1, Control(wait_for_joystick)); - - MenuStorage::get_joystick_options_menu()->update(); - wait_for_joystick = -1; - } - } - else - { - // Split the axis into left and right, so that both can be - // mapped separately (needed for jump/down vs up/down) - int axis = jaxis.axis + 1; - - AxisMap::iterator left = joy_axis_map.find(std::make_pair(jaxis.which, -axis)); - AxisMap::iterator right = joy_axis_map.find(std::make_pair(jaxis.which, axis)); - - if(left == joy_axis_map.end()) { - // std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl; - } else { - if (jaxis.value < -dead_zone) - set_joy_controls(left->second, true); - else - set_joy_controls(left->second, false); - } - - if(right == joy_axis_map.end()) { - // std::cout << "Unmapped joyaxis " << (int)jaxis.axis << " moved" << std::endl; - } else { - if (jaxis.value > dead_zone) - set_joy_controls(right->second, true); - else - set_joy_controls(right->second, false); - } - } -} - -void -JoystickKeyboardController::process_hat_event(const SDL_JoyHatEvent& jhat) -{ - Uint8 changed = hat_state ^ jhat.value; - - if (wait_for_joystick >= 0) - { - if (changed & SDL_HAT_UP && jhat.value & SDL_HAT_UP) - bind_joyhat(jhat.which, SDL_HAT_UP, Control(wait_for_joystick)); - - if (changed & SDL_HAT_DOWN && jhat.value & SDL_HAT_DOWN) - bind_joyhat(jhat.which, SDL_HAT_DOWN, Control(wait_for_joystick)); + case SDL_JOYDEVICEREMOVED: + joystick_manager->on_joystick_removed(event.jdevice.which); + break; - if (changed & SDL_HAT_LEFT && jhat.value & SDL_HAT_LEFT) - bind_joyhat(jhat.which, SDL_HAT_LEFT, Control(wait_for_joystick)); + case SDL_CONTROLLERBUTTONDOWN: + std::cout << "SDL_CONTROLLERBUTTONDOWN" << std::endl; + break; - if (changed & SDL_HAT_RIGHT && jhat.value & SDL_HAT_RIGHT) - bind_joyhat(jhat.which, SDL_HAT_RIGHT, Control(wait_for_joystick)); + case SDL_CONTROLLERBUTTONUP: + std::cout << "SDL_CONTROLLERBUTTONUP" << std::endl; + break; - MenuStorage::get_joystick_options_menu()->update(); - wait_for_joystick = -1; - } - else - { - if (changed & SDL_HAT_UP) - { - HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_UP)); - if (it != joy_hat_map.end()) - set_joy_controls(it->second, jhat.value & SDL_HAT_UP); - } + case SDL_CONTROLLERDEVICEADDED: + // ignored, handled in SDL_JOYDEVICEADDED + std::cout << "SDL_CONTROLLERDEVICEADDED" << std::endl; + break; - if (changed & SDL_HAT_DOWN) - { - HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_DOWN)); - if (it != joy_hat_map.end()) - set_joy_controls(it->second, jhat.value & SDL_HAT_DOWN); - } + case SDL_CONTROLLERDEVICEREMOVED: + // ignored, handled in SDL_JOYDEVICEREMOVED + std::cout << "SDL_CONTROLLERDEVICEREMOVED" << std::endl; + break; - if (changed & SDL_HAT_LEFT) - { - HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_LEFT)); - if (it != joy_hat_map.end()) - set_joy_controls(it->second, jhat.value & SDL_HAT_LEFT); - } + case SDL_CONTROLLERDEVICEREMAPPED: + std::cout << "SDL_CONTROLLERDEVICEREMAPPED" << std::endl; + break; - if (changed & SDL_HAT_RIGHT) - { - HatMap::iterator it = joy_hat_map.find(std::make_pair(jhat.which, SDL_HAT_RIGHT)); - if (it != joy_hat_map.end()) - set_joy_controls(it->second, jhat.value & SDL_HAT_RIGHT); - } + default: + break; } - - hat_state = jhat.value; } void @@ -519,7 +229,7 @@ JoystickKeyboardController::process_key_event(const SDL_KeyboardEvent& event) } else if (MenuManager::current()) { // if menu mode: send key there process_menu_key_event(event); - } else if(key_mapping == keymap.end()) { + } else if (key_mapping == keymap.end()) { // default action: update controls //log_debug << "Key " << event.key.SDL_Keycode.sym << " is unbound" << std::endl; } else { @@ -581,11 +291,11 @@ void JoystickKeyboardController::process_menu_key_event(const SDL_KeyboardEvent& event) { // wait for key mode? - if(wait_for_key >= 0) { - if(event.type == SDL_KEYUP) + if (wait_for_key >= 0) { + if (event.type == SDL_KEYUP) return; - if(event.keysym.sym != SDLK_ESCAPE + if (event.keysym.sym != SDLK_ESCAPE && event.keysym.sym != SDLK_PAUSE) { bind_key(event.keysym.sym, Control(wait_for_key)); } @@ -594,11 +304,11 @@ JoystickKeyboardController::process_menu_key_event(const SDL_KeyboardEvent& even wait_for_key = -1; return; } - if(wait_for_joystick >= 0) { - if(event.keysym.sym == SDLK_ESCAPE) { + if (joystick_manager->wait_for_joystick >= 0) { + if (event.keysym.sym == SDLK_ESCAPE) { reset(); MenuStorage::get_joystick_options_menu()->update(); - wait_for_joystick = -1; + joystick_manager->wait_for_joystick = -1; } return; } @@ -638,70 +348,12 @@ JoystickKeyboardController::process_menu_key_event(const SDL_KeyboardEvent& even } void -JoystickKeyboardController::unbind_joystick_control(Control control) -{ - // remove all previous mappings for that control - for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); /* no ++i */) { - if(i->second == control) - joy_axis_map.erase(i++); - else - ++i; - } - - for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); /* no ++i */) { - if(i->second == control) - joy_button_map.erase(i++); - else - ++i; - } - - for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); /* no ++i */) { - if(i->second == control) - joy_hat_map.erase(i++); - else - ++i; - } -} - -void -JoystickKeyboardController::bind_joyaxis(JoyId joy_id, int axis, Control control) -{ - // axis isn't the SDL axis number, but axisnumber + 1 with sign - // changed depending on if the positive or negative end is to be - // used (negative axis 0 becomes -1, positive axis 2 becomes +3, - // etc.) - - unbind_joystick_control(control); - - // add new mapping - joy_axis_map[std::make_pair(joy_id, axis)] = control; -} - -void -JoystickKeyboardController::bind_joyhat(JoyId joy_id, int dir, Control c) -{ - unbind_joystick_control(c); - - // add new mapping - joy_hat_map[std::make_pair(joy_id, dir)] = c; -} - -void -JoystickKeyboardController::bind_joybutton(JoyId joy_id, int button, Control control) -{ - unbind_joystick_control(control); - - // add new mapping - joy_button_map[std::make_pair(joy_id, button)] = control; -} - -void JoystickKeyboardController::bind_key(SDL_Keycode key, Control control) { // remove all previous mappings for that control and for that key for(KeyMap::iterator i = keymap.begin(); i != keymap.end(); /* no ++i */) { - if(i->second == control) { + if (i->second == control) { KeyMap::iterator e = i; ++i; keymap.erase(e); @@ -711,74 +363,22 @@ JoystickKeyboardController::bind_key(SDL_Keycode key, Control control) } KeyMap::iterator i = keymap.find(key); - if(i != keymap.end()) + if (i != keymap.end()) keymap.erase(i); // add new mapping keymap[key] = control; } -void -JoystickKeyboardController::print_joystick_mappings() -{ - std::cout << _("Joystick Mappings") << std::endl; - std::cout << "-----------------" << std::endl; - for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { - std::cout << "Axis: " << i->first.second << " -> " << i->second << std::endl; - } - - for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) { - std::cout << "Button: " << i->first.second << " -> " << i->second << std::endl; - } - - for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { - std::cout << "Hat: " << i->first.second << " -> " << i->second << std::endl; - } - std::cout << std::endl; -} - SDL_Keycode JoystickKeyboardController::reversemap_key(Control c) { for(KeyMap::iterator i = keymap.begin(); i != keymap.end(); ++i) { - if(i->second == c) + if (i->second == c) return i->first; } return SDLK_UNKNOWN; } -int -JoystickKeyboardController::reversemap_joyaxis(Control c) -{ - for(AxisMap::iterator i = joy_axis_map.begin(); i != joy_axis_map.end(); ++i) { - if(i->second == c) - return i->first.second; - } - - return 0; -} - -int -JoystickKeyboardController::reversemap_joybutton(Control c) -{ - for(ButtonMap::iterator i = joy_button_map.begin(); i != joy_button_map.end(); ++i) { - if(i->second == c) - return i->first.second; - } - - return -1; -} - -int -JoystickKeyboardController::reversemap_joyhat(Control c) -{ - for(HatMap::iterator i = joy_hat_map.begin(); i != joy_hat_map.end(); ++i) { - if(i->second == c) - return i->first.second; - } - - return -1; -} - /* EOF */ diff --git a/src/control/joystickkeyboardcontroller.hpp b/src/control/joystickkeyboardcontroller.hpp index 661ca7fb3..76798a194 100644 --- a/src/control/joystickkeyboardcontroller.hpp +++ b/src/control/joystickkeyboardcontroller.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "util/reader_fwd.hpp" #include "util/writer_fwd.hpp" @@ -31,6 +32,7 @@ class Menu; class KeyboardMenu; class JoystickMenu; class Controller; +class JoystickManager; class JoystickKeyboardController { @@ -39,12 +41,8 @@ private: friend class JoystickMenu; typedef Controller::Control Control; - typedef Uint8 JoyId; typedef std::map KeyMap; - typedef std::map, Control> ButtonMap; - typedef std::map, Control> AxisMap; - typedef std::map, Control> HatMap; public: JoystickKeyboardController(); @@ -66,60 +64,25 @@ public: private: void process_text_input_event(const SDL_TextInputEvent& event); void process_key_event(const SDL_KeyboardEvent& event); - void process_hat_event(const SDL_JoyHatEvent& jhat); - void process_axis_event(const SDL_JoyAxisEvent& jaxis); - void process_button_event(const SDL_JoyButtonEvent& jbutton); void process_console_key_event(const SDL_KeyboardEvent& event); void process_menu_key_event(const SDL_KeyboardEvent& event); - void print_joystick_mappings(); - SDL_Keycode reversemap_key(Control c); - int reversemap_joybutton(Control c); - int reversemap_joyaxis(Control c); - int reversemap_joyhat(Control c); - - void unbind_joystick_control(Control c); - - void bind_joybutton(JoyId joy_id, int button, Control c); - void bind_joyaxis(JoyId joy_id, int axis, Control c); - void bind_joyhat(JoyId joy_id, int dir, Control c); void bind_key(SDL_Keycode key, Control c); - void set_joy_controls(Control id, bool value); - private: - Controller *controller; + std::unique_ptr controller; +public: + std::unique_ptr joystick_manager; +private: KeyMap keymap; - ButtonMap joy_button_map; - - AxisMap joy_axis_map; - - HatMap joy_hat_map; - - std::vector joysticks; - std::string name; - int dead_zone; - /// the number of buttons all joysticks have - int min_joybuttons; - /// the max number of buttons a joystick has - int max_joybuttons; - - int max_joyaxis; - - int max_joyhats; - - Uint8 hat_state; - - bool jump_with_up_joy; // Joystick up jumps bool jump_with_up_kbd; // Keyboard up jumps int wait_for_key; - int wait_for_joystick; private: JoystickKeyboardController(const JoystickKeyboardController&); diff --git a/src/supertux/menu/joystick_menu.cpp b/src/supertux/menu/joystick_menu.cpp index e4e1dcb24..27fe9f0ba 100644 --- a/src/supertux/menu/joystick_menu.cpp +++ b/src/supertux/menu/joystick_menu.cpp @@ -19,14 +19,12 @@ #include +#include "control/joystick_manager.hpp" #include "util/gettext.hpp" -namespace{ - const int SCAN_JOYSTICKS = Controller::CONTROLCOUNT + 1; -} - JoystickMenu::JoystickMenu(JoystickKeyboardController* _controller) : - controller(_controller) + controller(_controller), + joysticks_available(false) { recreateMenu(); } @@ -40,7 +38,9 @@ JoystickMenu::recreateMenu() clear(); add_label(_("Setup Joystick")); add_hl(); - if(controller->joysticks.size() > 0) { + if(controller->joystick_manager->joysticks.size() > 0) { + joysticks_available = true; + add_controlfield(Controller::UP, _("Up")); add_controlfield(Controller::DOWN, _("Down")); add_controlfield(Controller::LEFT, _("Left")); @@ -53,22 +53,10 @@ JoystickMenu::recreateMenu() add_controlfield(Controller::PEEK_UP, _("Peek Up")); add_controlfield(Controller::PEEK_DOWN, _("Peek Down")); - add_toggle(Controller::CONTROLCOUNT, _("Jump with Up"), controller->jump_with_up_joy); + add_toggle(Controller::CONTROLCOUNT, _("Jump with Up"), controller->joystick_manager->jump_with_up_joy); } else { add_inactive(-1, _("No Joysticks found")); - } - add_inactive(-1,""); - add_entry(SCAN_JOYSTICKS, _("Scan for Joysticks")); - //Show Joysticks currently activated: //edit by giby - SDL_Joystick *joy; - if (SDL_NumJoysticks() > 0) { - joy = SDL_JoystickOpen(0); - } - - for(std::vector::iterator i = controller->joysticks.begin(); - i != controller->joysticks.end(); ++i) { - if(*i != 0) - add_inactive(-1, SDL_JoystickName(joy) ); + joysticks_available = false; } add_hl(); @@ -92,21 +80,18 @@ JoystickMenu::menu_action(MenuItem* item) { if (item->id >= 0 && item->id < Controller::CONTROLCOUNT) { item->change_input(_("Press Button")); - controller->wait_for_joystick = item->id; + controller->joystick_manager->wait_for_joystick = item->id; } else if (item->id == Controller::CONTROLCOUNT) { - controller->jump_with_up_joy = item->toggled; - } else if( item->id == SCAN_JOYSTICKS) { - controller->updateAvailableJoysticks(); - recreateMenu(); + controller->joystick_manager->jump_with_up_joy = item->toggled; } } void JoystickMenu::update_menu_item(Controller::Control id) { - int button = controller->reversemap_joybutton(id); - int axis = controller->reversemap_joyaxis(id); - int hat_dir = controller->reversemap_joyhat(id); + int button = controller->joystick_manager->reversemap_joybutton(id); + int axis = controller->joystick_manager->reversemap_joyaxis(id); + int hat_dir = controller->joystick_manager->reversemap_joyhat(id); if (button != -1) { get_item_by_id((int)id).change_input(get_button_name(button)); @@ -167,23 +152,34 @@ JoystickMenu::update_menu_item(Controller::Control id) void JoystickMenu::update() { - if(controller->joysticks.size() == 0) - return; - - update_menu_item(Controller::UP); - update_menu_item(Controller::DOWN); - update_menu_item(Controller::LEFT); - update_menu_item(Controller::RIGHT); - - update_menu_item(Controller::JUMP); - update_menu_item(Controller::ACTION); - update_menu_item(Controller::PAUSE_MENU); - update_menu_item(Controller::PEEK_LEFT); - update_menu_item(Controller::PEEK_RIGHT); - update_menu_item(Controller::PEEK_UP); - update_menu_item(Controller::PEEK_DOWN); - - get_item_by_id(Controller::CONTROLCOUNT).toggled = controller->jump_with_up_joy; + log_info << controller->joystick_manager->joysticks.size() << std::endl; + + if(true) //controller->joysticks.size() == 0) + { + // do nothing + } + else + { + if (!joysticks_available) + { + recreateMenu(); + } + + update_menu_item(Controller::UP); + update_menu_item(Controller::DOWN); + update_menu_item(Controller::LEFT); + update_menu_item(Controller::RIGHT); + + update_menu_item(Controller::JUMP); + update_menu_item(Controller::ACTION); + update_menu_item(Controller::PAUSE_MENU); + update_menu_item(Controller::PEEK_LEFT); + update_menu_item(Controller::PEEK_RIGHT); + update_menu_item(Controller::PEEK_UP); + update_menu_item(Controller::PEEK_DOWN); + + //get_item_by_id(Controller::CONTROLCOUNT).toggled = controller->jump_with_up_joy; + } } /* EOF */ diff --git a/src/supertux/menu/joystick_menu.hpp b/src/supertux/menu/joystick_menu.hpp index 6529563d8..8efbda83a 100644 --- a/src/supertux/menu/joystick_menu.hpp +++ b/src/supertux/menu/joystick_menu.hpp @@ -31,13 +31,16 @@ public: std::string get_button_name(int button); void update_menu_item(Controller::Control id); virtual void menu_action(MenuItem* item); - JoystickKeyboardController* controller; void check_menu() {} private: void recreateMenu(); private: + JoystickKeyboardController* controller; + bool joysticks_available; + +private: JoystickMenu(const JoystickMenu&); JoystickMenu& operator=(const JoystickMenu&); }; -- 2.11.0