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