mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-18 07:35:46 -04:00
Qt: Implement controller interface/binding
This commit is contained in:
@ -43,7 +43,7 @@ add_executable(duckstation-qt
|
||||
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)
|
||||
target_sources(duckstation-qt PRIVATE
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "inputbindingwidgets.h"
|
||||
#include "core/settings.h"
|
||||
#include "frontend-common/sdl_controller_interface.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QTimer>
|
||||
@ -81,6 +82,49 @@ void InputButtonBindingWidget::onInputListenTimerTimeout()
|
||||
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()
|
||||
{
|
||||
m_input_listen_timer = new QTimer(this);
|
||||
@ -95,6 +139,7 @@ void InputButtonBindingWidget::startListeningForInput()
|
||||
installEventFilter(this);
|
||||
grabKeyboard();
|
||||
grabMouse();
|
||||
hookControllerInput();
|
||||
}
|
||||
|
||||
void InputButtonBindingWidget::stopListeningForInput()
|
||||
@ -103,6 +148,7 @@ void InputButtonBindingWidget::stopListeningForInput()
|
||||
delete m_input_listen_timer;
|
||||
m_input_listen_timer = nullptr;
|
||||
|
||||
unhookControllerInput();
|
||||
releaseMouse();
|
||||
releaseKeyboard();
|
||||
removeEventFilter(this);
|
||||
|
@ -20,12 +20,16 @@ protected:
|
||||
private Q_SLOTS:
|
||||
void onPressed();
|
||||
void onInputListenTimerTimeout();
|
||||
void bindToControllerAxis(int controller_index, int axis_index, bool positive);
|
||||
void bindToControllerButton(int controller_index, int button_index);
|
||||
|
||||
private:
|
||||
bool isListeningForInput() const { return m_input_listen_timer != nullptr; }
|
||||
void startListeningForInput();
|
||||
void stopListeningForInput();
|
||||
void setNewBinding();
|
||||
void hookControllerInput();
|
||||
void unhookControllerInput();
|
||||
|
||||
QtHostInterface* m_host_interface;
|
||||
QString m_setting_name;
|
||||
|
@ -8,12 +8,14 @@
|
||||
#include "core/game_list.h"
|
||||
#include "core/gpu.h"
|
||||
#include "core/system.h"
|
||||
#include "frontend-common/sdl_controller_interface.h"
|
||||
#include "qtsettingsinterface.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QEventLoop>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QMenu>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <memory>
|
||||
@ -28,7 +30,6 @@ QtHostInterface::QtHostInterface(QObject* parent)
|
||||
{
|
||||
checkSettings();
|
||||
refreshGameList();
|
||||
doUpdateInputMap();
|
||||
createThread();
|
||||
}
|
||||
|
||||
@ -283,6 +284,7 @@ void QtHostInterface::OnSystemCreated()
|
||||
HostInterface::OnSystemCreated();
|
||||
|
||||
wakeThread();
|
||||
destroyBackgroundControllerPollTimer();
|
||||
|
||||
emit emulationStarted();
|
||||
}
|
||||
@ -301,6 +303,9 @@ void QtHostInterface::OnSystemDestroyed()
|
||||
{
|
||||
HostInterface::OnSystemDestroyed();
|
||||
|
||||
if (m_background_controller_polling_enable_count > 0)
|
||||
createBackgroundControllerPollTimer();
|
||||
|
||||
emit emulationStopped();
|
||||
}
|
||||
|
||||
@ -350,7 +355,9 @@ void QtHostInterface::updateInputMap()
|
||||
void QtHostInterface::doUpdateInputMap()
|
||||
{
|
||||
m_keyboard_input_handlers.clear();
|
||||
g_sdl_controller_interface.ClearControllerBindings();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_qsettings_mutex);
|
||||
updateControllerInputMap();
|
||||
updateHotkeyInputMap();
|
||||
}
|
||||
@ -493,9 +500,41 @@ void QtHostInterface::addButtonToInputMap(const QString& binding, InputButtonHan
|
||||
|
||||
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
|
||||
{
|
||||
qWarning() << "Unknown input device: " << device;
|
||||
qWarning() << "Unknown input device: " << binding;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -659,6 +698,63 @@ void QtHostInterface::saveState(bool global, qint32 slot, bool block_until_done
|
||||
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()
|
||||
{
|
||||
m_original_thread = QThread::currentThread();
|
||||
@ -685,6 +781,12 @@ void QtHostInterface::threadEntryPoint()
|
||||
{
|
||||
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
|
||||
while (!m_shutdown_flag.load())
|
||||
{
|
||||
@ -710,10 +812,14 @@ void QtHostInterface::threadEntryPoint()
|
||||
m_system->Throttle();
|
||||
|
||||
m_worker_thread_event_loop->processEvents(QEventLoop::AllEvents);
|
||||
g_sdl_controller_interface.PumpSDLEvents();
|
||||
}
|
||||
|
||||
m_system.reset();
|
||||
m_audio_stream.reset();
|
||||
|
||||
g_sdl_controller_interface.Shutdown();
|
||||
|
||||
delete m_worker_thread_event_loop;
|
||||
m_worker_thread_event_loop = nullptr;
|
||||
|
||||
|
@ -18,6 +18,7 @@ class ByteStream;
|
||||
class QEventLoop;
|
||||
class QMenu;
|
||||
class QWidget;
|
||||
class QTimer;
|
||||
|
||||
class GameList;
|
||||
|
||||
@ -88,11 +89,19 @@ public Q_SLOTS:
|
||||
void loadState(bool global, qint32 slot);
|
||||
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:
|
||||
void doStopThread();
|
||||
void doUpdateInputMap();
|
||||
void doHandleKeyEvent(int key, bool pressed);
|
||||
void onDisplayWindowResized(int width, int height);
|
||||
void doBackgroundControllerPoll();
|
||||
|
||||
protected:
|
||||
bool AcquireHostDisplay() override;
|
||||
@ -107,6 +116,12 @@ protected:
|
||||
void OnControllerTypeChanged(u32 slot) override;
|
||||
|
||||
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)>;
|
||||
|
||||
class Thread : public QThread
|
||||
@ -124,6 +139,8 @@ private:
|
||||
|
||||
void checkSettings();
|
||||
void updateQSettingsFromCoreSettings();
|
||||
void createBackgroundControllerPollTimer();
|
||||
void destroyBackgroundControllerPollTimer();
|
||||
|
||||
void updateControllerInputMap();
|
||||
void updateHotkeyInputMap();
|
||||
@ -145,4 +162,7 @@ private:
|
||||
|
||||
// input key maps, todo hotkeys
|
||||
std::map<int, InputButtonHandler> m_keyboard_input_handlers;
|
||||
|
||||
QTimer* m_background_controller_polling_timer = nullptr;
|
||||
u32 m_background_controller_polling_enable_count = 0;
|
||||
};
|
||||
|
Reference in New Issue
Block a user