Qt: Implement controller interface/binding
This commit is contained in:
parent
6a1206dde7
commit
59cf799491
|
@ -39,6 +39,9 @@ public:
|
||||||
/// Returns the game list.
|
/// Returns the game list.
|
||||||
ALWAYS_INLINE const GameList* GetGameList() const { return m_game_list.get(); }
|
ALWAYS_INLINE const GameList* GetGameList() const { return m_game_list.get(); }
|
||||||
|
|
||||||
|
/// Access to emulated system.
|
||||||
|
ALWAYS_INLINE System* GetSystem() const { return m_system.get(); }
|
||||||
|
|
||||||
bool BootSystemFromFile(const char* filename);
|
bool BootSystemFromFile(const char* filename);
|
||||||
bool BootSystemFromBIOS();
|
bool BootSystemFromBIOS();
|
||||||
void PauseSystem(bool paused);
|
void PauseSystem(bool paused);
|
||||||
|
|
|
@ -43,7 +43,7 @@ add_executable(duckstation-qt
|
||||||
settingsdialog.ui
|
settingsdialog.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(duckstation-qt PRIVATE core common imgui glad minizip Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network)
|
target_link_libraries(duckstation-qt PRIVATE frontend-common core common imgui glad minizip Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(duckstation-qt PRIVATE
|
target_sources(duckstation-qt PRIVATE
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "inputbindingwidgets.h"
|
#include "inputbindingwidgets.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
#include "frontend-common/sdl_controller_interface.h"
|
||||||
#include "qthostinterface.h"
|
#include "qthostinterface.h"
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
#include <QtCore/QTimer>
|
#include <QtCore/QTimer>
|
||||||
|
@ -81,6 +82,49 @@ void InputButtonBindingWidget::onInputListenTimerTimeout()
|
||||||
setText(tr("Push Button... [%1]").arg(m_input_listen_remaining_seconds));
|
setText(tr("Push Button... [%1]").arg(m_input_listen_remaining_seconds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputButtonBindingWidget::hookControllerInput()
|
||||||
|
{
|
||||||
|
m_host_interface->enableBackgroundControllerPolling();
|
||||||
|
g_sdl_controller_interface.SetHook([this](const SDLControllerInterface::Hook& ei) {
|
||||||
|
if (ei.type == SDLControllerInterface::Hook::Type::Axis)
|
||||||
|
{
|
||||||
|
// TODO: this probably should consider the "last value"
|
||||||
|
QMetaObject::invokeMethod(this, "bindToControllerAxis", Q_ARG(int, ei.controller_index),
|
||||||
|
Q_ARG(int, ei.button_or_axis_number), Q_ARG(bool, ei.value > 0));
|
||||||
|
return SDLControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||||
|
}
|
||||||
|
else if (ei.type == SDLControllerInterface::Hook::Type::Button && ei.value > 0.0f)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "bindToControllerButton", Q_ARG(int, ei.controller_index),
|
||||||
|
Q_ARG(int, ei.button_or_axis_number));
|
||||||
|
return SDLControllerInterface::Hook::CallbackResult::StopMonitoring;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDLControllerInterface::Hook::CallbackResult::ContinueMonitoring;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputButtonBindingWidget::unhookControllerInput()
|
||||||
|
{
|
||||||
|
g_sdl_controller_interface.ClearHook();
|
||||||
|
m_host_interface->disableBackgroundControllerPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputButtonBindingWidget::bindToControllerAxis(int controller_index, int axis_index, bool positive)
|
||||||
|
{
|
||||||
|
m_new_binding_value =
|
||||||
|
QStringLiteral("Controller%1/%2Axis%3").arg(controller_index).arg(positive ? '+' : '-').arg(axis_index);
|
||||||
|
setNewBinding();
|
||||||
|
stopListeningForInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputButtonBindingWidget::bindToControllerButton(int controller_index, int button_index)
|
||||||
|
{
|
||||||
|
m_new_binding_value = QStringLiteral("Controller%1/Button%2").arg(controller_index).arg(button_index);
|
||||||
|
setNewBinding();
|
||||||
|
stopListeningForInput();
|
||||||
|
}
|
||||||
|
|
||||||
void InputButtonBindingWidget::startListeningForInput()
|
void InputButtonBindingWidget::startListeningForInput()
|
||||||
{
|
{
|
||||||
m_input_listen_timer = new QTimer(this);
|
m_input_listen_timer = new QTimer(this);
|
||||||
|
@ -95,6 +139,7 @@ void InputButtonBindingWidget::startListeningForInput()
|
||||||
installEventFilter(this);
|
installEventFilter(this);
|
||||||
grabKeyboard();
|
grabKeyboard();
|
||||||
grabMouse();
|
grabMouse();
|
||||||
|
hookControllerInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputButtonBindingWidget::stopListeningForInput()
|
void InputButtonBindingWidget::stopListeningForInput()
|
||||||
|
@ -103,6 +148,7 @@ void InputButtonBindingWidget::stopListeningForInput()
|
||||||
delete m_input_listen_timer;
|
delete m_input_listen_timer;
|
||||||
m_input_listen_timer = nullptr;
|
m_input_listen_timer = nullptr;
|
||||||
|
|
||||||
|
unhookControllerInput();
|
||||||
releaseMouse();
|
releaseMouse();
|
||||||
releaseKeyboard();
|
releaseKeyboard();
|
||||||
removeEventFilter(this);
|
removeEventFilter(this);
|
||||||
|
|
|
@ -20,12 +20,16 @@ protected:
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void onPressed();
|
void onPressed();
|
||||||
void onInputListenTimerTimeout();
|
void onInputListenTimerTimeout();
|
||||||
|
void bindToControllerAxis(int controller_index, int axis_index, bool positive);
|
||||||
|
void bindToControllerButton(int controller_index, int button_index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
|
bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
|
||||||
void startListeningForInput();
|
void startListeningForInput();
|
||||||
void stopListeningForInput();
|
void stopListeningForInput();
|
||||||
void setNewBinding();
|
void setNewBinding();
|
||||||
|
void hookControllerInput();
|
||||||
|
void unhookControllerInput();
|
||||||
|
|
||||||
QtHostInterface* m_host_interface;
|
QtHostInterface* m_host_interface;
|
||||||
QString m_setting_name;
|
QString m_setting_name;
|
||||||
|
|
|
@ -8,12 +8,14 @@
|
||||||
#include "core/game_list.h"
|
#include "core/game_list.h"
|
||||||
#include "core/gpu.h"
|
#include "core/gpu.h"
|
||||||
#include "core/system.h"
|
#include "core/system.h"
|
||||||
|
#include "frontend-common/sdl_controller_interface.h"
|
||||||
#include "qtsettingsinterface.h"
|
#include "qtsettingsinterface.h"
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QDateTime>
|
#include <QtCore/QDateTime>
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtCore/QEventLoop>
|
#include <QtCore/QEventLoop>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
#include <QtWidgets/QMenu>
|
#include <QtWidgets/QMenu>
|
||||||
#include <QtWidgets/QMessageBox>
|
#include <QtWidgets/QMessageBox>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -28,7 +30,6 @@ QtHostInterface::QtHostInterface(QObject* parent)
|
||||||
{
|
{
|
||||||
checkSettings();
|
checkSettings();
|
||||||
refreshGameList();
|
refreshGameList();
|
||||||
doUpdateInputMap();
|
|
||||||
createThread();
|
createThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,6 +284,7 @@ void QtHostInterface::OnSystemCreated()
|
||||||
HostInterface::OnSystemCreated();
|
HostInterface::OnSystemCreated();
|
||||||
|
|
||||||
wakeThread();
|
wakeThread();
|
||||||
|
destroyBackgroundControllerPollTimer();
|
||||||
|
|
||||||
emit emulationStarted();
|
emit emulationStarted();
|
||||||
}
|
}
|
||||||
|
@ -301,6 +303,9 @@ void QtHostInterface::OnSystemDestroyed()
|
||||||
{
|
{
|
||||||
HostInterface::OnSystemDestroyed();
|
HostInterface::OnSystemDestroyed();
|
||||||
|
|
||||||
|
if (m_background_controller_polling_enable_count > 0)
|
||||||
|
createBackgroundControllerPollTimer();
|
||||||
|
|
||||||
emit emulationStopped();
|
emit emulationStopped();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +355,9 @@ void QtHostInterface::updateInputMap()
|
||||||
void QtHostInterface::doUpdateInputMap()
|
void QtHostInterface::doUpdateInputMap()
|
||||||
{
|
{
|
||||||
m_keyboard_input_handlers.clear();
|
m_keyboard_input_handlers.clear();
|
||||||
|
g_sdl_controller_interface.ClearControllerBindings();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_qsettings_mutex);
|
||||||
updateControllerInputMap();
|
updateControllerInputMap();
|
||||||
updateHotkeyInputMap();
|
updateHotkeyInputMap();
|
||||||
}
|
}
|
||||||
|
@ -493,9 +500,41 @@ void QtHostInterface::addButtonToInputMap(const QString& binding, InputButtonHan
|
||||||
|
|
||||||
m_keyboard_input_handlers.emplace(key_id.value(), std::move(handler));
|
m_keyboard_input_handlers.emplace(key_id.value(), std::move(handler));
|
||||||
}
|
}
|
||||||
|
else if (device.startsWith(QStringLiteral("Controller")))
|
||||||
|
{
|
||||||
|
bool controller_index_okay;
|
||||||
|
const int controller_index = device.mid(10).toInt(&controller_index_okay);
|
||||||
|
|
||||||
|
if (!controller_index_okay || controller_index < 0)
|
||||||
|
{
|
||||||
|
qWarning() << "Malformed controller binding: " << binding;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (button.startsWith(QStringLiteral("Button")))
|
||||||
|
{
|
||||||
|
bool button_index_okay;
|
||||||
|
const int button_index = button.mid(6).toInt(&button_index_okay);
|
||||||
|
if (!button_index_okay ||
|
||||||
|
!g_sdl_controller_interface.BindControllerButton(controller_index, button_index, std::move(handler)))
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to bind " << binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (button.startsWith(QStringLiteral("+Axis")) || button.startsWith(QStringLiteral("-Axis")))
|
||||||
|
{
|
||||||
|
bool axis_index_okay;
|
||||||
|
const int axis_index = button.mid(5).toInt(&axis_index_okay);
|
||||||
|
const bool positive = (button[0] == '+');
|
||||||
|
if (!axis_index_okay || !g_sdl_controller_interface.BindControllerAxisToButton(controller_index, axis_index,
|
||||||
|
positive, std::move(handler)))
|
||||||
|
{
|
||||||
|
qWarning() << "Failed to bind " << binding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
qWarning() << "Unknown input device: " << device;
|
qWarning() << "Unknown input device: " << binding;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -659,6 +698,63 @@ void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done
|
||||||
SaveState(global, slot);
|
SaveState(global, slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QtHostInterface::enableBackgroundControllerPolling()
|
||||||
|
{
|
||||||
|
if (!isOnWorkerThread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "enableBackgroundControllerPolling", Qt::BlockingQueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_background_controller_polling_enable_count++ > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_system)
|
||||||
|
{
|
||||||
|
createBackgroundControllerPollTimer();
|
||||||
|
|
||||||
|
// drain the event queue so we don't get events late
|
||||||
|
g_sdl_controller_interface.PumpSDLEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHostInterface::disableBackgroundControllerPolling()
|
||||||
|
{
|
||||||
|
if (!isOnWorkerThread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "disableBackgroundControllerPolling");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(m_background_controller_polling_enable_count > 0);
|
||||||
|
if (--m_background_controller_polling_enable_count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_system)
|
||||||
|
destroyBackgroundControllerPollTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHostInterface::doBackgroundControllerPoll()
|
||||||
|
{
|
||||||
|
g_sdl_controller_interface.PumpSDLEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHostInterface::createBackgroundControllerPollTimer()
|
||||||
|
{
|
||||||
|
DebugAssert(!m_background_controller_polling_timer);
|
||||||
|
m_background_controller_polling_timer = new QTimer(this);
|
||||||
|
m_background_controller_polling_timer->setSingleShot(false);
|
||||||
|
m_background_controller_polling_timer->setTimerType(Qt::VeryCoarseTimer);
|
||||||
|
connect(m_background_controller_polling_timer, &QTimer::timeout, this, &QtHostInterface::doBackgroundControllerPoll);
|
||||||
|
m_background_controller_polling_timer->start(BACKGROUND_CONTROLLER_POLLING_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QtHostInterface::destroyBackgroundControllerPollTimer()
|
||||||
|
{
|
||||||
|
delete m_background_controller_polling_timer;
|
||||||
|
m_background_controller_polling_timer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void QtHostInterface::createThread()
|
void QtHostInterface::createThread()
|
||||||
{
|
{
|
||||||
m_original_thread = QThread::currentThread();
|
m_original_thread = QThread::currentThread();
|
||||||
|
@ -685,6 +781,12 @@ void QtHostInterface::threadEntryPoint()
|
||||||
{
|
{
|
||||||
m_worker_thread_event_loop = new QEventLoop();
|
m_worker_thread_event_loop = new QEventLoop();
|
||||||
|
|
||||||
|
// set up controller interface and immediate poll to pick up the controller attached events
|
||||||
|
g_sdl_controller_interface.Initialize(this, true);
|
||||||
|
g_sdl_controller_interface.PumpSDLEvents();
|
||||||
|
|
||||||
|
doUpdateInputMap();
|
||||||
|
|
||||||
// TODO: Event which flags the thread as ready
|
// TODO: Event which flags the thread as ready
|
||||||
while (!m_shutdown_flag.load())
|
while (!m_shutdown_flag.load())
|
||||||
{
|
{
|
||||||
|
@ -710,10 +812,14 @@ void QtHostInterface::threadEntryPoint()
|
||||||
m_system->Throttle();
|
m_system->Throttle();
|
||||||
|
|
||||||
m_worker_thread_event_loop->processEvents(QEventLoop::AllEvents);
|
m_worker_thread_event_loop->processEvents(QEventLoop::AllEvents);
|
||||||
|
g_sdl_controller_interface.PumpSDLEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_system.reset();
|
m_system.reset();
|
||||||
m_audio_stream.reset();
|
m_audio_stream.reset();
|
||||||
|
|
||||||
|
g_sdl_controller_interface.Shutdown();
|
||||||
|
|
||||||
delete m_worker_thread_event_loop;
|
delete m_worker_thread_event_loop;
|
||||||
m_worker_thread_event_loop = nullptr;
|
m_worker_thread_event_loop = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ class ByteStream;
|
||||||
class QEventLoop;
|
class QEventLoop;
|
||||||
class QMenu;
|
class QMenu;
|
||||||
class QWidget;
|
class QWidget;
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
class GameList;
|
class GameList;
|
||||||
|
|
||||||
|
@ -88,11 +89,19 @@ public Q_SLOTS:
|
||||||
void loadState(bool global, qint32 slot);
|
void loadState(bool global, qint32 slot);
|
||||||
void saveState(bool global, qint32 slot, bool block_until_done = false);
|
void saveState(bool global, qint32 slot, bool block_until_done = false);
|
||||||
|
|
||||||
|
/// Enables controller polling even without a system active. Must be matched by a call to
|
||||||
|
/// disableBackgroundControllerPolling.
|
||||||
|
void enableBackgroundControllerPolling();
|
||||||
|
|
||||||
|
/// Disables background controller polling.
|
||||||
|
void disableBackgroundControllerPolling();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void doStopThread();
|
void doStopThread();
|
||||||
void doUpdateInputMap();
|
void doUpdateInputMap();
|
||||||
void doHandleKeyEvent(int key, bool pressed);
|
void doHandleKeyEvent(int key, bool pressed);
|
||||||
void onDisplayWindowResized(int width, int height);
|
void onDisplayWindowResized(int width, int height);
|
||||||
|
void doBackgroundControllerPoll();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool AcquireHostDisplay() override;
|
bool AcquireHostDisplay() override;
|
||||||
|
@ -107,6 +116,12 @@ protected:
|
||||||
void OnControllerTypeChanged(u32 slot) override;
|
void OnControllerTypeChanged(u32 slot) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
enum : u32
|
||||||
|
{
|
||||||
|
BACKGROUND_CONTROLLER_POLLING_INTERVAL =
|
||||||
|
100 /// Interval at which the controllers are polled when the system is not active.
|
||||||
|
};
|
||||||
|
|
||||||
using InputButtonHandler = std::function<void(bool)>;
|
using InputButtonHandler = std::function<void(bool)>;
|
||||||
|
|
||||||
class Thread : public QThread
|
class Thread : public QThread
|
||||||
|
@ -124,6 +139,8 @@ private:
|
||||||
|
|
||||||
void checkSettings();
|
void checkSettings();
|
||||||
void updateQSettingsFromCoreSettings();
|
void updateQSettingsFromCoreSettings();
|
||||||
|
void createBackgroundControllerPollTimer();
|
||||||
|
void destroyBackgroundControllerPollTimer();
|
||||||
|
|
||||||
void updateControllerInputMap();
|
void updateControllerInputMap();
|
||||||
void updateHotkeyInputMap();
|
void updateHotkeyInputMap();
|
||||||
|
@ -145,4 +162,7 @@ private:
|
||||||
|
|
||||||
// input key maps, todo hotkeys
|
// input key maps, todo hotkeys
|
||||||
std::map<int, InputButtonHandler> m_keyboard_input_handlers;
|
std::map<int, InputButtonHandler> m_keyboard_input_handlers;
|
||||||
|
|
||||||
|
QTimer* m_background_controller_polling_timer = nullptr;
|
||||||
|
u32 m_background_controller_polling_enable_count = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,8 @@ add_library(frontend-common
|
||||||
imgui_styles.h
|
imgui_styles.h
|
||||||
sdl_audio_stream.cpp
|
sdl_audio_stream.cpp
|
||||||
sdl_audio_stream.h
|
sdl_audio_stream.h
|
||||||
|
sdl_controller_interface.cpp
|
||||||
|
sdl_controller_interface.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(frontend-common PRIVATE ${SDL2_INCLUDE_DIRS})
|
target_include_directories(frontend-common PRIVATE ${SDL2_INCLUDE_DIRS})
|
||||||
|
|
|
@ -49,11 +49,13 @@
|
||||||
<ClCompile Include="icon.cpp" />
|
<ClCompile Include="icon.cpp" />
|
||||||
<ClCompile Include="imgui_styles.cpp" />
|
<ClCompile Include="imgui_styles.cpp" />
|
||||||
<ClCompile Include="sdl_audio_stream.cpp" />
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
|
<ClCompile Include="sdl_controller_interface.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="icon.h" />
|
<ClInclude Include="icon.h" />
|
||||||
<ClInclude Include="imgui_styles.h" />
|
<ClInclude Include="imgui_styles.h" />
|
||||||
<ClInclude Include="sdl_audio_stream.h" />
|
<ClInclude Include="sdl_audio_stream.h" />
|
||||||
|
<ClInclude Include="sdl_controller_interface.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="font_roboto_regular.inl" />
|
<None Include="font_roboto_regular.inl" />
|
||||||
|
|
|
@ -4,11 +4,13 @@
|
||||||
<ClCompile Include="icon.cpp" />
|
<ClCompile Include="icon.cpp" />
|
||||||
<ClCompile Include="imgui_styles.cpp" />
|
<ClCompile Include="imgui_styles.cpp" />
|
||||||
<ClCompile Include="sdl_audio_stream.cpp" />
|
<ClCompile Include="sdl_audio_stream.cpp" />
|
||||||
|
<ClCompile Include="sdl_controller_interface.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="icon.h" />
|
<ClInclude Include="icon.h" />
|
||||||
<ClInclude Include="imgui_styles.h" />
|
<ClInclude Include="imgui_styles.h" />
|
||||||
<ClInclude Include="sdl_audio_stream.h" />
|
<ClInclude Include="sdl_audio_stream.h" />
|
||||||
|
<ClInclude Include="sdl_controller_interface.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="font_roboto_regular.inl" />
|
<None Include="font_roboto_regular.inl" />
|
||||||
|
|
|
@ -0,0 +1,425 @@
|
||||||
|
#include "sdl_controller_interface.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "core/controller.h"
|
||||||
|
#include "core/host_interface.h"
|
||||||
|
#include "core/system.h"
|
||||||
|
#include <SDL.h>
|
||||||
|
Log_SetChannel(SDLControllerInterface);
|
||||||
|
|
||||||
|
SDLControllerInterface g_sdl_controller_interface;
|
||||||
|
|
||||||
|
SDLControllerInterface::SDLControllerInterface() = default;
|
||||||
|
|
||||||
|
SDLControllerInterface::~SDLControllerInterface()
|
||||||
|
{
|
||||||
|
Assert(m_controllers.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::Initialize(HostInterface* host_interface, bool init_sdl)
|
||||||
|
{
|
||||||
|
if (init_sdl)
|
||||||
|
{
|
||||||
|
if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("SDL_Init() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we should open the controllers as the connected events come in, so no need to do any more here
|
||||||
|
m_host_interface = host_interface;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::Shutdown()
|
||||||
|
{
|
||||||
|
while (!m_controllers.empty())
|
||||||
|
CloseGameController(m_controllers.begin()->first);
|
||||||
|
|
||||||
|
if (m_sdl_initialized_by_us)
|
||||||
|
{
|
||||||
|
SDL_Quit();
|
||||||
|
m_sdl_initialized_by_us = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_host_interface = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::PumpSDLEvents()
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
SDL_Event ev;
|
||||||
|
if (SDL_PollEvent(&ev))
|
||||||
|
ProcessSDLEvent(&ev);
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::ProcessSDLEvent(const SDL_Event* event)
|
||||||
|
{
|
||||||
|
switch (event->type)
|
||||||
|
{
|
||||||
|
case SDL_CONTROLLERDEVICEADDED:
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Controller %d inserted", event->cdevice.which);
|
||||||
|
OpenGameController(event->cdevice.which);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SDL_CONTROLLERDEVICEREMOVED:
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("Controller %d removed", event->cdevice.which);
|
||||||
|
CloseGameController(event->cdevice.which);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case SDL_CONTROLLERAXISMOTION:
|
||||||
|
return HandleControllerAxisEvent(event);
|
||||||
|
|
||||||
|
case SDL_CONTROLLERBUTTONDOWN:
|
||||||
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
|
return HandleControllerButtonEvent(event);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System* SDLControllerInterface::GetSystem() const
|
||||||
|
{
|
||||||
|
return m_host_interface->GetSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
Controller* SDLControllerInterface::GetController(u32 slot) const
|
||||||
|
{
|
||||||
|
System* system = GetSystem();
|
||||||
|
return system ? system->GetController(slot) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::SetHook(Hook::Callback callback)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(m_event_intercept_mutex);
|
||||||
|
Assert(!m_event_intercept_callback);
|
||||||
|
m_event_intercept_callback = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::ClearHook()
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(m_event_intercept_mutex);
|
||||||
|
if (m_event_intercept_callback)
|
||||||
|
m_event_intercept_callback = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::DoEventHook(Hook::Type type, int controller_index, int button_or_axis_number, float value)
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(m_event_intercept_mutex);
|
||||||
|
if (!m_event_intercept_callback)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const Hook ei{type, controller_index, button_or_axis_number, value};
|
||||||
|
const Hook::CallbackResult action = m_event_intercept_callback(ei);
|
||||||
|
if (action == Hook::CallbackResult::StopMonitoring)
|
||||||
|
m_event_intercept_callback = {};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::OpenGameController(int index)
|
||||||
|
{
|
||||||
|
if (m_controllers.find(index) != m_controllers.end())
|
||||||
|
CloseGameController(index);
|
||||||
|
|
||||||
|
SDL_GameController* gcontroller = SDL_GameControllerOpen(index);
|
||||||
|
if (!gcontroller)
|
||||||
|
{
|
||||||
|
Log_WarningPrintf("Failed to open controller %d", index);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log_InfoPrintf("Opened controller %d: %s", index, SDL_GameControllerName(gcontroller));
|
||||||
|
|
||||||
|
ControllerData cd = {};
|
||||||
|
cd.controller = gcontroller;
|
||||||
|
|
||||||
|
SDL_Joystick* joystick = SDL_GameControllerGetJoystick(gcontroller);
|
||||||
|
if (joystick)
|
||||||
|
{
|
||||||
|
SDL_Haptic* haptic = SDL_HapticOpenFromJoystick(joystick);
|
||||||
|
if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) == 0)
|
||||||
|
cd.haptic = haptic;
|
||||||
|
else
|
||||||
|
SDL_HapticClose(haptic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cd.haptic)
|
||||||
|
Log_InfoPrintf("Rumble is supported on '%s'", SDL_GameControllerName(gcontroller));
|
||||||
|
else
|
||||||
|
Log_WarningPrintf("Rumble is not supported on '%s'", SDL_GameControllerName(gcontroller));
|
||||||
|
|
||||||
|
m_controllers.emplace(index, cd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::CloseGameControllers()
|
||||||
|
{
|
||||||
|
while (!m_controllers.empty())
|
||||||
|
CloseGameController(m_controllers.begin()->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::CloseGameController(int index)
|
||||||
|
{
|
||||||
|
auto it = m_controllers.find(index);
|
||||||
|
if (it == m_controllers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (it->second.haptic)
|
||||||
|
SDL_HapticClose(static_cast<SDL_Haptic*>(it->second.haptic));
|
||||||
|
|
||||||
|
SDL_GameControllerClose(static_cast<SDL_GameController*>(it->second.controller));
|
||||||
|
m_controllers.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::ClearControllerBindings()
|
||||||
|
{
|
||||||
|
for (auto& it : m_controllers)
|
||||||
|
{
|
||||||
|
for (AxisCallback& ac : it.second.axis_mapping)
|
||||||
|
ac = {};
|
||||||
|
for (ButtonCallback& bc : it.second.button_mapping)
|
||||||
|
bc = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::BindControllerAxis(int controller_index, int axis_number, AxisCallback callback)
|
||||||
|
{
|
||||||
|
auto it = m_controllers.find(controller_index);
|
||||||
|
if (it == m_controllers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (axis_number < 0 || axis_number >= MAX_NUM_AXISES)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
it->second.axis_mapping[axis_number] = std::move(callback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::BindControllerButton(int controller_index, int button_number, ButtonCallback callback)
|
||||||
|
{
|
||||||
|
auto it = m_controllers.find(controller_index);
|
||||||
|
if (it == m_controllers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (button_number < 0 || button_number >= MAX_NUM_BUTTONS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
it->second.button_mapping[button_number] = std::move(callback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::BindControllerAxisToButton(int controller_index, int axis_number, bool direction,
|
||||||
|
ButtonCallback callback)
|
||||||
|
{
|
||||||
|
auto it = m_controllers.find(controller_index);
|
||||||
|
if (it == m_controllers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (axis_number < 0 || axis_number >= MAX_NUM_AXISES)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
it->second.axis_button_mapping[axis_number][BoolToUInt8(direction)] = std::move(callback);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::SetDefaultBindings()
|
||||||
|
{
|
||||||
|
ClearControllerBindings();
|
||||||
|
|
||||||
|
const ControllerType type = m_host_interface->GetSettings().controller_types[0];
|
||||||
|
if (type == ControllerType::None || m_controllers.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int first_controller_index = m_controllers.begin()->first;
|
||||||
|
|
||||||
|
#define SET_AXIS_MAP(axis, name) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
std::optional<s32> code = Controller::GetAxisCodeByName(type, name); \
|
||||||
|
if (code) \
|
||||||
|
{ \
|
||||||
|
const s32 code_value = code.value(); \
|
||||||
|
BindControllerAxis(first_controller_index, axis, [this, code_value](float value) { \
|
||||||
|
Controller* controller = GetController(0); \
|
||||||
|
if (controller) \
|
||||||
|
controller->SetAxisState(code_value, value); \
|
||||||
|
}); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define SET_BUTTON_MAP(button, name) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
std::optional<s32> code = Controller::GetButtonCodeByName(type, name); \
|
||||||
|
if (code) \
|
||||||
|
{ \
|
||||||
|
const s32 code_value = code.value(); \
|
||||||
|
BindControllerButton(first_controller_index, button, [this, code_value](bool pressed) { \
|
||||||
|
Controller* controller = GetController(0); \
|
||||||
|
if (controller) \
|
||||||
|
controller->SetButtonState(code_value, pressed); \
|
||||||
|
}); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define SET_AXIS_BUTTON_MAP(axis, direction, name) \
|
||||||
|
do \
|
||||||
|
{ \
|
||||||
|
std::optional<s32> code = Controller::GetButtonCodeByName(type, name); \
|
||||||
|
if (code) \
|
||||||
|
{ \
|
||||||
|
const s32 code_value = code.value(); \
|
||||||
|
BindControllerAxisToButton(first_controller_index, axis, direction, [this, code_value](bool pressed) { \
|
||||||
|
Controller* controller = GetController(0); \
|
||||||
|
if (controller) \
|
||||||
|
controller->SetButtonState(code_value, pressed); \
|
||||||
|
}); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
SET_AXIS_MAP(SDL_CONTROLLER_AXIS_LEFTX, "LeftX");
|
||||||
|
SET_AXIS_MAP(SDL_CONTROLLER_AXIS_LEFTY, "LeftY");
|
||||||
|
SET_AXIS_MAP(SDL_CONTROLLER_AXIS_RIGHTX, "RightX");
|
||||||
|
SET_AXIS_MAP(SDL_CONTROLLER_AXIS_RIGHTY, "RightY");
|
||||||
|
SET_AXIS_MAP(SDL_CONTROLLER_AXIS_TRIGGERLEFT, "LeftTrigger");
|
||||||
|
SET_AXIS_MAP(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, "RightTrigger");
|
||||||
|
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_DPAD_UP, "Up");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_DPAD_DOWN, "Down");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_DPAD_LEFT, "Left");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_DPAD_RIGHT, "Right");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_Y, "Triangle");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_A, "Cross");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_X, "Square");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_B, "Circle");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, "L1");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, "R1");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_LEFTSTICK, "L3");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_RIGHTSTICK, "R3");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_START, "Start");
|
||||||
|
SET_BUTTON_MAP(SDL_CONTROLLER_BUTTON_BACK, "Select");
|
||||||
|
|
||||||
|
// fallback axis -> button mappings
|
||||||
|
SET_AXIS_BUTTON_MAP(SDL_CONTROLLER_AXIS_LEFTX, false, "Left");
|
||||||
|
SET_AXIS_BUTTON_MAP(SDL_CONTROLLER_AXIS_LEFTX, true, "Right");
|
||||||
|
SET_AXIS_BUTTON_MAP(SDL_CONTROLLER_AXIS_LEFTY, false, "Up");
|
||||||
|
SET_AXIS_BUTTON_MAP(SDL_CONTROLLER_AXIS_LEFTY, true, "Down");
|
||||||
|
SET_AXIS_BUTTON_MAP(SDL_CONTROLLER_AXIS_TRIGGERLEFT, true, "L2");
|
||||||
|
SET_AXIS_BUTTON_MAP(SDL_CONTROLLER_AXIS_TRIGGERRIGHT, true, "R2");
|
||||||
|
|
||||||
|
#undef SET_AXIS_MAP
|
||||||
|
#undef SET_BUTTON_MAP
|
||||||
|
#undef SET_AXIS_BUTTON_MAP
|
||||||
|
|
||||||
|
// TODO: L2/R2 -> buttons
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::HandleControllerAxisEvent(const SDL_Event* ev)
|
||||||
|
{
|
||||||
|
Log_DebugPrintf("controller %d axis %d %d", ev->caxis.which, ev->caxis.axis, ev->caxis.value);
|
||||||
|
|
||||||
|
// TODO: Make deadzone customizable.
|
||||||
|
static constexpr float deadzone = 8192.0f / 32768.0f;
|
||||||
|
|
||||||
|
const float value = static_cast<float>(ev->caxis.value) / (ev->caxis.value < 0 ? 32768.0f : 32767.0f);
|
||||||
|
const bool outside_deadzone = (std::abs(value) >= deadzone);
|
||||||
|
|
||||||
|
// only send monitor events if it's outside of the deadzone, otherwise it's really hard to bind
|
||||||
|
if (outside_deadzone && DoEventHook(Hook::Type::Axis, ev->caxis.which, ev->caxis.axis, value))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto it = m_controllers.find(ev->caxis.which);
|
||||||
|
if (it == m_controllers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const ControllerData& cd = it->second;
|
||||||
|
const AxisCallback& cb = cd.axis_mapping[ev->caxis.axis];
|
||||||
|
if (cb)
|
||||||
|
{
|
||||||
|
cb(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the other direction to false so large movements don't leave the opposite on
|
||||||
|
const bool positive = (value >= 0.0f);
|
||||||
|
const ButtonCallback& other_button_cb = cd.axis_button_mapping[ev->caxis.axis][BoolToUInt8(!positive)];
|
||||||
|
const ButtonCallback& button_cb = cd.axis_button_mapping[ev->caxis.axis][BoolToUInt8(positive)];
|
||||||
|
if (button_cb)
|
||||||
|
{
|
||||||
|
button_cb(outside_deadzone);
|
||||||
|
if (other_button_cb)
|
||||||
|
other_button_cb(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (other_button_cb)
|
||||||
|
{
|
||||||
|
other_button_cb(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SDLControllerInterface::HandleControllerButtonEvent(const SDL_Event* ev)
|
||||||
|
{
|
||||||
|
Log_DebugPrintf("controller %d button %d %s", ev->cbutton.which, ev->cbutton.button,
|
||||||
|
ev->cbutton.state == SDL_PRESSED ? "pressed" : "released");
|
||||||
|
|
||||||
|
const bool pressed = (ev->cbutton.state == SDL_PRESSED);
|
||||||
|
if (DoEventHook(Hook::Type::Button, ev->cbutton.which, ev->cbutton.button, pressed ? 1.0f : 0.0f))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto it = m_controllers.find(ev->caxis.which);
|
||||||
|
if (it == m_controllers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const ButtonCallback& cb = it->second.button_mapping[ev->cbutton.button];
|
||||||
|
if (!cb)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
cb(pressed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLControllerInterface::UpdateControllerRumble()
|
||||||
|
{
|
||||||
|
for (auto& it : m_controllers)
|
||||||
|
{
|
||||||
|
ControllerData& cd = it.second;
|
||||||
|
if (!cd.haptic)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float new_strength = 0.0f;
|
||||||
|
Controller* controller = GetController(cd.controller_index);
|
||||||
|
if (controller)
|
||||||
|
{
|
||||||
|
const u32 motor_count = controller->GetVibrationMotorCount();
|
||||||
|
for (u32 i = 0; i < motor_count; i++)
|
||||||
|
new_strength = std::max(new_strength, controller->GetVibrationMotorStrength(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cd.last_rumble_strength == new_strength)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (new_strength > 0.01f)
|
||||||
|
SDL_HapticRumblePlay(static_cast<SDL_Haptic*>(cd.haptic), new_strength, 100000);
|
||||||
|
else
|
||||||
|
SDL_HapticRumbleStop(static_cast<SDL_Haptic*>(cd.haptic));
|
||||||
|
|
||||||
|
cd.last_rumble_strength = new_strength;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
#pragma once
|
||||||
|
#include "core/types.h"
|
||||||
|
#include <array>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
class HostInterface;
|
||||||
|
class System;
|
||||||
|
class Controller;
|
||||||
|
|
||||||
|
union SDL_Event;
|
||||||
|
|
||||||
|
class SDLControllerInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum : int
|
||||||
|
{
|
||||||
|
MAX_NUM_AXISES = 7,
|
||||||
|
MAX_NUM_BUTTONS = 15
|
||||||
|
};
|
||||||
|
|
||||||
|
using AxisCallback = std::function<void(float value)>;
|
||||||
|
using ButtonCallback = std::function<void(bool pressed)>;
|
||||||
|
|
||||||
|
SDLControllerInterface();
|
||||||
|
~SDLControllerInterface();
|
||||||
|
|
||||||
|
bool Initialize(HostInterface* host_interface, bool init_sdl);
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
// Removes all bindings. Call before setting new bindings.
|
||||||
|
void ClearControllerBindings();
|
||||||
|
|
||||||
|
// Binding to events. If a binding for this axis/button already exists, returns false.
|
||||||
|
bool BindControllerAxis(int controller_index, int axis_number, AxisCallback callback);
|
||||||
|
bool BindControllerButton(int controller_index, int button_number, ButtonCallback callback);
|
||||||
|
bool BindControllerAxisToButton(int controller_index, int axis_number, bool direction, ButtonCallback callback);
|
||||||
|
|
||||||
|
// Default bindings, used by SDL frontend.
|
||||||
|
void SetDefaultBindings();
|
||||||
|
|
||||||
|
void PumpSDLEvents();
|
||||||
|
|
||||||
|
bool ProcessSDLEvent(const SDL_Event* event);
|
||||||
|
|
||||||
|
void UpdateControllerRumble();
|
||||||
|
|
||||||
|
// Input monitoring for external access.
|
||||||
|
struct Hook
|
||||||
|
{
|
||||||
|
enum class Type
|
||||||
|
{
|
||||||
|
Axis,
|
||||||
|
Button
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CallbackResult
|
||||||
|
{
|
||||||
|
StopMonitoring,
|
||||||
|
ContinueMonitoring
|
||||||
|
};
|
||||||
|
|
||||||
|
using Callback = std::function<CallbackResult(const Hook& ei)>;
|
||||||
|
|
||||||
|
Type type;
|
||||||
|
int controller_index;
|
||||||
|
int button_or_axis_number;
|
||||||
|
float value; // 0/1 for buttons, -1..1 for axises
|
||||||
|
};
|
||||||
|
void SetHook(Hook::Callback callback);
|
||||||
|
void ClearHook();
|
||||||
|
|
||||||
|
private:
|
||||||
|
System* GetSystem() const;
|
||||||
|
Controller* GetController(u32 slot) const;
|
||||||
|
bool DoEventHook(Hook::Type type, int controller_index, int button_or_axis_number, float value);
|
||||||
|
|
||||||
|
bool OpenGameController(int index);
|
||||||
|
bool CloseGameController(int index);
|
||||||
|
void CloseGameControllers();
|
||||||
|
bool HandleControllerAxisEvent(const SDL_Event* event);
|
||||||
|
bool HandleControllerButtonEvent(const SDL_Event* event);
|
||||||
|
|
||||||
|
struct ControllerData
|
||||||
|
{
|
||||||
|
void* controller;
|
||||||
|
void* haptic;
|
||||||
|
u32 controller_index;
|
||||||
|
float last_rumble_strength;
|
||||||
|
|
||||||
|
std::array<AxisCallback, MAX_NUM_AXISES> axis_mapping;
|
||||||
|
std::array<ButtonCallback, MAX_NUM_BUTTONS> button_mapping;
|
||||||
|
std::array<std::array<ButtonCallback, 2>, MAX_NUM_AXISES> axis_button_mapping;
|
||||||
|
};
|
||||||
|
|
||||||
|
HostInterface* m_host_interface = nullptr;
|
||||||
|
|
||||||
|
std::map<int, ControllerData> m_controllers;
|
||||||
|
|
||||||
|
std::mutex m_event_intercept_mutex;
|
||||||
|
Hook::Callback m_event_intercept_callback;
|
||||||
|
|
||||||
|
bool m_sdl_initialized_by_us = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SDLControllerInterface g_sdl_controller_interface;
|
Loading…
Reference in New Issue