all repos — mgba @ b6bc8d54e3ba7f8d11c1888003b18bab738c1e89

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#include "InputProfile.h"
 12#include "LogController.h"
 13#include "utils.h"
 14
 15#include <QApplication>
 16#include <QTimer>
 17#include <QWidget>
 18#ifdef BUILD_QT_MULTIMEDIA
 19#include <QCameraInfo>
 20#include <QVideoSurfaceFormat>
 21#endif
 22
 23#include <mgba/core/interface.h>
 24#include <mgba-util/configuration.h>
 25
 26using namespace QGBA;
 27
 28#ifdef BUILD_SDL
 29int InputController::s_sdlInited = 0;
 30mSDLEvents InputController::s_sdlEvents;
 31#endif
 32
 33InputController::InputController(int playerId, QWidget* topLevel, QObject* parent)
 34	: QObject(parent)
 35	, m_playerId(playerId)
 36	, m_topLevel(topLevel)
 37	, m_focusParent(topLevel)
 38{
 39	mInputMapInit(&m_inputMap, &GBAInputInfo);
 40
 41#ifdef BUILD_SDL
 42	if (s_sdlInited == 0) {
 43		mSDLInitEvents(&s_sdlEvents);
 44	}
 45	++s_sdlInited;
 46	m_sdlPlayer.bindings = &m_inputMap;
 47	mSDLInitBindingsGBA(&m_inputMap);
 48	updateJoysticks();
 49#endif
 50
 51#ifdef BUILD_SDL
 52	connect(&m_gamepadTimer, &QTimer::timeout, [this]() {
 53		testGamepad(SDL_BINDING_BUTTON);
 54		if (m_playerId == 0) {
 55			updateJoysticks();
 56		}
 57	});
 58#endif
 59	m_gamepadTimer.setInterval(50);
 60	m_gamepadTimer.start();
 61
 62#ifdef BUILD_QT_MULTIMEDIA
 63	connect(&m_videoDumper, &VideoDumper::imageAvailable, this, &InputController::setCamImage);
 64#endif
 65
 66	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_X, GBA_KEY_A);
 67	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Z, GBA_KEY_B);
 68	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_A, GBA_KEY_L);
 69	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_S, GBA_KEY_R);
 70	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Return, GBA_KEY_START);
 71	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Backspace, GBA_KEY_SELECT);
 72	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Up, GBA_KEY_UP);
 73	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Down, GBA_KEY_DOWN);
 74	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Left, GBA_KEY_LEFT);
 75	mInputBindKey(&m_inputMap, KEYBOARD, Qt::Key_Right, GBA_KEY_RIGHT);
 76
 77
 78#ifdef M_CORE_GBA
 79	m_lux.p = this;
 80	m_lux.sample = [](GBALuminanceSource* context) {
 81		InputControllerLux* lux = static_cast<InputControllerLux*>(context);
 82		lux->value = 0xFF - lux->p->m_luxValue;
 83	};
 84
 85	m_lux.readLuminance = [](GBALuminanceSource* context) {
 86		InputControllerLux* lux = static_cast<InputControllerLux*>(context);
 87		return lux->value;
 88	};
 89	setLuminanceLevel(0);
 90#endif
 91
 92	m_image.p = this;
 93	m_image.startRequestImage = [](mImageSource* context, unsigned w, unsigned h, int) {
 94		InputControllerImage* image = static_cast<InputControllerImage*>(context);
 95		image->w = w;
 96		image->h = h;
 97		if (image->image.isNull()) {
 98			image->image.load(":/res/no-cam.png");
 99		}
100#ifdef BUILD_QT_MULTIMEDIA
101		if (image->p->m_config->getQtOption("cameraDriver").toInt() == static_cast<int>(CameraDriver::QT_MULTIMEDIA)) {
102			QByteArray camera = image->p->m_config->getQtOption("camera").toByteArray();
103			if (!camera.isNull()) {
104				QMetaObject::invokeMethod(image->p, "setCamera", Q_ARG(QByteArray, camera));
105			}
106			QMetaObject::invokeMethod(image->p, "setupCam");
107		}
108#endif
109	};
110
111	m_image.stopRequestImage = [](mImageSource* context) {
112		InputControllerImage* image = static_cast<InputControllerImage*>(context);
113#ifdef BUILD_QT_MULTIMEDIA
114		QMetaObject::invokeMethod(image->p, "teardownCam");
115#endif
116	};
117
118	m_image.requestImage = [](mImageSource* context, const void** buffer, size_t* stride, mColorFormat* format) {
119		InputControllerImage* image = static_cast<InputControllerImage*>(context);
120		QSize size;
121		{
122			QMutexLocker locker(&image->mutex);
123			if (image->outOfDate) {
124				image->resizedImage = image->image.scaled(image->w, image->h, Qt::KeepAspectRatioByExpanding);
125				image->resizedImage = image->resizedImage.convertToFormat(QImage::Format_RGB16);
126				image->outOfDate = false;
127			}
128		}
129		size = image->resizedImage.size();
130		const uint16_t* bits = reinterpret_cast<const uint16_t*>(image->resizedImage.constBits());
131		if (size.width() > image->w) {
132			bits += (size.width() - image->w) / 2;
133		}
134		if (size.height() > image->h) {
135			bits += ((size.height() - image->h) / 2) * size.width();
136		}
137		*buffer = bits;
138		*stride = image->resizedImage.bytesPerLine() / sizeof(*bits);
139		*format = mCOLOR_RGB565;
140	};
141}
142
143InputController::~InputController() {
144	mInputMapDeinit(&m_inputMap);
145
146#ifdef BUILD_SDL
147	if (m_playerAttached) {
148		mSDLDetachPlayer(&s_sdlEvents, &m_sdlPlayer);
149	}
150
151	--s_sdlInited;
152	if (s_sdlInited == 0) {
153		mSDLDeinitEvents(&s_sdlEvents);
154	}
155#endif
156}
157
158void InputController::setConfiguration(ConfigController* config) {
159	m_config = config;
160	loadConfiguration(KEYBOARD);
161#ifdef BUILD_SDL
162	mSDLEventsLoadConfig(&s_sdlEvents, config->input());
163	if (!m_playerAttached) {
164		m_playerAttached = mSDLAttachPlayer(&s_sdlEvents, &m_sdlPlayer);
165	}
166	loadConfiguration(SDL_BINDING_BUTTON);
167	loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
168#endif
169}
170
171void InputController::loadConfiguration(uint32_t type) {
172	mInputMapLoad(&m_inputMap, type, m_config->input());
173#ifdef BUILD_SDL
174	if (m_playerAttached) {
175		mSDLPlayerLoadConfig(&m_sdlPlayer, m_config->input());
176	}
177#endif
178}
179
180void InputController::loadProfile(uint32_t type, const QString& profile) {
181	if (profile.isEmpty()) {
182		return;
183	}
184	bool loaded = mInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
185	recalibrateAxes();
186	if (!loaded) {
187		const InputProfile* ip = InputProfile::findProfile(profile);
188		if (ip) {
189			ip->apply(this);
190		}
191	}
192	emit profileLoaded(profile);
193}
194
195void InputController::saveConfiguration() {
196	saveConfiguration(KEYBOARD);
197#ifdef BUILD_SDL
198	saveConfiguration(SDL_BINDING_BUTTON);
199	saveProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
200	if (m_playerAttached) {
201		mSDLPlayerSaveConfig(&m_sdlPlayer, m_config->input());
202	}
203#endif
204	m_config->write();
205}
206
207void InputController::saveConfiguration(uint32_t type) {
208	mInputMapSave(&m_inputMap, type, m_config->input());
209	m_config->write();
210}
211
212void InputController::saveProfile(uint32_t type, const QString& profile) {
213	if (profile.isEmpty()) {
214		return;
215	}
216	mInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
217	m_config->write();
218}
219
220const char* InputController::profileForType(uint32_t type) {
221	UNUSED(type);
222#ifdef BUILD_SDL
223	if (type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
224#if SDL_VERSION_ATLEAST(2, 0, 0)
225		return SDL_JoystickName(m_sdlPlayer.joystick->joystick);
226#else
227		return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick->joystick));
228#endif
229	}
230#endif
231	return 0;
232}
233
234QStringList InputController::connectedGamepads(uint32_t type) const {
235	UNUSED(type);
236
237#ifdef BUILD_SDL
238	if (type == SDL_BINDING_BUTTON) {
239		QStringList pads;
240		for (size_t i = 0; i < SDL_JoystickListSize(&s_sdlEvents.joysticks); ++i) {
241			const char* name;
242#if SDL_VERSION_ATLEAST(2, 0, 0)
243			name = SDL_JoystickName(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, i)->joystick);
244#else
245			name = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, i)->joystick));
246#endif
247			if (name) {
248				pads.append(QString(name));
249			} else {
250				pads.append(QString());
251			}
252		}
253		return pads;
254	}
255#endif
256
257	return QStringList();
258}
259
260int InputController::gamepad(uint32_t type) const {
261#ifdef BUILD_SDL
262	if (type == SDL_BINDING_BUTTON) {
263		return m_sdlPlayer.joystick ? m_sdlPlayer.joystick->index : 0;
264	}
265#endif
266	return 0;
267}
268
269void InputController::setGamepad(uint32_t type, int index) {
270#ifdef BUILD_SDL
271	if (type == SDL_BINDING_BUTTON) {
272		mSDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index);
273	}
274#endif
275}
276
277void InputController::setPreferredGamepad(uint32_t type, int index) {
278	if (!m_config) {
279		return;
280	}
281#ifdef BUILD_SDL
282#if SDL_VERSION_ATLEAST(2, 0, 0)
283	char name[34] = {0};
284	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, index)->joystick), name, sizeof(name));
285#else
286	const char* name = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, index)->joystick));
287	if (!name) {
288		return;
289	}
290#endif
291	mInputSetPreferredDevice(m_config->input(), "gba", type, m_playerId, name);
292#else
293	UNUSED(type);
294	UNUSED(index);
295#endif
296}
297
298mRumble* InputController::rumble() {
299#ifdef BUILD_SDL
300#if SDL_VERSION_ATLEAST(2, 0, 0)
301	if (m_playerAttached) {
302		return &m_sdlPlayer.rumble.d;
303	}
304#endif
305#endif
306	return nullptr;
307}
308
309mRotationSource* InputController::rotationSource() {
310#ifdef BUILD_SDL
311	if (m_playerAttached) {
312		return &m_sdlPlayer.rotation.d;
313	}
314#endif
315	return nullptr;
316}
317
318void InputController::registerTiltAxisX(int axis) {
319#ifdef BUILD_SDL
320	if (m_playerAttached) {
321		m_sdlPlayer.rotation.axisX = axis;
322	}
323#endif
324}
325
326void InputController::registerTiltAxisY(int axis) {
327#ifdef BUILD_SDL
328	if (m_playerAttached) {
329		m_sdlPlayer.rotation.axisY = axis;
330	}
331#endif
332}
333
334void InputController::registerGyroAxisX(int axis) {
335#ifdef BUILD_SDL
336	if (m_playerAttached) {
337		m_sdlPlayer.rotation.gyroX = axis;
338	}
339#endif
340}
341
342void InputController::registerGyroAxisY(int axis) {
343#ifdef BUILD_SDL
344	if (m_playerAttached) {
345		m_sdlPlayer.rotation.gyroY = axis;
346	}
347#endif
348}
349
350float InputController::gyroSensitivity() const {
351#ifdef BUILD_SDL
352	if (m_playerAttached) {
353		return m_sdlPlayer.rotation.gyroSensitivity;
354	}
355#endif
356	return 0;
357}
358
359void InputController::setGyroSensitivity(float sensitivity) {
360#ifdef BUILD_SDL
361	if (m_playerAttached) {
362		m_sdlPlayer.rotation.gyroSensitivity = sensitivity;
363	}
364#endif
365}
366
367GBAKey InputController::mapKeyboard(int key) const {
368	return static_cast<GBAKey>(mInputMapKey(&m_inputMap, KEYBOARD, key));
369}
370
371void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
372	return mInputBindKey(&m_inputMap, type, key, gbaKey);
373}
374
375void InputController::updateJoysticks() {
376#ifdef BUILD_SDL
377	QString profile = profileForType(SDL_BINDING_BUTTON);
378	mSDLUpdateJoysticks(&s_sdlEvents, m_config->input());
379	QString newProfile = profileForType(SDL_BINDING_BUTTON);
380	if (profile != newProfile) {
381		loadProfile(SDL_BINDING_BUTTON, newProfile);
382	}
383#endif
384}
385
386int InputController::pollEvents() {
387	int activeButtons = 0;
388#ifdef BUILD_SDL
389	if (m_playerAttached && m_sdlPlayer.joystick) {
390		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
391		SDL_JoystickUpdate();
392		int numButtons = SDL_JoystickNumButtons(joystick);
393		int i;
394		QReadLocker l(&m_eventsLock);
395		for (i = 0; i < numButtons; ++i) {
396			GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i));
397			if (key == GBA_KEY_NONE) {
398				continue;
399			}
400			if (hasPendingEvent(key)) {
401				continue;
402			}
403			if (SDL_JoystickGetButton(joystick, i)) {
404				activeButtons |= 1 << key;
405			}
406		}
407		l.unlock();
408		int numHats = SDL_JoystickNumHats(joystick);
409		for (i = 0; i < numHats; ++i) {
410			int hat = SDL_JoystickGetHat(joystick, i);
411			activeButtons |= mInputMapHat(&m_inputMap, SDL_BINDING_BUTTON, i, hat);
412		}
413
414		int numAxes = SDL_JoystickNumAxes(joystick);
415		for (i = 0; i < numAxes; ++i) {
416			int value = SDL_JoystickGetAxis(joystick, i);
417
418			enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value));
419			if (key != GBA_KEY_NONE) {
420				activeButtons |= 1 << key;
421			}
422		}
423	}
424#endif
425	return activeButtons;
426}
427
428QSet<int> InputController::activeGamepadButtons(int type) {
429	QSet<int> activeButtons;
430#ifdef BUILD_SDL
431	if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
432		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
433		SDL_JoystickUpdate();
434		int numButtons = SDL_JoystickNumButtons(joystick);
435		int i;
436		for (i = 0; i < numButtons; ++i) {
437			if (SDL_JoystickGetButton(joystick, i)) {
438				activeButtons.insert(i);
439			}
440		}
441	}
442#endif
443	return activeButtons;
444}
445
446void InputController::recalibrateAxes() {
447#ifdef BUILD_SDL
448	if (m_playerAttached && m_sdlPlayer.joystick) {
449		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
450		SDL_JoystickUpdate();
451		int numAxes = SDL_JoystickNumAxes(joystick);
452		if (numAxes < 1) {
453			return;
454		}
455		m_deadzones.resize(numAxes);
456		int i;
457		for (i = 0; i < numAxes; ++i) {
458			m_deadzones[i] = SDL_JoystickGetAxis(joystick, i);
459		}
460	}
461#endif
462}
463
464QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes(int type) {
465	QSet<QPair<int, GamepadAxisEvent::Direction>> activeAxes;
466#ifdef BUILD_SDL
467	if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
468		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
469		SDL_JoystickUpdate();
470		int numAxes = SDL_JoystickNumAxes(joystick);
471		if (numAxes < 1) {
472			return activeAxes;
473		}
474		m_deadzones.resize(numAxes);
475		int i;
476		for (i = 0; i < numAxes; ++i) {
477			int32_t axis = SDL_JoystickGetAxis(joystick, i);
478			axis -= m_deadzones[i];
479			if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) {
480				activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE));
481			}
482		}
483	}
484#endif
485	return activeAxes;
486}
487
488void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) {
489	const mInputAxis* old = mInputQueryAxis(&m_inputMap, type, axis);
490	mInputAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD };
491	if (old) {
492		description = *old;
493	}
494	int deadzone = 0;
495	if (axis > 0 && m_deadzones.size() > axis) {
496		deadzone = m_deadzones[axis];
497	}
498	switch (direction) {
499	case GamepadAxisEvent::NEGATIVE:
500		description.lowDirection = key;
501
502		description.deadLow = deadzone - AXIS_THRESHOLD;
503		break;
504	case GamepadAxisEvent::POSITIVE:
505		description.highDirection = key;
506		description.deadHigh = deadzone + AXIS_THRESHOLD;
507		break;
508	default:
509		return;
510	}
511	mInputBindAxis(&m_inputMap, type, axis, &description);
512}
513
514void InputController::unbindAllAxes(uint32_t type) {
515	mInputUnbindAllAxes(&m_inputMap, type);
516}
517
518QSet<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(int type) {
519	QSet<QPair<int, GamepadHatEvent::Direction>> activeHats;
520#ifdef BUILD_SDL
521	if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
522		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
523		SDL_JoystickUpdate();
524		int numHats = SDL_JoystickNumHats(joystick);
525		if (numHats < 1) {
526			return activeHats;
527		}
528
529		int i;
530		for (i = 0; i < numHats; ++i) {
531			int hat = SDL_JoystickGetHat(joystick, i);
532			if (hat & GamepadHatEvent::UP) {
533				activeHats.insert(qMakePair(i, GamepadHatEvent::UP));
534			}
535			if (hat & GamepadHatEvent::RIGHT) {
536				activeHats.insert(qMakePair(i, GamepadHatEvent::RIGHT));
537			}
538			if (hat & GamepadHatEvent::DOWN) {
539				activeHats.insert(qMakePair(i, GamepadHatEvent::DOWN));
540			}
541			if (hat & GamepadHatEvent::LEFT) {
542				activeHats.insert(qMakePair(i, GamepadHatEvent::LEFT));
543			}
544		}
545	}
546#endif
547	return activeHats;
548}
549
550void InputController::bindHat(uint32_t type, int hat, GamepadHatEvent::Direction direction, GBAKey gbaKey) {
551	mInputHatBindings bindings{ -1, -1, -1, -1 };
552	mInputQueryHat(&m_inputMap, type, hat, &bindings);
553	switch (direction) {
554	case GamepadHatEvent::UP:
555		bindings.up = gbaKey;
556		break;
557	case GamepadHatEvent::RIGHT:
558		bindings.right = gbaKey;
559		break;
560	case GamepadHatEvent::DOWN:
561		bindings.down = gbaKey;
562		break;
563	case GamepadHatEvent::LEFT:
564		bindings.left = gbaKey;
565		break;
566	default:
567		return;
568	}
569	mInputBindHat(&m_inputMap, type, hat, &bindings);
570}
571
572void InputController::testGamepad(int type) {
573	QWriteLocker l(&m_eventsLock);
574	auto activeAxes = activeGamepadAxes(type);
575	auto oldAxes = m_activeAxes;
576	m_activeAxes = activeAxes;
577
578	auto activeButtons = activeGamepadButtons(type);
579	auto oldButtons = m_activeButtons;
580	m_activeButtons = activeButtons;
581
582	auto activeHats = activeGamepadHats(type);
583	auto oldHats = m_activeHats;
584	m_activeHats = activeHats;
585
586	if (!QApplication::focusWidget()) {
587		return;
588	}
589
590	activeAxes.subtract(oldAxes);
591	oldAxes.subtract(m_activeAxes);
592
593	for (auto& axis : m_activeAxes) {
594		bool newlyAboveThreshold = activeAxes.contains(axis);
595		if (newlyAboveThreshold) {
596			GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this);
597			postPendingEvent(event->gbaKey());
598			sendGamepadEvent(event);
599			if (!event->isAccepted()) {
600				clearPendingEvent(event->gbaKey());
601			}
602		}
603	}
604	for (auto axis : oldAxes) {
605		GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this);
606		clearPendingEvent(event->gbaKey());
607		sendGamepadEvent(event);
608	}
609
610	if (!QApplication::focusWidget()) {
611		return;
612	}
613
614	activeButtons.subtract(oldButtons);
615	oldButtons.subtract(m_activeButtons);
616
617	for (int button : activeButtons) {
618		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this);
619		postPendingEvent(event->gbaKey());
620		sendGamepadEvent(event);
621		if (!event->isAccepted()) {
622			clearPendingEvent(event->gbaKey());
623		}
624	}
625	for (int button : oldButtons) {
626		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this);
627		clearPendingEvent(event->gbaKey());
628		sendGamepadEvent(event);
629	}
630
631	activeHats.subtract(oldHats);
632	oldHats.subtract(m_activeHats);
633
634	for (auto& hat : activeHats) {
635		GamepadHatEvent* event = new GamepadHatEvent(GamepadHatEvent::Down(), hat.first, hat.second, type, this);
636		postPendingEvent(event->gbaKey());
637		sendGamepadEvent(event);
638		if (!event->isAccepted()) {
639			clearPendingEvent(event->gbaKey());
640		}
641	}
642	for (auto& hat : oldHats) {
643		GamepadHatEvent* event = new GamepadHatEvent(GamepadHatEvent::Up(), hat.first, hat.second, type, this);
644		clearPendingEvent(event->gbaKey());
645		sendGamepadEvent(event);
646	}
647}
648
649void InputController::sendGamepadEvent(QEvent* event) {
650	QWidget* focusWidget = nullptr;
651	if (m_focusParent) {
652		focusWidget = m_focusParent->focusWidget();
653		if (!focusWidget) {
654			focusWidget = m_focusParent;
655		}
656	} else {
657		focusWidget = QApplication::focusWidget();
658	}
659	QApplication::postEvent(focusWidget, event, Qt::HighEventPriority);
660}
661
662void InputController::postPendingEvent(GBAKey key) {
663	m_pendingEvents.insert(key);
664}
665
666void InputController::clearPendingEvent(GBAKey key) {
667	m_pendingEvents.remove(key);
668}
669
670bool InputController::hasPendingEvent(GBAKey key) const {
671	return m_pendingEvents.contains(key);
672}
673
674void InputController::suspendScreensaver() {
675#ifdef BUILD_SDL
676#if SDL_VERSION_ATLEAST(2, 0, 0)
677	mSDLSuspendScreensaver(&s_sdlEvents);
678#endif
679#endif
680}
681
682void InputController::resumeScreensaver() {
683#ifdef BUILD_SDL
684#if SDL_VERSION_ATLEAST(2, 0, 0)
685	mSDLResumeScreensaver(&s_sdlEvents);
686#endif
687#endif
688}
689
690void InputController::setScreensaverSuspendable(bool suspendable) {
691#ifdef BUILD_SDL
692#if SDL_VERSION_ATLEAST(2, 0, 0)
693	mSDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
694#endif
695#endif
696}
697
698void InputController::stealFocus(QWidget* focus) {
699	m_focusParent = focus;
700}
701
702void InputController::releaseFocus(QWidget* focus) {
703	if (focus == m_focusParent) {
704		m_focusParent = m_topLevel;
705	}
706}
707
708void InputController::loadCamImage(const QString& path) {
709	setCamImage(QImage(path));
710}
711
712void InputController::setCamImage(const QImage& image) {
713	if (image.isNull()) {
714		return;
715	}
716	QMutexLocker locker(&m_image.mutex);
717	m_image.image = image;
718	m_image.resizedImage = QImage();
719	m_image.outOfDate = true;
720}
721
722QList<QPair<QByteArray, QString>> InputController::listCameras() const {
723	QList<QPair<QByteArray, QString>> out;
724#ifdef BUILD_QT_MULTIMEDIA
725	QList<QCameraInfo> cams = QCameraInfo::availableCameras();
726	for (const auto& cam : cams) {
727		out.append(qMakePair(cam.deviceName().toLatin1(), cam.description()));
728	}
729#endif
730	return out;
731}
732
733void InputController::increaseLuminanceLevel() {
734	setLuminanceLevel(m_luxLevel + 1);
735}
736
737void InputController::decreaseLuminanceLevel() {
738	setLuminanceLevel(m_luxLevel - 1);
739}
740
741void InputController::setLuminanceLevel(int level) {
742	int value = 0x16;
743	level = clamp(level, 0, 10);
744	if (level > 0) {
745		value += GBA_LUX_LEVELS[level - 1];
746	}
747	setLuminanceValue(value);
748}
749
750void InputController::setLuminanceValue(uint8_t value) {
751	m_luxValue = value;
752	value = std::max<int>(value - 0x16, 0);
753	m_luxLevel = 10;
754	for (int i = 0; i < 10; ++i) {
755		if (value < GBA_LUX_LEVELS[i]) {
756			m_luxLevel = i;
757			break;
758		}
759	}
760	emit luminanceValueChanged(m_luxValue);
761}
762
763void InputController::setupCam() {
764#ifdef BUILD_QT_MULTIMEDIA
765	if (!m_camera) {
766		m_camera = std::make_unique<QCamera>();
767		connect(m_camera.get(), &QCamera::statusChanged, this, &InputController::prepareCamSettings, Qt::QueuedConnection);
768	}
769	m_camera->setCaptureMode(QCamera::CaptureVideo);
770	m_camera->setViewfinder(&m_videoDumper);
771	m_camera->load();
772#endif
773}
774
775#ifdef BUILD_QT_MULTIMEDIA
776void InputController::prepareCamSettings(QCamera::Status status) {
777	if (status != QCamera::LoadedStatus || m_camera->state() == QCamera::ActiveState) {
778		return;
779	}
780#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
781	QCameraViewfinderSettings settings;
782	QSize size(1280, 720);
783	auto cameraRes = m_camera->supportedViewfinderResolutions(settings);
784	for (auto& cameraSize : cameraRes) {
785		if (cameraSize.width() < m_image.w || cameraSize.height() < m_image.h) {
786			continue;
787		}
788		if (cameraSize.width() <= size.width() && cameraSize.height() <= size.height()) {
789			size = cameraSize;
790		}
791	}
792	settings.setResolution(size);
793
794	auto cameraFormats = m_camera->supportedViewfinderPixelFormats(settings);
795	auto goodFormats = m_videoDumper.supportedPixelFormats();
796	bool goodFormatFound = false;
797	for (const auto& goodFormat : goodFormats) {
798		if (cameraFormats.contains(goodFormat)) {
799			settings.setPixelFormat(goodFormat);
800			goodFormatFound = true;
801			break;
802		}
803	}
804	if (!goodFormatFound) {
805		LOG(QT, WARN) << "Could not find a valid camera format!";
806		for (const auto& format : cameraFormats) {
807			LOG(QT, WARN) << "Camera supported format: " << QString::number(format);
808		}
809	}
810	m_camera->setViewfinderSettings(settings);
811#endif
812	m_camera->start();
813}
814#endif
815
816void InputController::teardownCam() {
817#ifdef BUILD_QT_MULTIMEDIA
818	if (m_camera) {
819		m_camera->stop();
820	}
821#endif
822}
823
824void InputController::setCamera(const QByteArray& name) {
825#ifdef BUILD_QT_MULTIMEDIA
826	bool needsRestart = false;
827	if (m_camera) {
828		needsRestart = m_camera->state() == QCamera::ActiveState;
829	}
830	m_camera = std::make_unique<QCamera>(name);
831	connect(m_camera.get(), &QCamera::statusChanged, this, &InputController::prepareCamSettings, Qt::QueuedConnection);
832	if (needsRestart) {
833		setupCam();
834	}
835#endif
836}