all repos — mgba @ 33098926577f52a74730de1708a427e275a9bc88

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	bool loaded = mInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
182	recalibrateAxes();
183	if (!loaded) {
184		const InputProfile* ip = InputProfile::findProfile(profile);
185		if (ip) {
186			ip->apply(this);
187		}
188	}
189	emit profileLoaded(profile);
190}
191
192void InputController::saveConfiguration() {
193	saveConfiguration(KEYBOARD);
194#ifdef BUILD_SDL
195	saveConfiguration(SDL_BINDING_BUTTON);
196	saveProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
197	if (m_playerAttached) {
198		mSDLPlayerSaveConfig(&m_sdlPlayer, m_config->input());
199	}
200#endif
201	m_config->write();
202}
203
204void InputController::saveConfiguration(uint32_t type) {
205	mInputMapSave(&m_inputMap, type, m_config->input());
206	m_config->write();
207}
208
209void InputController::saveProfile(uint32_t type, const QString& profile) {
210	mInputProfileSave(&m_inputMap, type, m_config->input(), profile.toUtf8().constData());
211	m_config->write();
212}
213
214const char* InputController::profileForType(uint32_t type) {
215	UNUSED(type);
216#ifdef BUILD_SDL
217	if (type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
218#if SDL_VERSION_ATLEAST(2, 0, 0)
219		return SDL_JoystickName(m_sdlPlayer.joystick->joystick);
220#else
221		return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick->joystick));
222#endif
223	}
224#endif
225	return 0;
226}
227
228QStringList InputController::connectedGamepads(uint32_t type) const {
229	UNUSED(type);
230
231#ifdef BUILD_SDL
232	if (type == SDL_BINDING_BUTTON) {
233		QStringList pads;
234		for (size_t i = 0; i < SDL_JoystickListSize(&s_sdlEvents.joysticks); ++i) {
235			const char* name;
236#if SDL_VERSION_ATLEAST(2, 0, 0)
237			name = SDL_JoystickName(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, i)->joystick);
238#else
239			name = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, i)->joystick));
240#endif
241			if (name) {
242				pads.append(QString(name));
243			} else {
244				pads.append(QString());
245			}
246		}
247		return pads;
248	}
249#endif
250
251	return QStringList();
252}
253
254int InputController::gamepad(uint32_t type) const {
255#ifdef BUILD_SDL
256	if (type == SDL_BINDING_BUTTON) {
257		return m_sdlPlayer.joystick ? m_sdlPlayer.joystick->index : 0;
258	}
259#endif
260	return 0;
261}
262
263void InputController::setGamepad(uint32_t type, int index) {
264#ifdef BUILD_SDL
265	if (type == SDL_BINDING_BUTTON) {
266		mSDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index);
267	}
268#endif
269}
270
271void InputController::setPreferredGamepad(uint32_t type, int index) {
272	if (!m_config) {
273		return;
274	}
275#ifdef BUILD_SDL
276#if SDL_VERSION_ATLEAST(2, 0, 0)
277	char name[34] = {0};
278	SDL_JoystickGetGUIDString(SDL_JoystickGetGUID(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, index)->joystick), name, sizeof(name));
279#else
280	const char* name = SDL_JoystickName(SDL_JoystickIndex(SDL_JoystickListGetPointer(&s_sdlEvents.joysticks, index)->joystick));
281	if (!name) {
282		return;
283	}
284#endif
285	mInputSetPreferredDevice(m_config->input(), "gba", type, m_playerId, name);
286#else
287	UNUSED(type);
288	UNUSED(index);
289#endif
290}
291
292mRumble* InputController::rumble() {
293#ifdef BUILD_SDL
294#if SDL_VERSION_ATLEAST(2, 0, 0)
295	if (m_playerAttached) {
296		return &m_sdlPlayer.rumble.d;
297	}
298#endif
299#endif
300	return nullptr;
301}
302
303mRotationSource* InputController::rotationSource() {
304#ifdef BUILD_SDL
305	if (m_playerAttached) {
306		return &m_sdlPlayer.rotation.d;
307	}
308#endif
309	return nullptr;
310}
311
312void InputController::registerTiltAxisX(int axis) {
313#ifdef BUILD_SDL
314	if (m_playerAttached) {
315		m_sdlPlayer.rotation.axisX = axis;
316	}
317#endif
318}
319
320void InputController::registerTiltAxisY(int axis) {
321#ifdef BUILD_SDL
322	if (m_playerAttached) {
323		m_sdlPlayer.rotation.axisY = axis;
324	}
325#endif
326}
327
328void InputController::registerGyroAxisX(int axis) {
329#ifdef BUILD_SDL
330	if (m_playerAttached) {
331		m_sdlPlayer.rotation.gyroX = axis;
332	}
333#endif
334}
335
336void InputController::registerGyroAxisY(int axis) {
337#ifdef BUILD_SDL
338	if (m_playerAttached) {
339		m_sdlPlayer.rotation.gyroY = axis;
340	}
341#endif
342}
343
344float InputController::gyroSensitivity() const {
345#ifdef BUILD_SDL
346	if (m_playerAttached) {
347		return m_sdlPlayer.rotation.gyroSensitivity;
348	}
349#endif
350	return 0;
351}
352
353void InputController::setGyroSensitivity(float sensitivity) {
354#ifdef BUILD_SDL
355	if (m_playerAttached) {
356		m_sdlPlayer.rotation.gyroSensitivity = sensitivity;
357	}
358#endif
359}
360
361GBAKey InputController::mapKeyboard(int key) const {
362	return static_cast<GBAKey>(mInputMapKey(&m_inputMap, KEYBOARD, key));
363}
364
365void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
366	return mInputBindKey(&m_inputMap, type, key, gbaKey);
367}
368
369void InputController::updateJoysticks() {
370#ifdef BUILD_SDL
371	QString profile = profileForType(SDL_BINDING_BUTTON);
372	mSDLUpdateJoysticks(&s_sdlEvents, m_config->input());
373	QString newProfile = profileForType(SDL_BINDING_BUTTON);
374	if (profile != newProfile) {
375		loadProfile(SDL_BINDING_BUTTON, newProfile);
376	}
377#endif
378}
379
380int InputController::pollEvents() {
381	int activeButtons = 0;
382#ifdef BUILD_SDL
383	if (m_playerAttached && m_sdlPlayer.joystick) {
384		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
385		SDL_JoystickUpdate();
386		int numButtons = SDL_JoystickNumButtons(joystick);
387		int i;
388		QReadLocker l(&m_eventsLock);
389		for (i = 0; i < numButtons; ++i) {
390			GBAKey key = static_cast<GBAKey>(mInputMapKey(&m_inputMap, SDL_BINDING_BUTTON, i));
391			if (key == GBA_KEY_NONE) {
392				continue;
393			}
394			if (hasPendingEvent(key)) {
395				continue;
396			}
397			if (SDL_JoystickGetButton(joystick, i)) {
398				activeButtons |= 1 << key;
399			}
400		}
401		l.unlock();
402		int numHats = SDL_JoystickNumHats(joystick);
403		for (i = 0; i < numHats; ++i) {
404			int hat = SDL_JoystickGetHat(joystick, i);
405			activeButtons |= mInputMapHat(&m_inputMap, SDL_BINDING_BUTTON, i, hat);
406		}
407
408		int numAxes = SDL_JoystickNumAxes(joystick);
409		for (i = 0; i < numAxes; ++i) {
410			int value = SDL_JoystickGetAxis(joystick, i);
411
412			enum GBAKey key = static_cast<GBAKey>(mInputMapAxis(&m_inputMap, SDL_BINDING_BUTTON, i, value));
413			if (key != GBA_KEY_NONE) {
414				activeButtons |= 1 << key;
415			}
416		}
417	}
418#endif
419	return activeButtons;
420}
421
422QSet<int> InputController::activeGamepadButtons(int type) {
423	QSet<int> activeButtons;
424#ifdef BUILD_SDL
425	if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
426		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
427		SDL_JoystickUpdate();
428		int numButtons = SDL_JoystickNumButtons(joystick);
429		int i;
430		for (i = 0; i < numButtons; ++i) {
431			if (SDL_JoystickGetButton(joystick, i)) {
432				activeButtons.insert(i);
433			}
434		}
435	}
436#endif
437	return activeButtons;
438}
439
440void InputController::recalibrateAxes() {
441#ifdef BUILD_SDL
442	if (m_playerAttached && m_sdlPlayer.joystick) {
443		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
444		SDL_JoystickUpdate();
445		int numAxes = SDL_JoystickNumAxes(joystick);
446		if (numAxes < 1) {
447			return;
448		}
449		m_deadzones.resize(numAxes);
450		int i;
451		for (i = 0; i < numAxes; ++i) {
452			m_deadzones[i] = SDL_JoystickGetAxis(joystick, i);
453		}
454	}
455#endif
456}
457
458QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes(int type) {
459	QSet<QPair<int, GamepadAxisEvent::Direction>> activeAxes;
460#ifdef BUILD_SDL
461	if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
462		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
463		SDL_JoystickUpdate();
464		int numAxes = SDL_JoystickNumAxes(joystick);
465		if (numAxes < 1) {
466			return activeAxes;
467		}
468		m_deadzones.resize(numAxes);
469		int i;
470		for (i = 0; i < numAxes; ++i) {
471			int32_t axis = SDL_JoystickGetAxis(joystick, i);
472			axis -= m_deadzones[i];
473			if (axis >= AXIS_THRESHOLD || axis <= -AXIS_THRESHOLD) {
474				activeAxes.insert(qMakePair(i, axis > 0 ? GamepadAxisEvent::POSITIVE : GamepadAxisEvent::NEGATIVE));
475			}
476		}
477	}
478#endif
479	return activeAxes;
480}
481
482void InputController::bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction direction, GBAKey key) {
483	const mInputAxis* old = mInputQueryAxis(&m_inputMap, type, axis);
484	mInputAxis description = { GBA_KEY_NONE, GBA_KEY_NONE, -AXIS_THRESHOLD, AXIS_THRESHOLD };
485	if (old) {
486		description = *old;
487	}
488	int deadzone = 0;
489	if (axis > 0 && m_deadzones.size() > axis) {
490		deadzone = m_deadzones[axis];
491	}
492	switch (direction) {
493	case GamepadAxisEvent::NEGATIVE:
494		description.lowDirection = key;
495
496		description.deadLow = deadzone - AXIS_THRESHOLD;
497		break;
498	case GamepadAxisEvent::POSITIVE:
499		description.highDirection = key;
500		description.deadHigh = deadzone + AXIS_THRESHOLD;
501		break;
502	default:
503		return;
504	}
505	mInputBindAxis(&m_inputMap, type, axis, &description);
506}
507
508void InputController::unbindAllAxes(uint32_t type) {
509	mInputUnbindAllAxes(&m_inputMap, type);
510}
511
512QSet<QPair<int, GamepadHatEvent::Direction>> InputController::activeGamepadHats(int type) {
513	QSet<QPair<int, GamepadHatEvent::Direction>> activeHats;
514#ifdef BUILD_SDL
515	if (m_playerAttached && type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
516		SDL_Joystick* joystick = m_sdlPlayer.joystick->joystick;
517		SDL_JoystickUpdate();
518		int numHats = SDL_JoystickNumHats(joystick);
519		if (numHats < 1) {
520			return activeHats;
521		}
522
523		int i;
524		for (i = 0; i < numHats; ++i) {
525			int hat = SDL_JoystickGetHat(joystick, i);
526			if (hat & GamepadHatEvent::UP) {
527				activeHats.insert(qMakePair(i, GamepadHatEvent::UP));
528			}
529			if (hat & GamepadHatEvent::RIGHT) {
530				activeHats.insert(qMakePair(i, GamepadHatEvent::RIGHT));
531			}
532			if (hat & GamepadHatEvent::DOWN) {
533				activeHats.insert(qMakePair(i, GamepadHatEvent::DOWN));
534			}
535			if (hat & GamepadHatEvent::LEFT) {
536				activeHats.insert(qMakePair(i, GamepadHatEvent::LEFT));
537			}
538		}
539	}
540#endif
541	return activeHats;
542}
543
544void InputController::bindHat(uint32_t type, int hat, GamepadHatEvent::Direction direction, GBAKey gbaKey) {
545	mInputHatBindings bindings{ -1, -1, -1, -1 };
546	mInputQueryHat(&m_inputMap, type, hat, &bindings);
547	switch (direction) {
548	case GamepadHatEvent::UP:
549		bindings.up = gbaKey;
550		break;
551	case GamepadHatEvent::RIGHT:
552		bindings.right = gbaKey;
553		break;
554	case GamepadHatEvent::DOWN:
555		bindings.down = gbaKey;
556		break;
557	case GamepadHatEvent::LEFT:
558		bindings.left = gbaKey;
559		break;
560	default:
561		return;
562	}
563	mInputBindHat(&m_inputMap, type, hat, &bindings);
564}
565
566void InputController::testGamepad(int type) {
567	QWriteLocker l(&m_eventsLock);
568	auto activeAxes = activeGamepadAxes(type);
569	auto oldAxes = m_activeAxes;
570	m_activeAxes = activeAxes;
571
572	auto activeButtons = activeGamepadButtons(type);
573	auto oldButtons = m_activeButtons;
574	m_activeButtons = activeButtons;
575
576	auto activeHats = activeGamepadHats(type);
577	auto oldHats = m_activeHats;
578	m_activeHats = activeHats;
579
580	if (!QApplication::focusWidget()) {
581		return;
582	}
583
584	activeAxes.subtract(oldAxes);
585	oldAxes.subtract(m_activeAxes);
586
587	for (auto& axis : m_activeAxes) {
588		bool newlyAboveThreshold = activeAxes.contains(axis);
589		if (newlyAboveThreshold) {
590			GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this);
591			postPendingEvent(event->gbaKey());
592			sendGamepadEvent(event);
593			if (!event->isAccepted()) {
594				clearPendingEvent(event->gbaKey());
595			}
596		}
597	}
598	for (auto axis : oldAxes) {
599		GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this);
600		clearPendingEvent(event->gbaKey());
601		sendGamepadEvent(event);
602	}
603
604	if (!QApplication::focusWidget()) {
605		return;
606	}
607
608	activeButtons.subtract(oldButtons);
609	oldButtons.subtract(m_activeButtons);
610
611	for (int button : activeButtons) {
612		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this);
613		postPendingEvent(event->gbaKey());
614		sendGamepadEvent(event);
615		if (!event->isAccepted()) {
616			clearPendingEvent(event->gbaKey());
617		}
618	}
619	for (int button : oldButtons) {
620		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this);
621		clearPendingEvent(event->gbaKey());
622		sendGamepadEvent(event);
623	}
624
625	activeHats.subtract(oldHats);
626	oldHats.subtract(m_activeHats);
627
628	for (auto& hat : activeHats) {
629		GamepadHatEvent* event = new GamepadHatEvent(GamepadHatEvent::Down(), hat.first, hat.second, type, this);
630		postPendingEvent(event->gbaKey());
631		sendGamepadEvent(event);
632		if (!event->isAccepted()) {
633			clearPendingEvent(event->gbaKey());
634		}
635	}
636	for (auto& hat : oldHats) {
637		GamepadHatEvent* event = new GamepadHatEvent(GamepadHatEvent::Up(), hat.first, hat.second, type, this);
638		clearPendingEvent(event->gbaKey());
639		sendGamepadEvent(event);
640	}
641}
642
643void InputController::sendGamepadEvent(QEvent* event) {
644	QWidget* focusWidget = nullptr;
645	if (m_focusParent) {
646		focusWidget = m_focusParent->focusWidget();
647		if (!focusWidget) {
648			focusWidget = m_focusParent;
649		}
650	} else {
651		focusWidget = QApplication::focusWidget();
652	}
653	QApplication::postEvent(focusWidget, event, Qt::HighEventPriority);
654}
655
656void InputController::postPendingEvent(GBAKey key) {
657	m_pendingEvents.insert(key);
658}
659
660void InputController::clearPendingEvent(GBAKey key) {
661	m_pendingEvents.remove(key);
662}
663
664bool InputController::hasPendingEvent(GBAKey key) const {
665	return m_pendingEvents.contains(key);
666}
667
668void InputController::suspendScreensaver() {
669#ifdef BUILD_SDL
670#if SDL_VERSION_ATLEAST(2, 0, 0)
671	mSDLSuspendScreensaver(&s_sdlEvents);
672#endif
673#endif
674}
675
676void InputController::resumeScreensaver() {
677#ifdef BUILD_SDL
678#if SDL_VERSION_ATLEAST(2, 0, 0)
679	mSDLResumeScreensaver(&s_sdlEvents);
680#endif
681#endif
682}
683
684void InputController::setScreensaverSuspendable(bool suspendable) {
685#ifdef BUILD_SDL
686#if SDL_VERSION_ATLEAST(2, 0, 0)
687	mSDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
688#endif
689#endif
690}
691
692void InputController::stealFocus(QWidget* focus) {
693	m_focusParent = focus;
694}
695
696void InputController::releaseFocus(QWidget* focus) {
697	if (focus == m_focusParent) {
698		m_focusParent = m_topLevel;
699	}
700}
701
702void InputController::loadCamImage(const QString& path) {
703	setCamImage(QImage(path));
704}
705
706void InputController::setCamImage(const QImage& image) {
707	if (image.isNull()) {
708		return;
709	}
710	QMutexLocker locker(&m_image.mutex);
711	m_image.image = image;
712	m_image.resizedImage = QImage();
713	m_image.outOfDate = true;
714}
715
716QList<QPair<QByteArray, QString>> InputController::listCameras() const {
717	QList<QPair<QByteArray, QString>> out;
718#ifdef BUILD_QT_MULTIMEDIA
719	QList<QCameraInfo> cams = QCameraInfo::availableCameras();
720	for (const auto& cam : cams) {
721		out.append(qMakePair(cam.deviceName().toLatin1(), cam.description()));
722	}
723#endif
724	return out;
725}
726
727void InputController::increaseLuminanceLevel() {
728	setLuminanceLevel(m_luxLevel + 1);
729}
730
731void InputController::decreaseLuminanceLevel() {
732	setLuminanceLevel(m_luxLevel - 1);
733}
734
735void InputController::setLuminanceLevel(int level) {
736	int value = 0x16;
737	level = clamp(level, 0, 10);
738	if (level > 0) {
739		value += GBA_LUX_LEVELS[level - 1];
740	}
741	setLuminanceValue(value);
742}
743
744void InputController::setLuminanceValue(uint8_t value) {
745	m_luxValue = value;
746	value = std::max<int>(value - 0x16, 0);
747	m_luxLevel = 10;
748	for (int i = 0; i < 10; ++i) {
749		if (value < GBA_LUX_LEVELS[i]) {
750			m_luxLevel = i;
751			break;
752		}
753	}
754	emit luminanceValueChanged(m_luxValue);
755}
756
757void InputController::setupCam() {
758#ifdef BUILD_QT_MULTIMEDIA
759	if (!m_camera) {
760		m_camera = std::make_unique<QCamera>();
761		connect(m_camera.get(), &QCamera::statusChanged, this, &InputController::prepareCamSettings, Qt::QueuedConnection);
762	}
763	m_camera->setCaptureMode(QCamera::CaptureVideo);
764	m_camera->setViewfinder(&m_videoDumper);
765	m_camera->load();
766#endif
767}
768
769#ifdef BUILD_QT_MULTIMEDIA
770void InputController::prepareCamSettings(QCamera::Status status) {
771	if (status != QCamera::LoadedStatus || m_camera->state() == QCamera::ActiveState) {
772		return;
773	}
774#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
775	QCameraViewfinderSettings settings;
776	QSize size(1280, 720);
777	auto cameraRes = m_camera->supportedViewfinderResolutions(settings);
778	for (auto& cameraSize : cameraRes) {
779		if (cameraSize.width() < m_image.w || cameraSize.height() < m_image.h) {
780			continue;
781		}
782		if (cameraSize.width() <= size.width() && cameraSize.height() <= size.height()) {
783			size = cameraSize;
784		}
785	}
786	settings.setResolution(size);
787
788	auto cameraFormats = m_camera->supportedViewfinderPixelFormats(settings);
789	auto goodFormats = m_videoDumper.supportedPixelFormats();
790	bool goodFormatFound = false;
791	for (const auto& goodFormat : goodFormats) {
792		if (cameraFormats.contains(goodFormat)) {
793			settings.setPixelFormat(goodFormat);
794			goodFormatFound = true;
795			break;
796		}
797	}
798	if (!goodFormatFound) {
799		LOG(QT, WARN) << "Could not find a valid camera format!";
800		for (const auto& format : cameraFormats) {
801			LOG(QT, WARN) << "Camera supported format: " << QString::number(format);
802		}
803	}
804	m_camera->setViewfinderSettings(settings);
805#endif
806	m_camera->start();
807}
808#endif
809
810void InputController::teardownCam() {
811#ifdef BUILD_QT_MULTIMEDIA
812	if (m_camera) {
813		m_camera->stop();
814	}
815#endif
816}
817
818void InputController::setCamera(const QByteArray& name) {
819#ifdef BUILD_QT_MULTIMEDIA
820	bool needsRestart = false;
821	if (m_camera) {
822		needsRestart = m_camera->state() == QCamera::ActiveState;
823	}
824	m_camera = std::make_unique<QCamera>(name);
825	connect(m_camera.get(), &QCamera::statusChanged, this, &InputController::prepareCamSettings, Qt::QueuedConnection);
826	if (needsRestart) {
827		setupCam();
828	}
829#endif
830}