all repos — mgba @ a2e1cd615c2fac7a515c0649d182d9a685b2de58

mGBA Game Boy Advance Emulator

src/platform/qt/InputController.cpp (view raw)

  1/* Copyright (c) 2013-2014 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "InputController.h"
  7
  8#include "ConfigController.h"
  9#include "GamepadAxisEvent.h"
 10#include "GamepadButtonEvent.h"
 11
 12#include <QApplication>
 13#include <QTimer>
 14#include <QWidget>
 15
 16extern "C" {
 17#include "util/configuration.h"
 18}
 19
 20using namespace QGBA;
 21
 22#ifdef BUILD_SDL
 23int InputController::s_sdlInited = 0;
 24GBASDLEvents InputController::s_sdlEvents;
 25#endif
 26
 27InputController::InputController(int playerId, QObject* parent)
 28	: QObject(parent)
 29	, m_playerId(playerId)
 30	, m_config(nullptr)
 31	, m_gamepadTimer(nullptr)
 32#ifdef BUILD_SDL
 33	, m_playerAttached(false)
 34#endif
 35	, m_allowOpposing(false)
 36{
 37	GBAInputMapInit(&m_inputMap);
 38
 39#ifdef BUILD_SDL
 40	if (s_sdlInited == 0) {
 41		GBASDLInitEvents(&s_sdlEvents);
 42	}
 43	++s_sdlInited;
 44	m_sdlPlayer.bindings = &m_inputMap;
 45	GBASDLInitBindings(&m_inputMap);
 46
 47	m_gamepadTimer = new QTimer(this);
 48	connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad()));
 49	m_gamepadTimer->setInterval(50);
 50	m_gamepadTimer->start();
 51#endif
 52
 53	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);
 54	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B);
 55	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L);
 56	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R);
 57	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START);
 58	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT);
 59	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP);
 60	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN);
 61	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT);
 62	GBAInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT);
 63}
 64
 65InputController::~InputController() {
 66	GBAInputMapDeinit(&m_inputMap);
 67
 68#ifdef BUILD_SDL
 69	if (m_playerAttached) {
 70		GBASDLDetachPlayer(&s_sdlEvents, &m_sdlPlayer);
 71	}
 72
 73	--s_sdlInited;
 74	if (s_sdlInited == 0) {
 75		GBASDLDeinitEvents(&s_sdlEvents);
 76	}
 77#endif
 78}
 79
 80void InputController::setConfiguration(ConfigController* config) {
 81	m_config = config;
 82	setAllowOpposing(config->getOption("allowOpposingDirections").toInt());
 83	loadConfiguration(KEYBOARD);
 84#ifdef BUILD_SDL
 85	GBASDLEventsLoadConfig(&s_sdlEvents, config->input());
 86	if (!m_playerAttached) {
 87		GBASDLAttachPlayer(&s_sdlEvents, &m_sdlPlayer);
 88		m_playerAttached = true;
 89	}
 90	loadConfiguration(SDL_BINDING_BUTTON);
 91	loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
 92#endif
 93}
 94
 95void InputController::loadConfiguration(uint32_t type) {
 96	GBAInputMapLoad(&m_inputMap, type, m_config->input());
 97#ifdef BUILD_SDL
 98	if (m_playerAttached) {
 99		GBASDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
100	}
101#endif
102}
103
104void InputController::loadProfile(uint32_t type, const QString& profile) {
105	GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData());
106#ifdef BUILD_SDL
107	recalibrateAxes();
108#endif
109}
110
111void InputController::saveConfiguration() {
112	saveConfiguration(KEYBOARD);
113#ifdef BUILD_SDL
114	saveConfiguration(SDL_BINDING_BUTTON);
115	saveProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
116	if (m_playerAttached) {
117		GBASDLPlayerSaveConfig(&m_sdlPlayer, m_config->input());
118	}
119	m_config->write();
120#endif
121}
122
123void InputController::saveConfiguration(uint32_t type) {
124	GBAInputMapSave(&m_inputMap, type, m_config->input());
125	m_config->write();
126}
127
128void InputController::saveProfile(uint32_t type, const QString& profile) {
129	GBAInputProfileSave(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData());
130	m_config->write();
131}
132
133const char* InputController::profileForType(uint32_t type) {
134	UNUSED(type);
135#ifdef BUILD_SDL
136	if (type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
137#if SDL_VERSION_ATLEAST(2, 0, 0)
138		return SDL_JoystickName(m_sdlPlayer.joystick);
139#else
140		return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick));
141#endif
142	}
143#endif
144	return 0;
145}
146
147#ifdef BUILD_SDL
148QStringList InputController::connectedGamepads(uint32_t type) const {
149	UNUSED(type);
150	if (type != SDL_BINDING_BUTTON) {
151		return QStringList();
152	}
153
154	QStringList pads;
155	for (size_t i = 0; i < s_sdlEvents.nJoysticks; ++i) {
156		const char* name;
157#if SDL_VERSION_ATLEAST(2, 0, 0)
158		name = SDL_JoystickName(s_sdlEvents.joysticks[i]);
159#else
160		name = SDL_JoystickName(SDL_JoystickIndex(s_sdlEvents.joysticks[i]));
161#endif
162		if (name) {
163			pads.append(QString(name));
164		} else {
165			pads.append(QString());
166		}
167	}
168	return pads;
169}
170
171void InputController::setPreferredGamepad(uint32_t type, const QString& device) {
172	if (!m_config) {
173		return;
174	}
175	GBAInputSetPreferredDevice(m_config->input(), type, m_sdlPlayer.playerId, device.toLocal8Bit().constData());
176}
177
178GBARumble* InputController::rumble() {
179	return &m_sdlPlayer.rumble.d;
180}
181
182GBARotationSource* InputController::rotationSource() {
183	return &m_sdlPlayer.rotation.d;
184}
185
186void InputController::registerTiltAxisX(int axis) {
187	m_sdlPlayer.rotation.axisX = axis;
188}
189void InputController::registerTiltAxisY(int axis) {
190	m_sdlPlayer.rotation.axisY = axis;
191}
192void InputController::registerGyroAxisX(int axis) {
193	m_sdlPlayer.rotation.gyroX = axis;
194}
195void InputController::registerGyroAxisY(int axis) {
196	m_sdlPlayer.rotation.gyroY = axis;
197}
198
199float InputController::gyroSensitivity() const {
200	return m_sdlPlayer.rotation.gyroSensitivity;
201}
202
203void InputController::setGyroSensitivity(float sensitivity) {
204	m_sdlPlayer.rotation.gyroSensitivity = sensitivity;
205}
206#else
207GBARumble* InputController::rumble() {
208	return nullptr;
209}
210
211GBARotationSource* InputController::rotationSource() {
212	return nullptr;
213}
214#endif
215
216GBAKey InputController::mapKeyboard(int key) const {
217	return GBAInputMapKey(&m_inputMap, KEYBOARD, key);
218}
219
220void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
221	return GBAInputBindKey(&m_inputMap, type, key, gbaKey);
222}
223
224#ifdef BUILD_SDL
225int InputController::testSDLEvents() {
226	SDL_Joystick* joystick = m_sdlPlayer.joystick;
227	SDL_JoystickUpdate();
228	int numButtons = SDL_JoystickNumButtons(joystick);
229	int activeButtons = 0;
230	int i;
231	for (i = 0; i < numButtons; ++i) {
232		GBAKey key = GBAInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i);
233		if (key == GBA_KEY_NONE) {
234			continue;
235		}
236		if (hasPendingEvent(key)) {
237			continue;
238		}
239		if (SDL_JoystickGetButton(joystick, i)) {
240			activeButtons |= 1 << key;
241		}
242	}
243	int numHats = SDL_JoystickNumHats(joystick);
244	for (i = 0; i < numHats; ++i) {
245		int hat = SDL_JoystickGetHat(joystick, i);
246		if (hat & SDL_HAT_UP) {
247			activeButtons |= 1 << GBA_KEY_UP;
248		}
249		if (hat & SDL_HAT_LEFT) {
250			activeButtons |= 1 << GBA_KEY_LEFT;
251		}
252		if (hat & SDL_HAT_DOWN) {
253			activeButtons |= 1 << GBA_KEY_DOWN;
254		}
255		if (hat & SDL_HAT_RIGHT) {
256			activeButtons |= 1 << GBA_KEY_RIGHT;
257		}
258	}
259
260	int numAxes = SDL_JoystickNumAxes(joystick);
261	for (i = 0; i < numAxes; ++i) {
262		int value = SDL_JoystickGetAxis(joystick, i);
263
264		enum GBAKey key = GBAInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value);
265		if (key != GBA_KEY_NONE) {
266			activeButtons |= 1 << key;
267		}
268	}
269	return activeButtons;
270}
271
272QSet<int> InputController::activeGamepadButtons() {
273	SDL_Joystick* joystick = m_sdlPlayer.joystick;
274	SDL_JoystickUpdate();
275	int numButtons = SDL_JoystickNumButtons(joystick);
276	QSet<int> activeButtons;
277	int i;
278	for (i = 0; i < numButtons; ++i) {
279		if (SDL_JoystickGetButton(joystick, i)) {
280			activeButtons.insert(i);
281		}
282	}
283	return activeButtons;
284}
285
286void InputController::recalibrateAxes() {
287	SDL_Joystick* joystick = m_sdlPlayer.joystick;
288	SDL_JoystickUpdate();
289	int numAxes = SDL_JoystickNumAxes(joystick);
290	if (numAxes < 1) {
291		return;
292	}
293	m_deadzones.resize(numAxes);
294	int i;
295	for (i = 0; i < numAxes; ++i) {
296		m_deadzones[i] = SDL_JoystickGetAxis(joystick, i);
297	}
298}
299
300QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes() {
301	SDL_Joystick* joystick = m_sdlPlayer.joystick;
302	SDL_JoystickUpdate();
303	int numAxes = SDL_JoystickNumAxes(joystick);
304	QSet<QPair<int, GamepadAxisEvent::Direction>> activeAxes;
305	if (numAxes < 1) {
306		return activeAxes;
307	}
308	m_deadzones.resize(numAxes);
309	int i;
310	for (i = 0; i < numAxes; ++i) {
311		int32_t axis = SDL_JoystickGetAxis(joystick, i);
312		axis -= m_deadzones[i];
313		if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) {
314			activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE));
315		}
316	}
317	return activeAxes;
318}
319
320void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) {
321	const GBAAxis* old = GBAInputQueryAxis(&m_inputMap, SDL_BINDING_BUTTON, axis);
322	GBAAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD };
323	if (old) {
324		description = *old;
325	}
326	switch (direction) {
327	case GamepadAxisEvent::NEGATIVE:
328		description.lowDirection = key;
329		description.deadLow = m_deadzones[axis] - AXIS_THRESHOLD;
330		break;
331	case GamepadAxisEvent::POSITIVE:
332		description.highDirection = key;
333		description.deadHigh = m_deadzones[axis] + AXIS_THRESHOLD;
334		break;
335	default:
336		return;
337	}
338	GBAInputBindAxis(&m_inputMap, SDL_BINDING_BUTTON, axis, &description);
339}
340#endif
341
342void InputController::testGamepad() {
343#ifdef BUILD_SDL
344	auto activeAxes = activeGamepadAxes();
345	auto oldAxes = m_activeAxes;
346	m_activeAxes = activeAxes;
347
348	auto activeButtons = activeGamepadButtons();
349	auto oldButtons = m_activeButtons;
350	m_activeButtons = activeButtons;
351
352	if (!QApplication::focusWidget()) {
353		return;
354	}
355
356	activeAxes.subtract(oldAxes);
357	oldAxes.subtract(m_activeAxes);
358
359	for (auto& axis : m_activeAxes) {
360		bool newlyAboveThreshold = activeAxes.contains(axis);
361		if (newlyAboveThreshold) {
362			GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, this);
363			postPendingEvent(event->gbaKey());
364			QApplication::sendEvent(QApplication::focusWidget(), event);
365			if (!event->isAccepted()) {
366				clearPendingEvent(event->gbaKey());
367			}
368		}
369	}
370	for (auto axis : oldAxes) {
371		GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, this);
372		clearPendingEvent(event->gbaKey());
373		QApplication::sendEvent(QApplication::focusWidget(), event);
374	}
375
376	if (!QApplication::focusWidget()) {
377		return;
378	}
379
380	activeButtons.subtract(oldButtons);
381	oldButtons.subtract(m_activeButtons);
382
383	for (int button : activeButtons) {
384		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, this);
385		postPendingEvent(event->gbaKey());
386		QApplication::sendEvent(QApplication::focusWidget(), event);
387		if (!event->isAccepted()) {
388			clearPendingEvent(event->gbaKey());
389		}
390	}
391	for (int button : oldButtons) {
392		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, this);
393		clearPendingEvent(event->gbaKey());
394		QApplication::sendEvent(QApplication::focusWidget(), event);
395	}
396#endif
397}
398
399void InputController::postPendingEvent(GBAKey key) {
400	m_pendingEvents.insert(key);
401}
402
403void InputController::clearPendingEvent(GBAKey key) {
404	m_pendingEvents.remove(key);
405}
406
407bool InputController::hasPendingEvent(GBAKey key) const {
408	return m_pendingEvents.contains(key);
409}