all repos — mgba @ c4b38790f211b65cb15af26cebe9fc25e3c19914

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::unbindAllHats(uint32_t type) {
573	mInputUnbindAllHats(&m_inputMap, type);
574}
575
576void InputController::testGamepad(int type) {
577	QWriteLocker l(&m_eventsLock);
578	auto activeAxes = activeGamepadAxes(type);
579	auto oldAxes = m_activeAxes;
580	m_activeAxes = activeAxes;
581
582	auto activeButtons = activeGamepadButtons(type);
583	auto oldButtons = m_activeButtons;
584	m_activeButtons = activeButtons;
585
586	auto activeHats = activeGamepadHats(type);
587	auto oldHats = m_activeHats;
588	m_activeHats = activeHats;
589
590	if (!QApplication::focusWidget()) {
591		return;
592	}
593
594	activeAxes.subtract(oldAxes);
595	oldAxes.subtract(m_activeAxes);
596
597	for (auto& axis : m_activeAxes) {
598		bool newlyAboveThreshold = activeAxes.contains(axis);
599		if (newlyAboveThreshold) {
600			GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, newlyAboveThreshold, type, this);
601			postPendingEvent(event->gbaKey());
602			sendGamepadEvent(event);
603			if (!event->isAccepted()) {
604				clearPendingEvent(event->gbaKey());
605			}
606		}
607	}
608	for (auto axis : oldAxes) {
609		GamepadAxisEvent* event = new GamepadAxisEvent(axis.first, axis.second, false, type, this);
610		clearPendingEvent(event->gbaKey());
611		sendGamepadEvent(event);
612	}
613
614	if (!QApplication::focusWidget()) {
615		return;
616	}
617
618	activeButtons.subtract(oldButtons);
619	oldButtons.subtract(m_activeButtons);
620
621	for (int button : activeButtons) {
622		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Down(), button, type, this);
623		postPendingEvent(event->gbaKey());
624		sendGamepadEvent(event);
625		if (!event->isAccepted()) {
626			clearPendingEvent(event->gbaKey());
627		}
628	}
629	for (int button : oldButtons) {
630		GamepadButtonEvent* event = new GamepadButtonEvent(GamepadButtonEvent::Up(), button, type, this);
631		clearPendingEvent(event->gbaKey());
632		sendGamepadEvent(event);
633	}
634
635	activeHats.subtract(oldHats);
636	oldHats.subtract(m_activeHats);
637
638	for (auto& hat : activeHats) {
639		GamepadHatEvent* event = new GamepadHatEvent(GamepadHatEvent::Down(), hat.first, hat.second, type, this);
640		postPendingEvent(event->gbaKey());
641		sendGamepadEvent(event);
642		if (!event->isAccepted()) {
643			clearPendingEvent(event->gbaKey());
644		}
645	}
646	for (auto& hat : oldHats) {
647		GamepadHatEvent* event = new GamepadHatEvent(GamepadHatEvent::Up(), hat.first, hat.second, type, this);
648		clearPendingEvent(event->gbaKey());
649		sendGamepadEvent(event);
650	}
651}
652
653void InputController::sendGamepadEvent(QEvent* event) {
654	QWidget* focusWidget = nullptr;
655	if (m_focusParent) {
656		focusWidget = m_focusParent->focusWidget();
657		if (!focusWidget) {
658			focusWidget = m_focusParent;
659		}
660	} else {
661		focusWidget = QApplication::focusWidget();
662	}
663	QApplication::postEvent(focusWidget, event, Qt::HighEventPriority);
664}
665
666void InputController::postPendingEvent(GBAKey key) {
667	m_pendingEvents.insert(key);
668}
669
670void InputController::clearPendingEvent(GBAKey key) {
671	m_pendingEvents.remove(key);
672}
673
674bool InputController::hasPendingEvent(GBAKey key) const {
675	return m_pendingEvents.contains(key);
676}
677
678void InputController::suspendScreensaver() {
679#ifdef BUILD_SDL
680#if SDL_VERSION_ATLEAST(2, 0, 0)
681	mSDLSuspendScreensaver(&s_sdlEvents);
682#endif
683#endif
684}
685
686void InputController::resumeScreensaver() {
687#ifdef BUILD_SDL
688#if SDL_VERSION_ATLEAST(2, 0, 0)
689	mSDLResumeScreensaver(&s_sdlEvents);
690#endif
691#endif
692}
693
694void InputController::setScreensaverSuspendable(bool suspendable) {
695#ifdef BUILD_SDL
696#if SDL_VERSION_ATLEAST(2, 0, 0)
697	mSDLSetScreensaverSuspendable(&s_sdlEvents, suspendable);
698#endif
699#endif
700}
701
702void InputController::stealFocus(QWidget* focus) {
703	m_focusParent = focus;
704}
705
706void InputController::releaseFocus(QWidget* focus) {
707	if (focus == m_focusParent) {
708		m_focusParent = m_topLevel;
709	}
710}
711
712void InputController::loadCamImage(const QString& path) {
713	setCamImage(QImage(path));
714}
715
716void InputController::setCamImage(const QImage& image) {
717	if (image.isNull()) {
718		return;
719	}
720	QMutexLocker locker(&m_image.mutex);
721	m_image.image = image;
722	m_image.resizedImage = QImage();
723	m_image.outOfDate = true;
724}
725
726QList<QPair<QByteArray, QString>> InputController::listCameras() const {
727	QList<QPair<QByteArray, QString>> out;
728#ifdef BUILD_QT_MULTIMEDIA
729	QList<QCameraInfo> cams = QCameraInfo::availableCameras();
730	for (const auto& cam : cams) {
731		out.append(qMakePair(cam.deviceName().toLatin1(), cam.description()));
732	}
733#endif
734	return out;
735}
736
737void InputController::increaseLuminanceLevel() {
738	setLuminanceLevel(m_luxLevel + 1);
739}
740
741void InputController::decreaseLuminanceLevel() {
742	setLuminanceLevel(m_luxLevel - 1);
743}
744
745void InputController::setLuminanceLevel(int level) {
746	int value = 0x16;
747	level = clamp(level, 0, 10);
748	if (level > 0) {
749		value += GBA_LUX_LEVELS[level - 1];
750	}
751	setLuminanceValue(value);
752}
753
754void InputController::setLuminanceValue(uint8_t value) {
755	m_luxValue = value;
756	value = std::max<int>(value - 0x16, 0);
757	m_luxLevel = 10;
758	for (int i = 0; i < 10; ++i) {
759		if (value < GBA_LUX_LEVELS[i]) {
760			m_luxLevel = i;
761			break;
762		}
763	}
764	emit luminanceValueChanged(m_luxValue);
765}
766
767void InputController::setupCam() {
768#ifdef BUILD_QT_MULTIMEDIA
769	if (!m_camera) {
770		m_camera = std::make_unique<QCamera>();
771		connect(m_camera.get(), &QCamera::statusChanged, this, &InputController::prepareCamSettings, Qt::QueuedConnection);
772	}
773	m_camera->setCaptureMode(QCamera::CaptureVideo);
774	m_camera->setViewfinder(&m_videoDumper);
775	m_camera->load();
776#endif
777}
778
779#ifdef BUILD_QT_MULTIMEDIA
780void InputController::prepareCamSettings(QCamera::Status status) {
781	if (status != QCamera::LoadedStatus || m_camera->state() == QCamera::ActiveState) {
782		return;
783	}
784#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
785	QCameraViewfinderSettings settings;
786	QSize size(1280, 720);
787	auto cameraRes = m_camera->supportedViewfinderResolutions(settings);
788	for (auto& cameraSize : cameraRes) {
789		if (cameraSize.width() < m_image.w || cameraSize.height() < m_image.h) {
790			continue;
791		}
792		if (cameraSize.width() <= size.width() && cameraSize.height() <= size.height()) {
793			size = cameraSize;
794		}
795	}
796	settings.setResolution(size);
797
798	auto cameraFormats = m_camera->supportedViewfinderPixelFormats(settings);
799	auto goodFormats = m_videoDumper.supportedPixelFormats();
800	bool goodFormatFound = false;
801	for (const auto& goodFormat : goodFormats) {
802		if (cameraFormats.contains(goodFormat)) {
803			settings.setPixelFormat(goodFormat);
804			goodFormatFound = true;
805			break;
806		}
807	}
808	if (!goodFormatFound) {
809		LOG(QT, WARN) << "Could not find a valid camera format!";
810		for (const auto& format : cameraFormats) {
811			LOG(QT, WARN) << "Camera supported format: " << QString::number(format);
812		}
813	}
814	m_camera->setViewfinderSettings(settings);
815#endif
816	m_camera->start();
817}
818#endif
819
820void InputController::teardownCam() {
821#ifdef BUILD_QT_MULTIMEDIA
822	if (m_camera) {
823		m_camera->stop();
824	}
825#endif
826}
827
828void InputController::setCamera(const QByteArray& name) {
829#ifdef BUILD_QT_MULTIMEDIA
830	bool needsRestart = false;
831	if (m_camera) {
832		needsRestart = m_camera->state() == QCamera::ActiveState;
833	}
834	m_camera = std::make_unique<QCamera>(name);
835	connect(m_camera.get(), &QCamera::statusChanged, this, &InputController::prepareCamSettings, Qt::QueuedConnection);
836	if (needsRestart) {
837		setupCam();
838	}
839#endif
840}