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