all repos — mgba @ 03aed12d2889544f9e88ef8da32c6037b9375362

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