Frontend: Support game controllers
This commit is contained in:
parent
2d0dd03705
commit
e98d109da2
|
@ -24,7 +24,7 @@ static int NoGUITest()
|
||||||
static int Run(int argc, char* argv[])
|
static int Run(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
// init sdl
|
// init sdl
|
||||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0)
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMECONTROLLER) < 0)
|
||||||
{
|
{
|
||||||
Panic("SDL initialization failed");
|
Panic("SDL initialization failed");
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -20,6 +20,8 @@ SDLInterface::SDLInterface() = default;
|
||||||
|
|
||||||
SDLInterface::~SDLInterface()
|
SDLInterface::~SDLInterface()
|
||||||
{
|
{
|
||||||
|
CloseGameControllers();
|
||||||
|
|
||||||
if (m_gl_context)
|
if (m_gl_context)
|
||||||
{
|
{
|
||||||
if (m_display_vao != 0)
|
if (m_display_vao != 0)
|
||||||
|
@ -200,7 +202,7 @@ void SDLInterface::UpdateAudioVisualSync()
|
||||||
Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "",
|
Log_InfoPrintf("Syncing to %s%s", audio_sync_enabled ? "audio" : "",
|
||||||
(speed_limiter_enabled && vsync_enabled) ? " and video" : "");
|
(speed_limiter_enabled && vsync_enabled) ? " and video" : "");
|
||||||
|
|
||||||
m_audio_stream->SetSync(audio_sync_enabled);
|
m_audio_stream->SetSync(false);
|
||||||
|
|
||||||
// Window framebuffer has to be bound to call SetSwapInterval.
|
// Window framebuffer has to be bound to call SetSwapInterval.
|
||||||
GLint current_fbo = 0;
|
GLint current_fbo = 0;
|
||||||
|
@ -210,6 +212,30 @@ void SDLInterface::UpdateAudioVisualSync()
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SDLInterface::OpenGameControllers()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SDL_NumJoysticks(); i++)
|
||||||
|
{
|
||||||
|
SDL_GameController* gcontroller = SDL_GameControllerOpen(i);
|
||||||
|
if (gcontroller)
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Opened controller %d: %s", i, SDL_GameControllerName(gcontroller));
|
||||||
|
m_sdl_controllers.emplace(i, gcontroller);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log_WarningPrintf("Failed to open controller %d", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLInterface::CloseGameControllers()
|
||||||
|
{
|
||||||
|
for (auto& it : m_sdl_controllers)
|
||||||
|
SDL_GameControllerClose(it.second);
|
||||||
|
m_sdl_controllers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool SDLInterface::InitializeSystem(const char* filename, const char* exp1_filename)
|
bool SDLInterface::InitializeSystem(const char* filename, const char* exp1_filename)
|
||||||
{
|
{
|
||||||
if (!HostInterface::InitializeSystem(filename, exp1_filename))
|
if (!HostInterface::InitializeSystem(filename, exp1_filename))
|
||||||
|
@ -243,6 +269,8 @@ std::unique_ptr<SDLInterface> SDLInterface::Create(const char* filename /* = nul
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
intf->OpenGameControllers();
|
||||||
|
|
||||||
const bool boot = (filename != nullptr || exp1_filename != nullptr || save_state_filename != nullptr);
|
const bool boot = (filename != nullptr || exp1_filename != nullptr || save_state_filename != nullptr);
|
||||||
if (boot)
|
if (boot)
|
||||||
{
|
{
|
||||||
|
@ -288,10 +316,144 @@ static inline u32 SDLButtonToHostButton(u32 button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDLInterface::HandleSDLEvent(const SDL_Event* event)
|
static bool HandleSDLKeyEventForController(const SDL_Event* event, DigitalController* controller)
|
||||||
|
{
|
||||||
|
const bool pressed = (event->type == SDL_KEYDOWN);
|
||||||
|
switch (event->key.keysym.scancode)
|
||||||
|
{
|
||||||
|
case SDL_SCANCODE_KP_8:
|
||||||
|
case SDL_SCANCODE_I:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Triangle, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_KP_2:
|
||||||
|
case SDL_SCANCODE_K:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Cross, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_KP_4:
|
||||||
|
case SDL_SCANCODE_J:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Square, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_KP_6:
|
||||||
|
case SDL_SCANCODE_L:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Circle, pressed);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SDL_SCANCODE_W:
|
||||||
|
case SDL_SCANCODE_UP:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Up, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_S:
|
||||||
|
case SDL_SCANCODE_DOWN:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Down, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_A:
|
||||||
|
case SDL_SCANCODE_LEFT:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Left, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_D:
|
||||||
|
case SDL_SCANCODE_RIGHT:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Right, pressed);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SDL_SCANCODE_Q:
|
||||||
|
controller->SetButtonState(DigitalController::Button::L1, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_E:
|
||||||
|
controller->SetButtonState(DigitalController::Button::R1, pressed);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SDL_SCANCODE_1:
|
||||||
|
controller->SetButtonState(DigitalController::Button::L2, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_3:
|
||||||
|
controller->SetButtonState(DigitalController::Button::R2, pressed);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SDL_SCANCODE_RETURN:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Start, pressed);
|
||||||
|
return true;
|
||||||
|
case SDL_SCANCODE_BACKSPACE:
|
||||||
|
controller->SetButtonState(DigitalController::Button::Select, pressed);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleSDLControllerAxisEventForController(const SDL_Event* ev, DigitalController* controller)
|
||||||
|
{
|
||||||
|
// Log_DevPrintf("axis %d %d", ev->caxis.axis, ev->caxis.value);
|
||||||
|
|
||||||
|
static constexpr int deadzone = 5000;
|
||||||
|
const bool negative = (ev->caxis.value < 0);
|
||||||
|
const bool active = (std::abs(ev->caxis.value) >= deadzone);
|
||||||
|
|
||||||
|
if (ev->caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT)
|
||||||
|
{
|
||||||
|
controller->SetButtonState(DigitalController::Button::L2, active);
|
||||||
|
}
|
||||||
|
else if (ev->caxis.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
|
||||||
|
{
|
||||||
|
controller->SetButtonState(DigitalController::Button::R2, active);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DigitalController::Button negative_button, positive_button;
|
||||||
|
if (ev->caxis.axis & 1)
|
||||||
|
{
|
||||||
|
negative_button = DigitalController::Button::Up;
|
||||||
|
positive_button = DigitalController::Button::Down;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
negative_button = DigitalController::Button::Left;
|
||||||
|
positive_button = DigitalController::Button::Right;
|
||||||
|
}
|
||||||
|
|
||||||
|
controller->SetButtonState(negative_button, negative && active);
|
||||||
|
controller->SetButtonState(positive_button, !negative && active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void HandleSDLControllerButtonEventForController(const SDL_Event* ev, DigitalController* controller)
|
||||||
|
{
|
||||||
|
// Log_DevPrintf("button %d %s", ev->cbutton.button, ev->cbutton.state == SDL_PRESSED ? "pressed" : "released");
|
||||||
|
|
||||||
|
// For xbox one controller..
|
||||||
|
static constexpr std::pair<SDL_GameControllerButton, DigitalController::Button> button_mapping[] = {
|
||||||
|
{SDL_CONTROLLER_BUTTON_A, DigitalController::Button::Cross},
|
||||||
|
{SDL_CONTROLLER_BUTTON_B, DigitalController::Button::Circle},
|
||||||
|
{SDL_CONTROLLER_BUTTON_X, DigitalController::Button::Square},
|
||||||
|
{SDL_CONTROLLER_BUTTON_Y, DigitalController::Button::Triangle},
|
||||||
|
{SDL_CONTROLLER_BUTTON_BACK, DigitalController::Button::Select},
|
||||||
|
{SDL_CONTROLLER_BUTTON_START, DigitalController::Button::Start},
|
||||||
|
{SDL_CONTROLLER_BUTTON_GUIDE, DigitalController::Button::Start},
|
||||||
|
{SDL_CONTROLLER_BUTTON_LEFTSTICK, DigitalController::Button::L3},
|
||||||
|
{SDL_CONTROLLER_BUTTON_RIGHTSTICK, DigitalController::Button::R3},
|
||||||
|
{SDL_CONTROLLER_BUTTON_LEFTSHOULDER, DigitalController::Button::L1},
|
||||||
|
{SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, DigitalController::Button::R1},
|
||||||
|
{SDL_CONTROLLER_BUTTON_DPAD_UP, DigitalController::Button::Up},
|
||||||
|
{SDL_CONTROLLER_BUTTON_DPAD_DOWN, DigitalController::Button::Down},
|
||||||
|
{SDL_CONTROLLER_BUTTON_DPAD_LEFT, DigitalController::Button::Left},
|
||||||
|
{SDL_CONTROLLER_BUTTON_DPAD_RIGHT, DigitalController::Button::Right}};
|
||||||
|
|
||||||
|
for (const auto& bm : button_mapping)
|
||||||
|
{
|
||||||
|
if (bm.first == ev->cbutton.button)
|
||||||
|
{
|
||||||
|
controller->SetButtonState(bm.second, ev->cbutton.state == SDL_PRESSED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLInterface::HandleSDLEvent(const SDL_Event* event)
|
||||||
{
|
{
|
||||||
if (PassEventToImGui(event))
|
if (PassEventToImGui(event))
|
||||||
return true;
|
return;
|
||||||
|
|
||||||
switch (event->type)
|
switch (event->type)
|
||||||
{
|
{
|
||||||
|
@ -305,78 +467,67 @@ bool SDLInterface::HandleSDLEvent(const SDL_Event* event)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SDL_KEYDOWN:
|
|
||||||
case SDL_KEYUP:
|
|
||||||
return HandleSDLKeyEvent(event);
|
|
||||||
|
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
m_running = false;
|
m_running = false;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
case SDL_KEYDOWN:
|
||||||
|
case SDL_KEYUP:
|
||||||
|
HandleSDLKeyEvent(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEADDED:
|
||||||
|
{
|
||||||
|
auto iter = m_sdl_controllers.find(event->cdevice.which);
|
||||||
|
if (iter == m_sdl_controllers.end())
|
||||||
|
{
|
||||||
|
SDL_GameController* gcontroller = SDL_GameControllerOpen(event->cdevice.which);
|
||||||
|
if (gcontroller)
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Controller %s inserted", SDL_GameControllerName(gcontroller));
|
||||||
|
m_sdl_controllers.emplace(event->cdevice.which, gcontroller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEREMOVED:
|
||||||
|
{
|
||||||
|
auto iter = m_sdl_controllers.find(event->cdevice.which);
|
||||||
|
if (iter != m_sdl_controllers.end())
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Controller %s removed", SDL_GameControllerName(iter->second));
|
||||||
|
SDL_GameControllerClose(iter->second);
|
||||||
|
m_sdl_controllers.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERAXISMOTION:
|
||||||
|
{
|
||||||
|
if (m_controller)
|
||||||
|
HandleSDLControllerAxisEventForController(event, m_controller.get());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_CONTROLLERBUTTONDOWN:
|
||||||
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
|
{
|
||||||
|
if (m_controller)
|
||||||
|
HandleSDLControllerButtonEventForController(event, m_controller.get());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDLInterface::HandleSDLKeyEvent(const SDL_Event* event)
|
void SDLInterface::HandleSDLKeyEvent(const SDL_Event* event)
|
||||||
{
|
{
|
||||||
|
if (m_controller && HandleSDLKeyEventForController(event, m_controller.get()))
|
||||||
|
return;
|
||||||
|
|
||||||
const bool pressed = (event->type == SDL_KEYDOWN);
|
const bool pressed = (event->type == SDL_KEYDOWN);
|
||||||
switch (event->key.keysym.scancode)
|
switch (event->key.keysym.scancode)
|
||||||
{
|
{
|
||||||
case SDL_SCANCODE_KP_8:
|
|
||||||
case SDL_SCANCODE_I:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Triangle, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_KP_2:
|
|
||||||
case SDL_SCANCODE_K:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Cross, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_KP_4:
|
|
||||||
case SDL_SCANCODE_J:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Square, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_KP_6:
|
|
||||||
case SDL_SCANCODE_L:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Circle, pressed);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_W:
|
|
||||||
case SDL_SCANCODE_UP:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Up, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_S:
|
|
||||||
case SDL_SCANCODE_DOWN:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Down, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_A:
|
|
||||||
case SDL_SCANCODE_LEFT:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Left, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_D:
|
|
||||||
case SDL_SCANCODE_RIGHT:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Right, pressed);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_Q:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::L1, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_E:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::R1, pressed);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_1:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::L2, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_3:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::R2, pressed);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_RETURN:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Start, pressed);
|
|
||||||
return true;
|
|
||||||
case SDL_SCANCODE_BACKSPACE:
|
|
||||||
m_controller->SetButtonState(DigitalController::Button::Select, pressed);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case SDL_SCANCODE_F1:
|
case SDL_SCANCODE_F1:
|
||||||
case SDL_SCANCODE_F2:
|
case SDL_SCANCODE_F2:
|
||||||
case SDL_SCANCODE_F3:
|
case SDL_SCANCODE_F3:
|
||||||
|
@ -403,12 +554,7 @@ bool SDLInterface::HandleSDLKeyEvent(const SDL_Event* event)
|
||||||
UpdateAudioVisualSync();
|
UpdateAudioVisualSync();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SDLInterface::PassEventToImGui(const SDL_Event* event)
|
bool SDLInterface::PassEventToImGui(const SDL_Event* event)
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
@ -55,6 +56,9 @@ private:
|
||||||
bool CreateAudioStream();
|
bool CreateAudioStream();
|
||||||
void UpdateAudioVisualSync();
|
void UpdateAudioVisualSync();
|
||||||
|
|
||||||
|
void OpenGameControllers();
|
||||||
|
void CloseGameControllers();
|
||||||
|
|
||||||
bool InitializeSystem(const char* filename = nullptr, const char* exp1_filename = nullptr);
|
bool InitializeSystem(const char* filename = nullptr, const char* exp1_filename = nullptr);
|
||||||
void ConnectDevices();
|
void ConnectDevices();
|
||||||
|
|
||||||
|
@ -69,8 +73,8 @@ private:
|
||||||
void DoLoadState(u32 index);
|
void DoLoadState(u32 index);
|
||||||
void DoSaveState(u32 index);
|
void DoSaveState(u32 index);
|
||||||
|
|
||||||
bool HandleSDLEvent(const SDL_Event* event);
|
void HandleSDLEvent(const SDL_Event* event);
|
||||||
bool HandleSDLKeyEvent(const SDL_Event* event);
|
void HandleSDLKeyEvent(const SDL_Event* event);
|
||||||
bool PassEventToImGui(const SDL_Event* event);
|
bool PassEventToImGui(const SDL_Event* event);
|
||||||
void Render();
|
void Render();
|
||||||
void RenderDisplay();
|
void RenderDisplay();
|
||||||
|
@ -99,6 +103,8 @@ private:
|
||||||
std::deque<OSDMessage> m_osd_messages;
|
std::deque<OSDMessage> m_osd_messages;
|
||||||
std::mutex m_osd_messages_lock;
|
std::mutex m_osd_messages_lock;
|
||||||
|
|
||||||
|
std::map<int, SDL_GameController*> m_sdl_controllers;
|
||||||
|
|
||||||
std::shared_ptr<DigitalController> m_controller;
|
std::shared_ptr<DigitalController> m_controller;
|
||||||
std::shared_ptr<MemoryCard> m_memory_card;
|
std::shared_ptr<MemoryCard> m_memory_card;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue