all repos — mgba @ f15aacd0b6aaf33c17eae8e0ec0558bc124b3bc4

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