diff --git a/app/app.pro b/app/app.pro index dc73766f..a5d57671 100644 --- a/app/app.pro +++ b/app/app.pro @@ -29,29 +29,29 @@ macx { } unix:!macx { CONFIG += link_pkgconfig - PKGCONFIG += openssl + PKGCONFIG += openssl sdl2 } SOURCES += \ main.cpp \ - mainwindow.cpp \ - nvhttp.cpp \ - nvpairingmanager.cpp \ - identitymanager.cpp \ - popupmanager.cpp \ - streamwidget.cpp + streaming/audio.c \ + streaming/input.c \ + gui/mainwindow.cpp \ + gui/popupmanager.cpp \ + http/identitymanager.cpp \ + http/nvhttp.cpp \ + http/nvpairingmanager.cpp HEADERS += \ - mainwindow.h \ - nvhttp.h \ - nvpairingmanager.h \ - identitymanager.h \ utils.h \ - popupmanager.h \ - streamwidget.h + gui/mainwindow.h \ + gui/popupmanager.h \ + http/identitymanager.h \ + http/nvhttp.h \ + http/nvpairingmanager.h FORMS += \ - mainwindow.ui + gui/mainwindow.ui RESOURCES += \ resources.qrc diff --git a/app/mainwindow.cpp b/app/gui/mainwindow.cpp similarity index 93% rename from app/mainwindow.cpp rename to app/gui/mainwindow.cpp index 8d5dde46..66484f87 100644 --- a/app/mainwindow.cpp +++ b/app/gui/mainwindow.cpp @@ -1,9 +1,9 @@ -#include "mainwindow.h" +#include "gui/mainwindow.h" #include "ui_mainwindow.h" -#include "popupmanager.h" -#include "identitymanager.h" -#include "nvpairingmanager.h" -#include "nvhttp.h" +#include "gui/popupmanager.h" +#include "http/identitymanager.h" +#include "http/nvpairingmanager.h" +#include "http/nvhttp.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), diff --git a/app/mainwindow.h b/app/gui/mainwindow.h similarity index 100% rename from app/mainwindow.h rename to app/gui/mainwindow.h diff --git a/app/mainwindow.ui b/app/gui/mainwindow.ui similarity index 100% rename from app/mainwindow.ui rename to app/gui/mainwindow.ui diff --git a/app/popupmanager.cpp b/app/gui/popupmanager.cpp similarity index 100% rename from app/popupmanager.cpp rename to app/gui/popupmanager.cpp diff --git a/app/popupmanager.h b/app/gui/popupmanager.h similarity index 100% rename from app/popupmanager.h rename to app/gui/popupmanager.h diff --git a/app/identitymanager.cpp b/app/http/identitymanager.cpp similarity index 100% rename from app/identitymanager.cpp rename to app/http/identitymanager.cpp diff --git a/app/identitymanager.h b/app/http/identitymanager.h similarity index 100% rename from app/identitymanager.h rename to app/http/identitymanager.h diff --git a/app/nvhttp.cpp b/app/http/nvhttp.cpp similarity index 100% rename from app/nvhttp.cpp rename to app/http/nvhttp.cpp diff --git a/app/nvhttp.h b/app/http/nvhttp.h similarity index 100% rename from app/nvhttp.h rename to app/http/nvhttp.h diff --git a/app/nvpairingmanager.cpp b/app/http/nvpairingmanager.cpp similarity index 100% rename from app/nvpairingmanager.cpp rename to app/http/nvpairingmanager.cpp diff --git a/app/nvpairingmanager.h b/app/http/nvpairingmanager.h similarity index 94% rename from app/nvpairingmanager.h rename to app/http/nvpairingmanager.h index a44ef27e..dbf8bba9 100644 --- a/app/nvpairingmanager.h +++ b/app/http/nvpairingmanager.h @@ -1,7 +1,7 @@ #pragma once -#include -#include +#include "http/identitymanager.h" +#include "http/nvhttp.h" #include #include diff --git a/app/main.cpp b/app/main.cpp index 503c270f..4934a392 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,4 +1,4 @@ -#include "mainwindow.h" +#include "gui/mainwindow.h" #include int main(int argc, char *argv[]) diff --git a/app/streaming/audio.c b/app/streaming/audio.c new file mode 100644 index 00000000..1c093c3e --- /dev/null +++ b/app/streaming/audio.c @@ -0,0 +1,130 @@ +#include +#include +#include + +#define MAX_CHANNELS 6 +#define SAMPLES_PER_FRAME 240 + +static SDL_AudioDeviceID g_AudioDevice; +static OpusMSDecoder* g_OpusDecoder; +static short g_OpusDecodeBuffer[MAX_CHANNELS * SAMPLES_PER_FRAME]; +static OPUS_MULTISTREAM_CONFIGURATION g_OpusConfig; + +int SdlDetermineAudioConfiguration(void) +{ + SDL_AudioSpec want, have; + SDL_AudioDeviceID dev; + + SDL_zero(want); + want.freq = 48000; + want.format = AUDIO_S16; + want.channels = 6; + want.samples = 1024; + + // Try to open for 5.1 surround sound, but allow SDL to tell us that's + // not available. + dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, SDL_AUDIO_ALLOW_CHANNELS_CHANGE); + if (dev == 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to open audio device"); + // We'll probably have issues during audio stream init, but we'll + // try anyway + return AUDIO_CONFIGURATION_STEREO; + } + + SDL_CloseAudioDevice(dev); + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Audio device has %d channels", have.channels); + + if (have.channels > 2) { + // We don't support quadraphonic or 7.1 surround, but SDL + // should be able to downmix or upmix better from 5.1 than + // from stereo, so use 5.1 in non-stereo cases. + return AUDIO_CONFIGURATION_51_SURROUND; + } + else { + return AUDIO_CONFIGURATION_STEREO; + } +} + +int SdlAudioInit(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* arContext, int arFlags) +{ + SDL_AudioSpec want, have; + int error; + + SDL_zero(want); + want.freq = opusConfig->sampleRate; + want.format = AUDIO_S16; + want.channels = opusConfig->channelCount; + want.samples = 1024; + + g_AudioDevice = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + if (g_AudioDevice == 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to open audio device: %s", + SDL_GetError()); + return -1; + } + + g_OpusDecoder = opus_multistream_decoder_create(opusConfig->sampleRate, + opusConfig->channelCount, + opusConfig->streams, + opusConfig->coupledStreams, + opusConfig->mapping, + &error); + if (g_OpusDecoder == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to create decoder: %d", + error); + SDL_CloseAudioDevice(g_AudioDevice); + g_AudioDevice = 0; + return -2; + } + + SDL_memcpy(&g_OpusConfig, opusConfig, sizeof(g_OpusConfig)); + + return 0; +} + +void SdlAudioStart(void) +{ + // Unpause the audio device + SDL_PauseAudioDevice(g_AudioDevice, 0); +} + +void SdlAudioStop(void) +{ + // Pause the audio device + SDL_PauseAudioDevice(g_AudioDevice, 1); +} + +void SdlAudioCleanup(void) +{ + SDL_CloseAudioDevice(g_AudioDevice); + g_AudioDevice = 0; + + opus_multistream_decoder_destroy(g_OpusDecoder); + g_OpusDecoder = NULL; +} + +void SdlAudioDecodeAndPlaySample(char* sampleData, int sampleLength) +{ + int samplesDecoded; + + samplesDecoded = opus_multistream_decode(g_OpusDecoder, + (unsigned char*)sampleData, + sampleLength, + g_OpusDecodeBuffer, + SAMPLES_PER_FRAME, + 0); + if (samplesDecoded > 0) { + if (SDL_QueueAudio(g_AudioDevice, + g_OpusDecodeBuffer, + sizeof(short) * samplesDecoded * g_OpusConfig.channelCount) < 0) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to queue audio sample: %s", + SDL_GetError()); + } + } +} diff --git a/app/streaming/input.c b/app/streaming/input.c new file mode 100644 index 00000000..95b927d5 --- /dev/null +++ b/app/streaming/input.c @@ -0,0 +1,484 @@ +#include +#include +#include + +#define VK_0 0x30 +#define VK_A 0x41 + +// These are real Windows VK_* codes +#ifndef VK_F1 +#define VK_F1 0x70 +#define VK_F13 0x7C +#define VK_NUMPAD1 0x61 +#endif + +typedef struct _GAMEPAD_STATE { + SDL_GameController* controller; + SDL_JoystickID jsId; + short index; + + short buttons; + short lsX, lsY; + short rsX, rsY; + unsigned char lt, rt; +} GAMEPAD_STATE, *PGAMEPAD_STATE; + +#define MAX_GAMEPADS 4 +GAMEPAD_STATE g_GamepadState[MAX_GAMEPADS]; +unsigned short g_GamepadMask; +bool g_MultiController; + +const short k_ButtonMap[] = { + A_FLAG, B_FLAG, X_FLAG, Y_FLAG, + BACK_FLAG, SPECIAL_FLAG, PLAY_FLAG, + LS_CLK_FLAG, RS_CLK_FLAG, + LB_FLAG, RB_FLAG, + UP_FLAG, DOWN_FLAG, LEFT_FLAG, RIGHT_FLAG +}; + +void SdlHandleKeyEvent(SDL_KeyboardEvent* event) +{ + short keyCode; + char modifiers; + + // Set modifier flags + modifiers = 0; + if (event->keysym.mod & KMOD_CTRL) { + modifiers |= MODIFIER_CTRL; + } + if (event->keysym.mod & KMOD_ALT) { + modifiers |= MODIFIER_ALT; + } + if (event->keysym.mod & KMOD_SHIFT) { + modifiers |= MODIFIER_SHIFT; + } + + // Ignore repeats + if (event->repeat != 0) { + return; + } + + // Set keycode + if (event->keysym.sym >= SDLK_0 && event->keysym.sym <= SDLK_9) { + keyCode = (event->keysym.sym - SDLK_0) + VK_0; + } + else if (event->keysym.sym >= SDLK_a && event->keysym.sym <= SDLK_z) { + keyCode = (event->keysym.sym - SDLK_a) + VK_A; + } + else if (event->keysym.sym >= SDLK_F1 && event->keysym.sym <= SDLK_F12) { + keyCode = (event->keysym.sym - SDLK_F1) + VK_F1; + } + else if (event->keysym.sym >= SDLK_F13 && event->keysym.sym <= SDLK_F24) { + keyCode = (event->keysym.sym - SDLK_F13) + VK_F13; + } + else if (event->keysym.sym >= SDLK_KP_1 && event->keysym.sym <= SDLK_KP_9) { + // SDL defines SDLK_KP_0 > SDLK_KP_9, so we need to handle that manually + keyCode = (event->keysym.sym - SDLK_KP_1) + VK_NUMPAD1; + } + else { + switch (event->keysym.sym) { + case SDLK_BACKSPACE: + keyCode = 0x08; + break; + case SDLK_TAB: + keyCode = 0x09; + break; + case SDLK_CLEAR: + keyCode = 0x0C; + break; + case SDLK_RETURN: + keyCode = 0x0D; + break; + case SDLK_PAUSE: + keyCode = 0x13; + break; + case SDLK_CAPSLOCK: + keyCode = 0x14; + break; + case SDLK_ESCAPE: + keyCode = 0x1B; + break; + case SDLK_SPACE: + keyCode = 0x20; + break; + case SDLK_PAGEUP: + keyCode = 0x21; + break; + case SDLK_PAGEDOWN: + keyCode = 0x22; + break; + case SDLK_END: + keyCode = 0x23; + break; + case SDLK_HOME: + keyCode = 0x24; + break; + case SDLK_LEFT: + keyCode = 0x25; + break; + case SDLK_UP: + keyCode = 0x26; + break; + case SDLK_RIGHT: + keyCode = 0x27; + break; + case SDLK_DOWN: + keyCode = 0x28; + break; + case SDLK_SELECT: + keyCode = 0x29; + break; + case SDLK_EXECUTE: + keyCode = 0x2B; + break; + case SDLK_PRINTSCREEN: + keyCode = 0x2C; + break; + case SDLK_INSERT: + keyCode = 0x2D; + break; + case SDLK_DELETE: + keyCode = 0x2E; + break; + case SDLK_HELP: + keyCode = 0x2F; + break; + case SDLK_KP_0: + // See comment above about why we only handle KP_0 here + keyCode = 0x60; + break; + case SDLK_KP_MULTIPLY: + keyCode = 0x6A; + break; + case SDLK_KP_PLUS: + keyCode = 0x6B; + break; + case SDLK_KP_COMMA: + keyCode = 0x6C; + break; + case SDLK_KP_MINUS: + keyCode = 0x6D; + break; + case SDLK_KP_DECIMAL: + keyCode = 0x6E; + break; + case SDLK_KP_DIVIDE: + keyCode = 0x6F; + break; + case SDLK_NUMLOCKCLEAR: + keyCode = 0x90; + break; + case SDLK_SCROLLLOCK: + keyCode = 0x91; + break; + case SDLK_LSHIFT: + keyCode = 0xA0; + break; + case SDLK_RSHIFT: + keyCode = 0xA1; + break; + case SDLK_LCTRL: + keyCode = 0xA2; + break; + case SDLK_RCTRL: + keyCode = 0xA3; + break; + case SDLK_LALT: + keyCode = 0xA4; + break; + case SDLK_RALT: + keyCode = 0xA5; + break; + case SDLK_AC_BACK: + keyCode = 0xA6; + break; + case SDLK_AC_FORWARD: + keyCode = 0xA7; + break; + case SDLK_AC_REFRESH: + keyCode = 0xA8; + break; + case SDLK_AC_STOP: + keyCode = 0xA9; + break; + case SDLK_AC_SEARCH: + keyCode = 0xAA; + break; + case SDLK_AC_BOOKMARKS: + keyCode = 0xAB; + break; + case SDLK_AC_HOME: + keyCode = 0xAC; + break; + case SDLK_SEMICOLON: + keyCode = 0xBA; + break; + case SDLK_PLUS: + keyCode = 0xBB; + break; + case SDLK_COMMA: + keyCode = 0xBC; + break; + case SDLK_MINUS: + keyCode = 0xBD; + break; + case SDLK_PERIOD: + keyCode = 0xBE; + break; + case SDLK_SLASH: + keyCode = 0xBF; + break; + case SDLK_BACKQUOTE: + keyCode = 0xC0; + break; + case SDLK_LEFTBRACKET: + keyCode = 0xDB; + break; + case SDLK_BACKSLASH: + keyCode = 0xDC; + break; + case SDLK_RIGHTBRACKET: + keyCode = 0xDD; + break; + case SDLK_QUOTE: + keyCode = 0xDE; + break; + default: + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Unhandled button event: %d", + event->keysym.sym); + return; + } + } + + LiSendKeyboardEvent(keyCode, + event->state == SDL_PRESSED ? + KEY_ACTION_DOWN : KEY_ACTION_UP, + modifiers); +} + +void SdlHandleMouseButtonEvent(SDL_MouseButtonEvent* event) +{ + int button; + + switch (event->button) + { + case SDL_BUTTON_LEFT: + button = BUTTON_LEFT; + break; + case SDL_BUTTON_MIDDLE: + button = BUTTON_MIDDLE; + break; + case SDL_BUTTON_RIGHT: + button = BUTTON_RIGHT; + break; + case SDL_BUTTON_X1: + case SDL_BUTTON_X2: + // Unsupported by GameStream + return; + default: + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Unhandled button event: %d", + event->button); + return; + } + + LiSendMouseButtonEvent(event->state == SDL_PRESSED ? + BUTTON_ACTION_PRESS : + BUTTON_ACTION_RELEASE, + button); +} + +void SdlHandleMouseMotionEvent(SDL_MouseMotionEvent* event) +{ + if (event->xrel != 0 || event->yrel != 0) { + LiSendMouseMoveEvent((unsigned short)event->xrel, + (unsigned short)event->yrel); + } +} + +void SdlHandleMouseWheelEvent(SDL_MouseWheelEvent* event) +{ + if (event->y != 0) { + LiSendScrollEvent((signed char)event->y); + } +} + +static PGAMEPAD_STATE FindStateForGamepad(SDL_JoystickID id) +{ + int i; + + for (i = 0; i < MAX_GAMEPADS; i++) { + if (g_GamepadState[i].jsId == id) { + SDL_assert(!g_MultiController || g_GamepadState[i].index == i); + return &g_GamepadState[i]; + } + } + + // This should only happen with > 4 gamepads + SDL_assert(SDL_NumJoysticks() > 4); + return NULL; +} + +void SendGamepadState(PGAMEPAD_STATE state) +{ + SDL_assert(g_GamepadMask == 0x1 || g_MultiController); + LiSendMultiControllerEvent(state->index, + g_GamepadMask, + state->buttons, + state->lt, + state->rt, + state->lsX, + state->lsY, + state->rsX, + state->rsY); +} + +void SdlHandleControllerAxisEvent(SDL_ControllerAxisEvent* event) +{ + PGAMEPAD_STATE state = FindStateForGamepad(event->which); + if (state == NULL) { + return; + } + + switch (event->axis) + { + case SDL_CONTROLLER_AXIS_LEFTX: + state->lsX = event->value; + break; + case SDL_CONTROLLER_AXIS_LEFTY: + state->lsY = event->value; + break; + case SDL_CONTROLLER_AXIS_RIGHTX: + state->rsX = event->value; + break; + case SDL_CONTROLLER_AXIS_RIGHTY: + state->rsY = event->value; + break; + case SDL_CONTROLLER_AXIS_TRIGGERLEFT: + state->lt = (unsigned char)((event->value + 32768UL) * 255 / 65535); + break; + case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: + state->rt = (unsigned char)((event->value + 32768UL) * 255 / 65535); + break; + default: + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Unhandled controller axis: %d", + event->axis); + return; + } + + SendGamepadState(state); +} + +void SdlHandleControllerButtonEvent(SDL_ControllerButtonEvent* event) +{ + PGAMEPAD_STATE state = FindStateForGamepad(event->which); + if (state == NULL) { + return; + } + + if (event->state == SDL_PRESSED) { + state->buttons |= k_ButtonMap[event->button]; + } + else { + state->buttons &= ~k_ButtonMap[event->button]; + } + + SendGamepadState(state); +} + +void SdlHandleControllerDeviceEvent(SDL_ControllerDeviceEvent* event) +{ + PGAMEPAD_STATE state; + + if (event->type == SDL_CONTROLLERDEVICEADDED) { + int i; + const char* name; + + for (i = 0; i < MAX_GAMEPADS; i++) { + if (g_GamepadState[i].controller == NULL) { + // Found an empty slot + break; + } + } + + if (i == MAX_GAMEPADS) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "No open gamepad slots found!"); + return; + } + + state = &g_GamepadState[i]; + if (g_MultiController) { + state->index = i; + } + else { + // Always player 1 in single controller mode + state->index = 0; + } + state->controller = SDL_GameControllerOpen(event->which); + if (state->controller == NULL) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, + "Failed to open gamepad: %s", + SDL_GetError()); + return; + } + + state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller)); + + name = SDL_GameControllerName(state->controller); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Gamepad %d (player %d) is: %s", + i, + state->index, + name != NULL ? name : ""); + + // Add this gamepad to the gamepad mask + if (g_MultiController) { + SDL_assert(!(g_GamepadMask & (1 << state->index))); + g_GamepadMask |= (1 << state->index); + } + else { + SDL_assert(g_GamepadMask == 0x1); + } + + // Send an empty event to tell the PC we've arrived + SendGamepadState(state); + } + else if (event->type == SDL_CONTROLLERDEVICEREMOVED) { + state = FindStateForGamepad(event->which); + if (state != NULL) { + SDL_GameControllerClose(state->controller); + + // Remove this from the gamepad mask in MC-mode + if (g_MultiController) { + SDL_assert(g_GamepadMask & (1 << state->index)); + g_GamepadMask &= ~(1 << state->index); + } + else { + SDL_assert(g_GamepadMask == 0x1); + } + + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, + "Gamepad %d is gone", + state->index); + + // Send a final event to let the PC know this gamepad is gone + LiSendMultiControllerEvent(state->index, g_GamepadMask, + 0, 0, 0, 0, 0, 0, 0); + + // Clear all remaining state from this slot + SDL_memset(state, 0, sizeof(*state)); + } + } +} + +void SdlInitializeGamepad(bool multiController) +{ + g_MultiController = multiController; + if (!g_MultiController) { + // Player 1 is always present in non-MC mode + g_GamepadMask = 0x01; + } +} diff --git a/app/streamwidget.cpp b/app/streamwidget.cpp deleted file mode 100644 index c20b92a8..00000000 --- a/app/streamwidget.cpp +++ /dev/null @@ -1,478 +0,0 @@ -#include "streamwidget.h" - -#include -#include -#include - -#include "Limelight.h" - -#define VK_0 0x30 -#define VK_A 0x41 - -// These are real Windows VK_* codes -#ifndef VK_F1 -#define VK_F1 0x70 -#define VK_NUMPAD0 0x60 -#endif - -StreamWidget::StreamWidget(QWidget *parent) : - QWidget(parent), - m_LastMouseX(0), - m_LastMouseY(0), - m_ScrollDeltaX(0), - m_ActiveGamepadMask(0) -{ - // Ensure we get mouse move events even if no buttons are down - setMouseTracking(true); - - // Register for QGamepadManager's signals - QGamepadManager* gpm = QGamepadManager::instance(); - connect(gpm, &QGamepadManager::gamepadConnected, this, &StreamWidget::gamepadConnected); - connect(gpm, &QGamepadManager::gamepadDisconnected, this, &StreamWidget::gamepadDisconnected); - connect(gpm, &QGamepadManager::gamepadButtonPressEvent, this, &StreamWidget::gamepadButtonPressEvent); - connect(gpm, &QGamepadManager::gamepadButtonReleaseEvent, this, &StreamWidget::gamepadButtonReleaseEvent); - connect(gpm, &QGamepadManager::gamepadAxisEvent, this, &StreamWidget::gamepadAxisEvent); - - // We won't be invoked for existing gamepads, so "connect" them now - for (int deviceId : gpm->connectedGamepads()) - { - gamepadConnected(deviceId); - } -} - -void -StreamWidget::mouseMoveEvent(QMouseEvent *event) -{ - int currentX = event->globalX(); - int currentY = event->globalY(); - - if (m_LastMouseX != 0 && m_LastMouseY != 0) - { - LiSendMouseMoveEvent(m_LastMouseX - currentX, - m_LastMouseY - currentY); - } - - m_LastMouseX = currentX; - m_LastMouseY = currentY; - - event->accept(); -} - -void -StreamWidget::handleMouseButtonEvent(QMouseEvent *event) -{ - Qt::MouseButton button = event->button(); - int buttonCode; - - Q_ASSERT(button != Qt::MouseButton::NoButton); - Q_ASSERT(event->type() == QEvent::Type::MouseButtonPress || - event->type() == QEvent::Type::MouseButtonRelease); - - // Accept all mouse button events - event->accept(); - - switch (button) - { - case Qt::MouseButton::LeftButton: - buttonCode = BUTTON_LEFT; - break; - case Qt::MouseButton::MiddleButton: - buttonCode = BUTTON_MIDDLE; - break; - case Qt::MouseButton::RightButton: - buttonCode = BUTTON_RIGHT; - break; - default: - qWarning() << "Unhandled mouse button: " << button; - return; - } - - LiSendMouseButtonEvent((event->type() == QEvent::Type::MouseButtonPress) ? - BUTTON_ACTION_PRESS : BUTTON_ACTION_RELEASE, - buttonCode); -} - -void -StreamWidget::mousePressEvent(QMouseEvent *event) -{ - handleMouseButtonEvent(event); -} - -void -StreamWidget::mouseReleaseEvent(QMouseEvent *event) -{ - handleMouseButtonEvent(event); -} - -void -StreamWidget::wheelEvent(QWheelEvent *event) -{ - QPoint scrollDelta = event->angleDelta(); - - if (!scrollDelta.isNull()) - { - m_ScrollDeltaX += scrollDelta.x(); - } - - // See if we've accumulated enough to send - if (m_ScrollDeltaX / 120 != 0) - { - // Send the scroll event - LiSendScrollEvent(m_ScrollDeltaX / 120); - - // Subtract the portion of the total scroll delta we're "consuming" - m_ScrollDeltaX -= (m_ScrollDeltaX / 120) * 120; - } - - event->accept(); -} - -void -StreamWidget::handleKeyEvent(QKeyEvent *event) -{ - // Accept all events - event->accept(); - - Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers(); - char modifierFlags = 0; - char keyAction = event->type() == QEvent::KeyPress ? - KEY_ACTION_DOWN : KEY_ACTION_UP; - - // Don't send auto-repeating events - if (keyAction == KEY_ACTION_DOWN && event->isAutoRepeat()) - { - return; - } - - if (modifiers.testFlag(Qt::KeyboardModifier::AltModifier)) - { - modifierFlags |= MODIFIER_ALT; - } - if (modifiers.testFlag(Qt::KeyboardModifier::ControlModifier)) - { - modifierFlags |= MODIFIER_CTRL; - } - if (modifiers.testFlag(Qt::KeyboardModifier::ShiftModifier)) - { - modifierFlags |= MODIFIER_SHIFT; - } - - short keyCode; - Qt::Key key = static_cast(event->key()); - - if (key >= Qt::Key::Key_0 && key <= Qt::Key::Key_9) - { - // In Qt, there's no separate key code for numpad button. - // Numpad is indicated in the modifier flags. - keyCode = (key - Qt::Key::Key_0) + - (event->modifiers().testFlag(Qt::KeyboardModifier::KeypadModifier) ? - VK_NUMPAD0 : VK_0); - } - else if (key >= Qt::Key::Key_A && key <= Qt::Key::Key_Z) - { - keyCode = (key - Qt::Key::Key_A) + VK_A; - } - else if (key >= Qt::Key::Key_F1 && key <= Qt::Key::Key_F12) - { - keyCode = (key - Qt::Key::Key_F1) + VK_F1; - } - else - { - switch (key) - { - case Qt::Key::Key_Alt: - // TODO: Tell left and right apart - keyCode = 0xA4; // 0xA5 (right) - break; - case Qt::Key::Key_Backslash: - keyCode = 0xDC; - break; - case Qt::Key::Key_CapsLock: - keyCode = 0x14; - break; - case Qt::Key::Key_Clear: - keyCode = 0x0C; - break; - case Qt::Key::Key_Comma: - keyCode = 0xBC; - break; - case Qt::Key::Key_Control: - // TODO: left and right - keyCode = 0xA2; // 0xA3 (right) - break; - case Qt::Key::Key_Backspace: - keyCode = 0x08; - break; - case Qt::Key::Key_Return: - keyCode = 0x0D; - break; - case Qt::Key::Key_Equal: - keyCode = 0xBB; - break; - case Qt::Key::Key_Escape: - keyCode = 0x1B; - break; - case Qt::Key::Key_Delete: - keyCode = 0x2E; - break; - case Qt::Key::Key_Insert: - keyCode = 0x2D; - break; - case Qt::Key::Key_BracketLeft: - keyCode = 0xDB; - break; - case Qt::Key::Key_BracketRight: - keyCode = 0xDD; - break; - case Qt::Key::Key_Minus: - keyCode = 0xBD; - break; - case Qt::Key::Key_End: - keyCode = 0x23; - break; - case Qt::Key::Key_Home: - keyCode = 0x24; - break; - case Qt::Key::Key_NumLock: - keyCode = 0x90; - break; - case Qt::Key::Key_PageDown: - keyCode = 0x22; - break; - case Qt::Key::Key_PageUp: - keyCode = 0x21; - break; - case Qt::Key::Key_Period: - keyCode = 0xBE; - break; - case Qt::Key::Key_ScrollLock: - keyCode = 0x91; - break; - case Qt::Key::Key_Semicolon: - keyCode = 0xBA; - break; - case Qt::Key::Key_Shift: - // TODO: left vs right - keyCode = 0xA0; // A1 (right) - break; - case Qt::Key::Key_Slash: - keyCode = 0xBF; - break; - case Qt::Key::Key_Space: - keyCode = 0x20; - break; - case Qt::Key::Key_SysReq: - keyCode = 0x9A; - break; - case Qt::Key::Key_Tab: - keyCode = 0x09; - break; - case Qt::Key::Key_Left: - keyCode = 0x25; - break; - case Qt::Key::Key_Right: - keyCode = 0x27; - break; - case Qt::Key::Key_Up: - keyCode = 0x26; - break; - case Qt::Key::Key_Down: - keyCode = 0x28; - break; - case Qt::Key::Key_QuoteLeft: - keyCode = 0xC0; - break; - case Qt::Key::Key_Apostrophe: - keyCode = 0xDE; - break; - case Qt::Key::Key_Pause: - keyCode = 0x13; - break; - // FIXME: Numpad keys - default: - qWarning() << "Unhandled key: " << key; - return; - } - } - - LiSendKeyboardEvent(0x80 | keyCode, keyAction, modifierFlags); -} - -void -StreamWidget::keyPressEvent(QKeyEvent *event) -{ - handleKeyEvent(event); -} - -void -StreamWidget::keyReleaseEvent(QKeyEvent *event) -{ - handleKeyEvent(event); -} - -void -StreamWidget::gamepadAxisEvent(int deviceId, QGamepadManager::GamepadAxis axis, double value) -{ - GamepadState* gamepad = m_Gamepads.value(deviceId, nullptr); - Q_ASSERT(gamepad != nullptr); - - switch (axis) - { - case QGamepadManager::GamepadAxis::AxisLeftX: - gamepad->LeftStickX = value * 0x7FFF; - break; - case QGamepadManager::GamepadAxis::AxisLeftY: - gamepad->LeftStickY = value * 0x7FFF; - break; - case QGamepadManager::GamepadAxis::AxisRightX: - gamepad->RightStickX = value * 0x7FFF; - break; - case QGamepadManager::GamepadAxis::AxisRightY: - gamepad->RightStickY = value * 0x7FFF; - break; - default: - qWarning() << "Unhandled axis: " << axis; - Q_ASSERT(false); - return; - } -} - -void -StreamWidget::handleGamepadButtonEvent(int deviceId, QGamepadManager::GamepadButton button, double value) -{ - GamepadState* gamepad = m_Gamepads.value(deviceId, nullptr); - Q_ASSERT(gamepad != nullptr); - - - // Triggers are special cases - if (button == QGamepadManager::GamepadButton::ButtonL2) - { - gamepad->LeftTrigger = value * 0xFF; - } - else if (button == QGamepadManager::GamepadButton::ButtonR2) - { - gamepad->RightTrigger = value * 0xFF; - } - else - { - short buttonFlag; - switch (button) - { - case QGamepadManager::GamepadButton::ButtonA: - buttonFlag = A_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonB: - buttonFlag = B_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonX: - buttonFlag = X_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonY: - buttonFlag = Y_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonSelect: - buttonFlag = BACK_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonStart: - buttonFlag = PLAY_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonGuide: - buttonFlag = SPECIAL_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonUp: - buttonFlag = UP_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonDown: - buttonFlag = DOWN_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonLeft: - buttonFlag = LEFT_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonRight: - buttonFlag = RIGHT_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonL3: - buttonFlag = LS_CLK_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonR3: - buttonFlag = RS_CLK_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonL1: - buttonFlag = LB_FLAG; - break; - case QGamepadManager::GamepadButton::ButtonR1: - buttonFlag = RB_FLAG; - break; - default: - qWarning() << "Unhandled button: " << button; - return; - } - - if (value != 0) - { - gamepad->ButtonFlags |= buttonFlag; - } - else - { - gamepad->ButtonFlags &= ~buttonFlag; - } - } - - LiSendMultiControllerEvent(gamepad->Index, - m_ActiveGamepadMask, - gamepad->ButtonFlags, - gamepad->LeftTrigger, - gamepad->RightTrigger, - gamepad->LeftStickX, - gamepad->LeftStickY, - gamepad->RightStickX, - gamepad->RightStickY); -} - -void -StreamWidget::gamepadButtonPressEvent(int deviceId, QGamepadManager::GamepadButton button, double value) -{ - handleGamepadButtonEvent(deviceId, button, value); -} - -void -StreamWidget::gamepadButtonReleaseEvent(int deviceId, QGamepadManager::GamepadButton button) -{ - handleGamepadButtonEvent(deviceId, button, 0); -} - -void -StreamWidget::gamepadConnected(int deviceId) -{ - Q_ASSERT(!m_Gamepads.contains(deviceId)); - - // Find an unallocated gamepad index and reserve it - GamepadState* gamepad = new GamepadState(); - for (int i = 0; i < 4; i++) - { - if ((m_ActiveGamepadMask & (1 << i)) == 0) - { - m_ActiveGamepadMask |= (1 << i); - gamepad->Index = i; - break; - } - } - - qDebug() << "New gamepad connected " << deviceId << " at index " << gamepad->Index; - - m_Gamepads.insert(deviceId, gamepad); -} - -void -StreamWidget::gamepadDisconnected(int deviceId) -{ - GamepadState* gamepad = m_Gamepads.take(deviceId); - Q_ASSERT(gamepad != nullptr); - - qDebug() << "Gamepad disconnected " << deviceId << " at index " << gamepad->Index; - - // Clear the allocated gamepad index - m_ActiveGamepadMask &= ~(1 << gamepad->Index); - - delete gamepad; -} diff --git a/app/streamwidget.h b/app/streamwidget.h deleted file mode 100644 index 9f4e4b8d..00000000 --- a/app/streamwidget.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -class GamepadState -{ -public: - GamepadState() : - ButtonFlags(0), - LeftStickX(0), - LeftStickY(0), - RightStickX(0), - RightStickY(0), - LeftTrigger(0), - RightTrigger(0) {} - - short Index; - short ButtonFlags; - short LeftStickX, LeftStickY; - short RightStickX, RightStickY; - unsigned char LeftTrigger, RightTrigger; -}; - -class StreamWidget : public QWidget -{ - Q_OBJECT -public: - explicit StreamWidget(QWidget *parent = nullptr); - -protected: - void - mouseMoveEvent(QMouseEvent *event); - - void - wheelEvent(QWheelEvent *event); - - void - mousePressEvent(QMouseEvent *event); - - void - mouseReleaseEvent(QMouseEvent *event); - - void - keyPressEvent(QKeyEvent *event); - - void - keyReleaseEvent(QKeyEvent *event); - - void - gamepadConnected(int deviceId); - - void - gamepadDisconnected(int deviceId); - - void - gamepadAxisEvent(int deviceId, QGamepadManager::GamepadAxis axis, double value); - - void - gamepadButtonPressEvent(int deviceId, QGamepadManager::GamepadButton button, double value); - - void - gamepadButtonReleaseEvent(int deviceId, QGamepadManager::GamepadButton button); - -signals: - -public slots: - -private: - void - handleMouseButtonEvent(QMouseEvent *event); - - void - handleKeyEvent(QKeyEvent *event); - - void - handleGamepadButtonEvent(int deviceId, QGamepadManager::GamepadButton button, double value); - - int m_LastMouseX, m_LastMouseY; - int m_ScrollDeltaX; - short m_ActiveGamepadMask; - QMap m_Gamepads; -}; diff --git a/app/utils.h b/app/utils.h index c5b2897c..95ec2775 100644 --- a/app/utils.h +++ b/app/utils.h @@ -2,3 +2,4 @@ #define THROW_BAD_ALLOC_IF_NULL(x) \ if ((x) == nullptr) throw new std::bad_alloc() +