mirror of https://github.com/mgba-emu/mgba.git
486 lines
12 KiB
C++
486 lines
12 KiB
C++
/* Copyright (c) 2013-2014 Jeffrey Pfau
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
#include "InputController.h"
|
|
|
|
#include "ConfigController.h"
|
|
#include "GamepadAxisEvent.h"
|
|
#include "GamepadButtonEvent.h"
|
|
|
|
#include <QApplication>
|
|
#include <QTimer>
|
|
#include <QWidget>
|
|
|
|
extern "C" {
|
|
#include "util/configuration.h"
|
|
}
|
|
|
|
using namespace QGBA;
|
|
|
|
#ifdef BUILD_SDL
|
|
int InputController::s_sdlInited = 0;
|
|
GBASDLEvents InputController::s_sdlEvents;
|
|
#endif
|
|
|
|
InputController::InputController(int playerId, QObject* parent)
|
|
: QObject(parent)
|
|
, m_playerId(playerId)
|
|
, m_config(nullptr)
|
|
, m_gamepadTimer(nullptr)
|
|
#ifdef BUILD_SDL
|
|
, m_playerAttached(false)
|
|
#endif
|
|
, m_allowOpposing(false)
|
|
{
|
|
GBAInputMapInit(&m_inputMap);
|
|
|
|
#ifdef BUILD_SDL
|
|
if (s_sdlInited == 0) {
|
|
GBASDLInitEvents(&s_sdlEvents);
|
|
}
|
|
++s_sdlInited;
|
|
m_sdlPlayer.bindings = &m_inputMap;
|
|
GBASDLInitBindings(&m_inputMap);
|
|
#endif
|
|
|
|
m_gamepadTimer = new QTimer(this);
|
|
#ifdef BUILD_SDL
|
|
connect(m_gamepadTimer, &QTimer::timeout, [this]() {
|
|
testGamepad(SDL_BINDING_BUTTON);
|
|
});
|
|
#endif
|
|
m_gamepadTimer->setInterval(50);
|
|
m_gamepadTimer->start();
|
|
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT);
|
|
GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT);
|
|
}
|
|
|
|
InputController::~InputController() {
|
|
GBAInputMapDeinit(&m_inputMap);
|
|
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
GBASDLDetachPlayer(&s_sdlEvents, &m_sdlPlayer);
|
|
}
|
|
|
|
--s_sdlInited;
|
|
if (s_sdlInited == 0) {
|
|
GBASDLDeinitEvents(&s_sdlEvents);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void InputController::setConfiguration(ConfigController* config) {
|
|
m_config = config;
|
|
setAllowOpposing(config->getOption("allowOpposingDirections").toInt());
|
|
loadConfiguration(KEYBOARD);
|
|
#ifdef BUILD_SDL
|
|
GBASDLEventsLoadConfig(&s_sdlEvents, config->input());
|
|
if (!m_playerAttached) {
|
|
GBASDLAttachPlayer(&s_sdlEvents, &m_sdlPlayer);
|
|
m_playerAttached = true;
|
|
}
|
|
loadConfiguration(SDL_BINDING_BUTTON);
|
|
loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
|
|
#endif
|
|
}
|
|
|
|
void InputController::loadConfiguration(uint32_t type) {
|
|
GBAInputMapLoad(&m_inputMap, type, m_config->input());
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
GBASDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void InputController::loadProfile(uint32_t type, const QString& profile) {
|
|
GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
|
|
recalibrateAxes();
|
|
}
|
|
|
|
void InputController::saveConfiguration() {
|
|
saveConfiguration(KEYBOARD);
|
|
#ifdef BUILD_SDL
|
|
saveConfiguration(SDL_BINDING_BUTTON);
|
|
saveProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
|
|
if (m_playerAttached) {
|
|
GBASDLPlayerSaveConfig(&m_sdlPlayer, m_config->input());
|
|
}
|
|
m_config->write();
|
|
#endif
|
|
}
|
|
|
|
void InputController::saveConfiguration(uint32_t type) {
|
|
GBAInputMapSave(&m_inputMap, type, m_config->input());
|
|
m_config->write();
|
|
}
|
|
|
|
void InputController::saveProfile(uint32_t type, const QString& profile) {
|
|
GBAInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
|
|
m_config->write();
|
|
}
|
|
|
|
const char* InputController::profileForType(uint32_t type) {
|
|
UNUSED(type);
|
|
#ifdef BUILD_SDL
|
|
if (type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
|
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
|
return SDL_JoystickName(m_sdlPlayer.joystick);
|
|
#else
|
|
return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick));
|
|
#endif
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
QStringList InputController::connectedGamepads(uint32_t type) const {
|
|
UNUSED(type);
|
|
|
|
#ifdef BUILD_SDL
|
|
if (type == SDL_BINDING_BUTTON) {
|
|
QStringList pads;
|
|
for (size_t i = 0; i < s_sdlEvents.nJoysticks; ++i) {
|
|
const char* name;
|
|
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
|
name = SDL_JoystickName(s_sdlEvents.joysticks[i]);
|
|
#else
|
|
name = SDL_JoystickName(SDL_JoystickIndex(s_sdlEvents.joysticks[i]));
|
|
#endif
|
|
if (name) {
|
|
pads.append(QString(name));
|
|
} else {
|
|
pads.append(QString());
|
|
}
|
|
}
|
|
return pads;
|
|
}
|
|
#endif
|
|
|
|
return QStringList();
|
|
}
|
|
|
|
int InputController::gamepad(uint32_t type) const {
|
|
#ifdef BUILD_SDL
|
|
if (type == SDL_BINDING_BUTTON) {
|
|
return m_sdlPlayer.joystickIndex;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void InputController::setGamepad(uint32_t type, int index) {
|
|
#ifdef BUILD_SDL
|
|
if (type == SDL_BINDING_BUTTON) {
|
|
GBASDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void InputController::setPreferredGamepad(uint32_t type, const QString& device) {
|
|
if (!m_config) {
|
|
return;
|
|
}
|
|
GBAInputSetPreferredDevice(m_config->input(), type, m_playerId, device.toUtf8().constData());
|
|
}
|
|
|
|
GBARumble* InputController::rumble() {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
return &m_sdlPlayer.rumble.d;
|
|
}
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
GBARotationSource* InputController::rotationSource() {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
return &m_sdlPlayer.rotation.d;
|
|
}
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
|
|
void InputController::registerTiltAxisX(int axis) {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
m_sdlPlayer.rotation.axisX = axis;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void InputController::registerTiltAxisY(int axis) {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
m_sdlPlayer.rotation.axisY = axis;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void InputController::registerGyroAxisX(int axis) {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
m_sdlPlayer.rotation.gyroX = axis;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void InputController::registerGyroAxisY(int axis) {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
m_sdlPlayer.rotation.gyroY = axis;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
float InputController::gyroSensitivity() const {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
return m_sdlPlayer.rotation.gyroSensitivity;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
void InputController::setGyroSensitivity(float sensitivity) {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
m_sdlPlayer.rotation.gyroSensitivity = sensitivity;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
GBAKey InputController::mapKeyboard(int key) const {
|
|
return GBAInputMapKey(&m_inputMap, KEYBOARD, key);
|
|
}
|
|
|
|
void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
|
|
return GBAInputBindKey(&m_inputMap, type, key, gbaKey);
|
|
}
|
|
|
|
int InputController::pollEvents() {
|
|
int activeButtons = 0;
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
SDL_Joystick* joystick = m_sdlPlayer.joystick;
|
|
SDL_JoystickUpdate();
|
|
int numButtons = SDL_JoystickNumButtons(joystick);
|
|
int i;
|
|
for (i = 0; i < numButtons; ++i) {
|
|
GBAKey key = GBAInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i);
|
|
if (key == GBA_KEY_NONE) {
|
|
continue;
|
|
}
|
|
if (hasPendingEvent(key)) {
|
|
continue;
|
|
}
|
|
if (SDL_JoystickGetButton(joystick, i)) {
|
|
activeButtons |= 1 << key;
|
|
}
|
|
}
|
|
int numHats = SDL_JoystickNumHats(joystick);
|
|
for (i = 0; i < numHats; ++i) {
|
|
int hat = SDL_JoystickGetHat(joystick, i);
|
|
if (hat & SDL_HAT_UP) {
|
|
activeButtons |= 1 << GBA_KEY_UP;
|
|
}
|
|
if (hat & SDL_HAT_LEFT) {
|
|
activeButtons |= 1 << GBA_KEY_LEFT;
|
|
}
|
|
if (hat & SDL_HAT_DOWN) {
|
|
activeButtons |= 1 << GBA_KEY_DOWN;
|
|
}
|
|
if (hat & SDL_HAT_RIGHT) {
|
|
activeButtons |= 1 << GBA_KEY_RIGHT;
|
|
}
|
|
}
|
|
|
|
int numAxes = SDL_JoystickNumAxes(joystick);
|
|
for (i = 0; i < numAxes; ++i) {
|
|
int value = SDL_JoystickGetAxis(joystick, i);
|
|
|
|
enum GBAKey key = GBAInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value);
|
|
if (key != GBA_KEY_NONE) {
|
|
activeButtons |= 1 << key;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return activeButtons;
|
|
}
|
|
|
|
QSet<int> InputController::activeGamepadButtons(int type) {
|
|
QSet<int> activeButtons;
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached && type == SDL_BINDING_BUTTON) {
|
|
SDL_Joystick* joystick = m_sdlPlayer.joystick;
|
|
SDL_JoystickUpdate();
|
|
int numButtons = SDL_JoystickNumButtons(joystick);
|
|
int i;
|
|
for (i = 0; i < numButtons; ++i) {
|
|
if (SDL_JoystickGetButton(joystick, i)) {
|
|
activeButtons.insert(i);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return activeButtons;
|
|
}
|
|
|
|
void InputController::recalibrateAxes() {
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached) {
|
|
SDL_Joystick* joystick = m_sdlPlayer.joystick;
|
|
SDL_JoystickUpdate();
|
|
int numAxes = SDL_JoystickNumAxes(joystick);
|
|
if (numAxes < 1) {
|
|
return;
|
|
}
|
|
m_deadzones.resize(numAxes);
|
|
int i;
|
|
for (i = 0; i < numAxes; ++i) {
|
|
m_deadzones[i] = SDL_JoystickGetAxis(joystick, i);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes(int type) {
|
|
QSet<QPair<int, GamepadAxisEvent::Direction>> activeAxes;
|
|
#ifdef BUILD_SDL
|
|
if (m_playerAttached && type == SDL_BINDING_BUTTON) {
|
|
SDL_Joystick* joystick = m_sdlPlayer.joystick;
|
|
SDL_JoystickUpdate();
|
|
int numAxes = SDL_JoystickNumAxes(joystick);
|
|
if (numAxes < 1) {
|
|
return activeAxes;
|
|
}
|
|
m_deadzones.resize(numAxes);
|
|
int i;
|
|
for (i = 0; i < numAxes; ++i) {
|
|
int32_t axis = SDL_JoystickGetAxis(joystick, i);
|
|
axis -= m_deadzones[i];
|
|
if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) {
|
|
activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return activeAxes;
|
|
}
|
|
|
|
void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) {
|
|
const GBAAxis* old = GBAInputQueryAxis(&m_inputMap, type, axis);
|
|
GBAAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD };
|
|
if (old) {
|
|
description = *old;
|
|
}
|
|
switch (direction) {
|
|
case GamepadAxisEvent::NEGATIVE:
|
|
description.lowDirection = key;
|
|
description.deadLow = m_deadzones[axis] - AXIS_THRESHOLD;
|
|
break;
|
|
case GamepadAxisEvent::POSITIVE:
|
|
description.highDirection = key;
|
|
description.deadHigh = m_deadzones[axis] + AXIS_THRESHOLD;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
GBAInputBindAxis(&m_inputMap, type, axis, &description);
|
|
}
|
|
|
|
void InputController::testGamepad(int type) {
|
|
auto activeAxes = activeGamepadAxes(type);
|
|
auto oldAxes = m_activeAxes;
|
|
m_activeAxes = activeAxes;
|
|
|
|
auto activeButtons = activeGamepadButtons(type);
|
|
auto oldButtons = m_activeButtons;
|
|
m_activeButtons = activeButtons;
|
|
|
|
if (!QApplication::focusWidget()) {
|
|
return;
|
|
}
|
|
|
|
activeAxes.subtract(oldAxes);
|
|
oldAxes.subtract(m_activeAxes);
|
|
|
|
for (auto& axis : m_activeAxes) {
|
|
bool newlyAboveThreshold = activeAxes.contains(axis);
|
|
if (newlyAboveThreshold) {
|
|
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this);
|
|
postPendingEvent(event->gbaKey());
|
|
QApplication::sendEvent(QApplication::focusWidget(), event);
|
|
if (!event->isAccepted()) {
|
|
clearPendingEvent(event->gbaKey());
|
|
}
|
|
}
|
|
}
|
|
for (auto axis : oldAxes) {
|
|
GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this);
|
|
clearPendingEvent(event->gbaKey());
|
|
QApplication::sendEvent(QApplication::focusWidget(), event);
|
|
}
|
|
|
|
if (!QApplication::focusWidget()) {
|
|
return;
|
|
}
|
|
|
|
activeButtons.subtract(oldButtons);
|
|
oldButtons.subtract(m_activeButtons);
|
|
|
|
for (int button : activeButtons) {
|
|
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this);
|
|
postPendingEvent(event->gbaKey());
|
|
QApplication::sendEvent(QApplication::focusWidget(), event);
|
|
if (!event->isAccepted()) {
|
|
clearPendingEvent(event->gbaKey());
|
|
}
|
|
}
|
|
for (int button : oldButtons) {
|
|
GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this);
|
|
clearPendingEvent(event->gbaKey());
|
|
QApplication::sendEvent(QApplication::focusWidget(), event);
|
|
}
|
|
}
|
|
|
|
void InputController::postPendingEvent(GBAKey key) {
|
|
m_pendingEvents.insert(key);
|
|
}
|
|
|
|
void InputController::clearPendingEvent(GBAKey key) {
|
|
m_pendingEvents.remove(key);
|
|
}
|
|
|
|
bool InputController::hasPendingEvent(GBAKey key) const {
|
|
return m_pendingEvents.contains(key);
|
|
}
|
|
|
|
#if defined(BUILD_SDL) && SDL_VERSION_ATLEAST(2, 0, 0)
|
|
void InputController::suspendScreensaver() {
|
|
GBASDLSuspendScreensaver(&s_sdlEvents);
|
|
}
|
|
|
|
void InputController::resumeScreensaver() {
|
|
GBASDLResumeScreensaver(&s_sdlEvents);
|
|
}
|
|
|
|
void InputController::setScreensaverSuspendable(bool suspendable) {
|
|
GBASDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
|
|
}
|
|
#endif
|