From 84756ba8efc11f6f539d73cc181170b4b1bcf082 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=A4rkl?= Date: Sun, 18 Aug 2019 17:15:13 +0200 Subject: [PATCH] Support SDL GameController --- CMakeLists.txt | 3 +- gui/CMakeLists.txt | 18 ++- gui/include/controllermanager.h | 96 +++++++++++++ gui/include/streamsession.h | 5 +- gui/src/controllermanager.cpp | 231 ++++++++++++++++++++++++++++++++ gui/src/main.cpp | 1 + gui/src/streamsession.cpp | 45 ++++++- gui/src/streamwindow.cpp | 1 + 8 files changed, 391 insertions(+), 9 deletions(-) create mode 100644 gui/include/controllermanager.h create mode 100644 gui/src/controllermanager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0843434..6178c0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,8 @@ project(chiaki) option(CHIAKI_ENABLE_TESTS "Enable tests for Chiaki" ON) option(CHIAKI_ENABLE_CLI "Enable CLI for Chiaki" OFF) -option(CHIAKI_GUI_ENABLE_QT_GAMEPAD "Use QtGamepad for Input" ON) +option(CHIAKI_GUI_ENABLE_QT_GAMEPAD "Use QtGamepad for Input" OFF) +option(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER "Use SDL Gamecontroller for Input" ON) set(CHIAKI_VERSION_MAJOR 1) set(CHIAKI_VERSION_MINOR 0) diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 53a40e4..3ee763f 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -3,11 +3,17 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Gui Multimedia OpenGL Svg) +if(CHIAKI_GUI_ENABLE_QT_GAMEPAD AND CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) + message(FATAL_ERROR "Only one input method may be enabled. Disable either CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER or CHIAKI_GUI_ENABLE_QT_GAMEPAD.") +endif() if(CHIAKI_GUI_ENABLE_QT_GAMEPAD) find_package(Qt5 REQUIRED COMPONENTS Gamepad) endif() +if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) + find_package(SDL2 REQUIRED) +endif() -find_package(FFMPEG REQUIRED COMPONENTS avcodec) +find_package(FFMPEG REQUIRED COMPONENTS avcodec avutil) set(RESOURCE_FILES "") @@ -16,6 +22,7 @@ if(APPLE) endif() add_executable(chiaki + ${RESOURCE_FILES} include/exception.h src/main.cpp include/streamwindow.h @@ -51,7 +58,8 @@ add_executable(chiaki include/manualhostdialog.h src/manualhostdialog.cpp res/resources.qrc - ${RESOURCE_FILES}) + include/controllermanager.h + src/controllermanager.cpp) target_include_directories(chiaki PRIVATE include) target_link_libraries(chiaki chiaki-lib) @@ -60,12 +68,16 @@ if(CHIAKI_ENABLE_CLI) target_link_libraries(chiaki chiaki-cli-lib) endif() -target_link_libraries(chiaki FFMPEG::avcodec) +target_link_libraries(chiaki FFMPEG::avcodec FFMPEG::avutil) target_link_libraries(chiaki Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Multimedia Qt5::OpenGL Qt5::Svg) if(CHIAKI_GUI_ENABLE_QT_GAMEPAD) target_link_libraries(chiaki Qt5::Gamepad) target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_QT_GAMEPAD) endif() +if(CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) + target_link_libraries(chiaki SDL2::SDL2) + target_compile_definitions(chiaki PRIVATE CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER) +endif() set_target_properties(chiaki PROPERTIES MACOSX_BUNDLE TRUE diff --git a/gui/include/controllermanager.h b/gui/include/controllermanager.h new file mode 100644 index 0000000..952275e --- /dev/null +++ b/gui/include/controllermanager.h @@ -0,0 +1,96 @@ +/* + * This file is part of Chiaki. + * + * Chiaki 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. + * + * Chiaki 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 Chiaki. If not, see . + */ + +#ifndef CHIAKI_CONTROLLERMANAGER_H +#define CHIAKI_CONTROLLERMANAGER_H + +#include + +#include +#include +#include +#include + +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER +#include +#endif + +class Controller; + +class ControllerManager : public QObject +{ + Q_OBJECT + + friend class Controller; + + private: +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + QSet available_controllers; +#endif + QMap open_controllers; + + void ControllerClosed(Controller *controller); + + private slots: + void UpdateAvailableControllers(); + void HandleEvents(); + void ControllerEvent(int device_id); + + public: + static ControllerManager *GetInstance(); + + ControllerManager(QObject *parent = nullptr); + ~ControllerManager(); + + QList GetAvailableControllers(); + Controller *OpenController(int device_id); + + signals: + void AvailableControllersUpdated(); +}; + +class Controller : public QObject +{ + Q_OBJECT + + friend class ControllerManager; + + private: + Controller(int device_id, ControllerManager *manager); + + void UpdateState(); + + ControllerManager *manager; + int id; + +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + SDL_GameController *controller; +#endif + + public: + ~Controller(); + + bool IsConnected(); + int GetDeviceID(); + QString GetName(); + ChiakiControllerState GetState(); + + signals: + void StateChanged(); +}; + +#endif // CHIAKI_CONTROLLERMANAGER_H diff --git a/gui/include/streamsession.h b/gui/include/streamsession.h index 3dab317..6a783de 100644 --- a/gui/include/streamsession.h +++ b/gui/include/streamsession.h @@ -21,6 +21,7 @@ #include "videodecoder.h" #include "exception.h" #include "sessionlog.h" +#include "controllermanager.h" #include #include @@ -68,6 +69,7 @@ class StreamSession : public QObject #if CHIAKI_GUI_ENABLE_QT_GAMEPAD QGamepad *gamepad; #endif + Controller *controller; ChiakiControllerState keyboard_state; @@ -93,6 +95,7 @@ class StreamSession : public QObject #if CHIAKI_GUI_ENABLE_QT_GAMEPAD QGamepad *GetGamepad() { return gamepad; } #endif + Controller *GetController() { return controller; } VideoDecoder *GetVideoDecoder() { return &video_decoder; } void HandleKeyboardEvent(QKeyEvent *event); @@ -102,9 +105,7 @@ class StreamSession : public QObject void SessionQuit(ChiakiQuitReason reason, const QString &reason_str); private slots: -#if CHIAKI_GUI_ENABLE_QT_GAMEPAD void UpdateGamepads(); -#endif void SendFeedbackState(); }; diff --git a/gui/src/controllermanager.cpp b/gui/src/controllermanager.cpp new file mode 100644 index 0000000..77941de --- /dev/null +++ b/gui/src/controllermanager.cpp @@ -0,0 +1,231 @@ +/* + * This file is part of Chiaki. + * + * Chiaki 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. + * + * Chiaki 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 Chiaki. If not, see . + */ + +#include + +#include +#include +#include +#include + +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER +#include +#endif + +static ControllerManager *instance = nullptr; + +#define UPDATE_INTERVAL_MS 4 + +ControllerManager *ControllerManager::GetInstance() +{ + if(!instance) + instance = new ControllerManager(qApp); + return instance; +} + +ControllerManager::ControllerManager(QObject *parent) + : QObject(parent) +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + SDL_SetMainReady(); + if(SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) + { + const char *err = SDL_GetError(); + QMessageBox::critical(nullptr, "SDL Init", tr("Failed to initialized SDL Gamecontroller: %1").arg(err ? err : "")); + } + + auto timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &ControllerManager::HandleEvents); + timer->start(UPDATE_INTERVAL_MS); +#endif + + UpdateAvailableControllers(); +} + +ControllerManager::~ControllerManager() +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + SDL_Quit(); +#endif +} + +void ControllerManager::UpdateAvailableControllers() +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + QSet current_controllers; + for(int i=0; iUpdateState(); +} + +QList ControllerManager::GetAvailableControllers() +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + return available_controllers.toList(); +#else + return {}; +#endif +} + +Controller *ControllerManager::OpenController(int device_id) +{ + if(open_controllers.contains(device_id)) + return nullptr; + auto controller = new Controller(device_id, this); + open_controllers[device_id] = controller; + return controller; +} + +void ControllerManager::ControllerClosed(Controller *controller) +{ + open_controllers.remove(controller->GetDeviceID()); +} + +Controller::Controller(int device_id, ControllerManager *manager) : QObject(manager) +{ + this->id = device_id; + this->manager = manager; + +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + controller = nullptr; + for(int i=0; iControllerClosed(this); +} + +void Controller::UpdateState() +{ + emit StateChanged(); +} + +bool Controller::IsConnected() +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + return controller && SDL_GameControllerGetAttached(controller); +#else + return false; +#endif +} + +int Controller::GetDeviceID() +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + return id; +#else + return -1; +#endif +} + +QString Controller::GetName() +{ +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + if(!controller) + return QString(); + return SDL_GameControllerName(controller); +#else + return QString(); +#endif +} + +ChiakiControllerState Controller::GetState() +{ + ChiakiControllerState state = {}; +#ifdef CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + if(!controller) + return state; + + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_A) ? CHIAKI_CONTROLLER_BUTTON_CROSS : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_B) ? CHIAKI_CONTROLLER_BUTTON_MOON : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_X) ? CHIAKI_CONTROLLER_BUTTON_BOX : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_Y) ? CHIAKI_CONTROLLER_BUTTON_PYRAMID : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) ? CHIAKI_CONTROLLER_BUTTON_DPAD_LEFT : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) ? CHIAKI_CONTROLLER_BUTTON_DPAD_RIGHT : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP) ? CHIAKI_CONTROLLER_BUTTON_DPAD_UP : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) ? CHIAKI_CONTROLLER_BUTTON_DPAD_DOWN : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER) ? CHIAKI_CONTROLLER_BUTTON_L1 : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) ? CHIAKI_CONTROLLER_BUTTON_R1 : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_LEFTSTICK) ? CHIAKI_CONTROLLER_BUTTON_L3 : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK) ? CHIAKI_CONTROLLER_BUTTON_R3 : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_START) ? CHIAKI_CONTROLLER_BUTTON_OPTIONS : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_BACK) ? CHIAKI_CONTROLLER_BUTTON_SHARE : 0; + state.buttons |= SDL_GameControllerGetButton(controller, SDL_CONTROLLER_BUTTON_GUIDE) ? CHIAKI_CONTROLLER_BUTTON_PS : 0; + state.l2_state = (uint8_t)(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) >> 4); + state.r2_state = (uint8_t)(SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 4); + state.left_x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + state.left_y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + state.right_x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + state.right_y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + +#endif + return state; +} \ No newline at end of file diff --git a/gui/src/main.cpp b/gui/src/main.cpp index eb95488..716fbca 100644 --- a/gui/src/main.cpp +++ b/gui/src/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef CHIAKI_ENABLE_CLI #include diff --git a/gui/src/streamsession.cpp b/gui/src/streamsession.cpp index cb146a6..0f186a9 100644 --- a/gui/src/streamsession.cpp +++ b/gui/src/streamsession.cpp @@ -17,6 +17,7 @@ #include #include +#include #include @@ -57,6 +58,7 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje #if CHIAKI_GUI_ENABLE_QT_GAMEPAD gamepad(nullptr), #endif + controller(nullptr), video_decoder(log.GetChiakiLog()), audio_output(nullptr), audio_io(nullptr) @@ -87,8 +89,12 @@ StreamSession::StreamSession(const StreamSessionConnectInfo &connect_info, QObje #if CHIAKI_GUI_ENABLE_QT_GAMEPAD connect(QGamepadManager::instance(), &QGamepadManager::connectedGamepadsChanged, this, &StreamSession::UpdateGamepads); - UpdateGamepads(); #endif +#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + connect(ControllerManager::GetInstance(), &ControllerManager::AvailableControllersUpdated, this, &StreamSession::UpdateGamepads); +#endif + + UpdateGamepads(); } StreamSession::~StreamSession() @@ -98,6 +104,9 @@ StreamSession::~StreamSession() #if CHIAKI_GUI_ENABLE_QT_GAMEPAD delete gamepad; #endif +#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + delete controller; +#endif } void StreamSession::Start() @@ -157,9 +166,9 @@ void StreamSession::HandleKeyboardEvent(QKeyEvent *event) SendFeedbackState(); } -#if CHIAKI_GUI_ENABLE_QT_GAMEPAD void StreamSession::UpdateGamepads() { +#if CHIAKI_GUI_ENABLE_QT_GAMEPAD if(!gamepad || !gamepad->isConnected()) { if(gamepad) @@ -198,8 +207,33 @@ void StreamSession::UpdateGamepads() } SendFeedbackState(); -} #endif +#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + if(!controller || !controller->IsConnected()) + { + if(controller) + { + CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d disconnected", controller->GetDeviceID()); + delete controller; + controller = nullptr; + } + const auto available_controllers = ControllerManager::GetInstance()->GetAvailableControllers(); + if(!available_controllers.isEmpty()) + { + controller = ControllerManager::GetInstance()->OpenController(available_controllers[0]); + if(!controller) + { + CHIAKI_LOGE(log.GetChiakiLog(), "Failed to open controller %d", available_controllers[0]); + return; + } + CHIAKI_LOGI(log.GetChiakiLog(), "Controller %d opened: \"%s\"", available_controllers[0], controller->GetName().toLocal8Bit().constData()); + connect(controller, &Controller::StateChanged, this, &StreamSession::SendFeedbackState); + } + } + + SendFeedbackState(); +#endif +} void StreamSession::SendFeedbackState() { @@ -232,6 +266,11 @@ void StreamSession::SendFeedbackState() } #endif +#if CHIAKI_GUI_ENABLE_SDL_GAMECONTROLLER + if(controller) + state = controller->GetState(); +#endif + chiaki_controller_state_or(&state, &state, &keyboard_state); chiaki_session_set_controller_state(&session, &state); } diff --git a/gui/src/streamwindow.cpp b/gui/src/streamwindow.cpp index ee99a92..bc91823 100644 --- a/gui/src/streamwindow.cpp +++ b/gui/src/streamwindow.cpp @@ -25,6 +25,7 @@ StreamWindow::StreamWindow(const StreamSessionConnectInfo &connect_info, QWidget *parent) : QMainWindow(parent) { + setAttribute(Qt::WA_DeleteOnClose); try { session = new StreamSession(connect_info, this);