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}