Compare commits

..

No commits in common. "parsec-features" and "master" have entirely different histories.

19 changed files with 229 additions and 1514 deletions

25
.gitignore vendored
View file

@ -4,30 +4,5 @@
**/.vs/
.vscode/
build/
.qmake.cache
.qmake.stash
/Makefile
/config.log
/config.tests/
/app/Makefile
/app/Makefile.Debug
/app/Makefile.Release
/app/release/
/app/moonlight
/h264bitstream/Makefile
/h264bitstream/Makefile.Debug
/h264bitstream/Makefile.Release
/h264bitstream/release/
/h264bitstream/libh264bitstream.a
/moonlight-common-c/Makefile
/moonlight-common-c/Makefile.Debug
/moonlight-common-c/Makefile.Release
/moonlight-common-c/release/
/moonlight-common-c/libmoonlight-common-c.a
/qmdnsengine/Makefile
/qmdnsengine/Makefile.Debug
/qmdnsengine/Makefile.Release
/qmdnsengine/release/
/qmdnsengine/libqmdnsengine.a
config.tests/*/.qmake.stash
config.tests/*/Makefile

2
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "moonlight-common-c/moonlight-common-c"]
path = moonlight-common-c/moonlight-common-c
url = git.joeypayne.com:jyapayne/moonlight-common-c
url = https://github.com/moonlight-stream/moonlight-common-c.git
[submodule "qmdnsengine/qmdnsengine"]
path = qmdnsengine/qmdnsengine
url = https://github.com/cgutman/qmdnsengine.git

View file

@ -179,7 +179,6 @@ SOURCES += \
settings/streamingpreferences.cpp \
streaming/input/abstouch.cpp \
streaming/input/gamepad.cpp \
streaming/input/sdlinputsubsystems.cpp \
streaming/input/input.cpp \
streaming/input/keyboard.cpp \
streaming/input/mouse.cpp \
@ -220,7 +219,6 @@ HEADERS += \
cli/quitstream.h \
cli/startstream.h \
settings/streamingpreferences.h \
streaming/input/sdlinputsubsystems.h \
streaming/input/input.h \
streaming/session.h \
streaming/audio/renderers/renderer.h \

View file

@ -4,27 +4,10 @@
#include <QGuiApplication>
#include <QWindow>
#include "streaming/input/sdlinputsubsystems.h"
#include "settings/mappingmanager.h"
#define AXIS_NAVIGATION_REPEAT_DELAY 150
namespace {
SdlInputSubsystems::LeaseOptions guiNavSubsystemLeaseOptions()
{
SdlInputSubsystems::LeaseOptions options = {};
options.joystick = true;
options.gameController = true;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
options.haptic = false;
#endif
options.applyMappings = true;
options.flushControllerDeviceEvents = true;
return options;
}
}
SdlGamepadKeyNavigation::SdlGamepadKeyNavigation(StreamingPreferences* prefs)
: m_Prefs(prefs),
m_Enabled(false),
@ -48,13 +31,32 @@ void SdlGamepadKeyNavigation::enable()
return;
}
if (!SdlInputSubsystems::acquire("GuiGamepadNavigation", guiNavSubsystemLeaseOptions())) {
// We have to initialize and uninitialize this in enable()/disable()
// because we need to get out of the way of the Session class. If it
// doesn't get to reinitialize the GC subsystem, it won't get initial
// arrival events. Additionally, there's a race condition between
// our QML objects being destroyed and SDL being deinitialized that
// this solves too.
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to acquire SDL input subsystems for GUI gamepad navigation");
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
SDL_GetError());
return;
}
MappingManager mappingManager;
mappingManager.applyMappings();
// Drop all pending gamepad add events. SDL will generate these for us
// on first init of the GC subsystem. We can't depend on them due to
// overlapping lifetimes of SdlGamepadKeyNavigation instances, so we
// will attach ourselves.
//
// NB: We use SDL_JoystickUpdate() instead of SDL_PumpEvents() because
// the latter can do a bit more work that we want (like handling video
// events that we intentionally do not want to process yet).
SDL_JoystickUpdate();
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
// Open all currently attached game controllers
int numJoysticks = SDL_NumJoysticks();
@ -88,7 +90,7 @@ void SdlGamepadKeyNavigation::disable()
m_Gamepads.removeAt(0);
}
SdlInputSubsystems::release("GuiGamepadNavigation", guiNavSubsystemLeaseOptions());
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
}
void SdlGamepadKeyNavigation::notifyWindowFocus(bool hasFocus)

View file

@ -8,7 +8,6 @@
#include "renderers/sdl.h"
#include <Limelight.h>
#include <cmath>
#define TRY_INIT_RENDERER(renderer, opusConfig) \
{ \
@ -184,7 +183,7 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
s_ActiveSession->m_AudioSampleCount++;
// If audio is muted, don't decode or play the audio
if (s_ActiveSession->m_AudioMuted.load(std::memory_order_relaxed)) {
if (s_ActiveSession->m_AudioMuted) {
return;
}
@ -218,27 +217,6 @@ void Session::arDecodeAndPlaySample(char* sampleData, int sampleLength)
if (samplesDecoded > 0) {
SDL_assert(desiredBufferSize >= frameSize * samplesDecoded);
desiredBufferSize = frameSize * samplesDecoded;
const float volume = s_ActiveSession->getAudioVolumeScalar();
if (volume <= 0.0f) {
SDL_memset(buffer, 0, desiredBufferSize);
}
else if (volume < 1.0f) {
const int totalSamples = samplesDecoded * s_ActiveSession->m_ActiveAudioConfig.channelCount;
if (s_ActiveSession->m_AudioRenderer->getAudioBufferFormat() == IAudioRenderer::AudioFormat::Float32NE) {
float* output = (float*)buffer;
for (int i = 0; i < totalSamples; i++) {
output[i] = SDL_clamp(output[i] * volume, -1.0f, 1.0f);
}
}
else {
short* output = (short*)buffer;
for (int i = 0; i < totalSamples; i++) {
const int scaled = (int) std::lround((float)output[i] * volume);
output[i] = (short)SDL_clamp(scaled, -32768, 32767);
}
}
}
}
else {
desiredBufferSize = 0;

View file

@ -1,5 +1,4 @@
#include "input.h"
#include "streaming/session.h"
#include <Limelight.h>
#include "SDL_compat.h"
@ -8,12 +7,6 @@
#include <QtMath>
static bool isKeyboardMouseInputAllowed()
{
auto session = Session::get();
return session == nullptr || session->isMouseInputAllowed();
}
// How long the fingers must be stationary to start a right click
#define LONG_PRESS_ACTIVATION_DELAY 650
@ -29,10 +22,8 @@ static bool isKeyboardMouseInputAllowed()
Uint32 SdlInputHandler::longPressTimerCallback(Uint32, void*)
{
// Raise the left click and start a right click
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
return 0;
}
@ -119,7 +110,7 @@ void SdlInputHandler::handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
}
// Try to send it as a native pen/touch event, otherwise fall back to our touch emulation
if (isKeyboardMouseInputAllowed() && (LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS)) {
if (LiGetHostFeatureFlags() & LI_FF_PEN_TOUCH_EVENTS) {
#if SDL_VERSION_ATLEAST(2, 0, 22)
bool isPen = false;
@ -206,9 +197,7 @@ void SdlInputHandler::emulateAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
short y = qMin(qMax((int)(event->y * windowHeight), dst.y), dst.y + dst.h);
// Update the cursor position relative to the video region
if (isKeyboardMouseInputAllowed()) {
LiSendMousePositionEvent(x - dst.x, y - dst.y, dst.w, dst.h);
}
LiSendMousePositionEvent(x - dst.x, y - dst.y, dst.w, dst.h);
}
if (event->type == SDL_FINGERDOWN) {
@ -221,9 +210,7 @@ void SdlInputHandler::emulateAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
nullptr);
// Left button down on finger down
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
else if (event->type == SDL_FINGERUP) {
m_LastTouchUpEvent = *event;
@ -233,13 +220,9 @@ void SdlInputHandler::emulateAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
m_LongPressTimer = 0;
// Left button up on finger up
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
// Raise right button too in case we triggered a long press gesture
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
}
}

View file

@ -2,33 +2,16 @@
#include <Limelight.h>
#include "SDL_compat.h"
#include "streaming/input/sdlinputsubsystems.h"
#include "settings/mappingmanager.h"
#include <QtMath>
static bool isGamepadInputAllowed()
{
auto session = Session::get();
return session == nullptr || session->isGamepadInputAllowed();
}
static bool isMouseInputAllowed()
{
auto session = Session::get();
return session == nullptr || session->isMouseInputAllowed();
}
// How long the Start button must be pressed to toggle mouse emulation
#define MOUSE_EMULATION_LONG_PRESS_TIME 750
// How long between polling the gamepad to send virtual mouse input
#define MOUSE_EMULATION_POLLING_INTERVAL 50
#define HOTPLUG_REENUMERATION_INTERVAL_MS 2000
#define HOTPLUG_REENUMERATION_MAX_INTERVAL_MS 8000
#define HOTPLUG_REENUMERATION_MIN_ZERO_POLLS 4
#define HOTPLUG_REENUMERATION_MIN_LAST_SEEN_MS 500
// Determines how fast the mouse will move each interval
#define MOUSE_EMULATION_MOTION_MULTIPLIER 4
@ -57,7 +40,7 @@ SdlInputHandler::findStateForGamepad(SDL_JoystickID id)
int i;
for (i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].controller != nullptr && m_GamepadState[i].jsId == id) {
if (m_GamepadState[i].jsId == id) {
SDL_assert(!m_MultiController || m_GamepadState[i].index == i);
return &m_GamepadState[i];
}
@ -68,191 +51,6 @@ SdlInputHandler::findStateForGamepad(SDL_JoystickID id)
return nullptr;
}
GamepadState*
SdlInputHandler::ensureStateForGamepad(SDL_JoystickID id)
{
GamepadState* state = findStateForGamepad(id);
if (state != nullptr) {
return state;
}
cleanupDetachedGamepads();
const int joystickCount = SDL_NumJoysticks();
for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) {
if (!SDL_IsGameController(deviceIndex)) {
continue;
}
if (SDL_JoystickGetDeviceInstanceID(deviceIndex) != id) {
continue;
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Recovering gamepad state for missing add event (instance ID: %d)",
id);
SDL_ControllerDeviceEvent controllerEvent = {};
controllerEvent.type = SDL_CONTROLLERDEVICEADDED;
controllerEvent.which = deviceIndex;
handleControllerDeviceEvent(&controllerEvent);
break;
}
return findStateForGamepad(id);
}
void SdlInputHandler::pollForMissingGamepads()
{
static uint32_t s_LastForcedReenumerationTick = 0;
static uint32_t s_ReenumerationIntervalMs = HOTPLUG_REENUMERATION_INTERVAL_MS;
static uint32_t s_ReenumerationAttempts = 0;
static uint32_t s_ConsecutiveZeroPolls = 0;
SDL_JoystickUpdate();
SDL_GameControllerUpdate();
uint32_t now = SDL_GetTicks();
auto recoverUntrackedGamepads = [this](int joystickCount) {
for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) {
if (!SDL_IsGameController(deviceIndex)) {
continue;
}
SDL_JoystickID jsId = SDL_JoystickGetDeviceInstanceID(deviceIndex);
if (jsId < 0 || findStateForGamepad(jsId) != nullptr) {
continue;
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Polling recovered missing gamepad add event (device index: %d, instance ID: %d)",
deviceIndex,
jsId);
SDL_ControllerDeviceEvent controllerEvent = {};
controllerEvent.type = SDL_CONTROLLERDEVICEADDED;
controllerEvent.which = deviceIndex;
handleControllerDeviceEvent(&controllerEvent);
}
};
int joystickCount = SDL_NumJoysticks();
if (joystickCount != m_LastJoystickCount) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected joystick count change: %d -> %d",
m_LastJoystickCount,
joystickCount);
m_LastJoystickCount = joystickCount;
}
if (joystickCount > 0) {
m_LastNonZeroJoystickTick = now;
s_ConsecutiveZeroPolls = 0;
s_ReenumerationAttempts = 0;
s_ReenumerationIntervalMs = HOTPLUG_REENUMERATION_INTERVAL_MS;
}
else {
s_ConsecutiveZeroPolls++;
}
for (int i = 0; i < MAX_GAMEPADS; i++) {
GamepadState* state = &m_GamepadState[i];
if (state->controller == nullptr) {
continue;
}
bool presentInDeviceList = false;
for (int deviceIndex = 0; deviceIndex < joystickCount; deviceIndex++) {
if (SDL_JoystickGetDeviceInstanceID(deviceIndex) == state->jsId) {
presentInDeviceList = true;
break;
}
}
if (presentInDeviceList) {
continue;
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Polling detected disconnected gamepad instance %d in slot %d; cleaning up",
state->jsId,
i);
SDL_ControllerDeviceEvent controllerEvent = {};
controllerEvent.type = SDL_CONTROLLERDEVICEREMOVED;
controllerEvent.which = state->jsId;
handleControllerDeviceEvent(&controllerEvent);
}
recoverUntrackedGamepads(joystickCount);
bool shouldAttemptReenumeration =
joystickCount == 0 &&
m_LastNonZeroJoystickTick != 0 &&
s_ConsecutiveZeroPolls >= HOTPLUG_REENUMERATION_MIN_ZERO_POLLS &&
SDL_TICKS_PASSED(now, m_LastNonZeroJoystickTick + HOTPLUG_REENUMERATION_MIN_LAST_SEEN_MS);
if (shouldAttemptReenumeration) {
if (SDL_TICKS_PASSED(now, s_LastForcedReenumerationTick + s_ReenumerationIntervalMs)) {
s_LastForcedReenumerationTick = now;
s_ReenumerationAttempts++;
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"No joysticks visible after reconciliation; forcing last-resort SDL re-enumeration (attempt %u, interval %u ms)",
s_ReenumerationAttempts,
s_ReenumerationIntervalMs);
if (SdlInputSubsystems::reenumerateGamepadSubsystems("StreamInputHotplug")) {
joystickCount = SDL_NumJoysticks();
if (joystickCount != m_LastJoystickCount) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected joystick count change after re-enumeration: %d -> %d",
m_LastJoystickCount,
joystickCount);
m_LastJoystickCount = joystickCount;
}
recoverUntrackedGamepads(joystickCount);
if (joystickCount > 0) {
m_LastNonZeroJoystickTick = now;
s_ConsecutiveZeroPolls = 0;
s_ReenumerationAttempts = 0;
s_ReenumerationIntervalMs = HOTPLUG_REENUMERATION_INTERVAL_MS;
}
else {
s_ReenumerationIntervalMs = qMin(s_ReenumerationIntervalMs * 2,
static_cast<uint32_t>(HOTPLUG_REENUMERATION_MAX_INTERVAL_MS));
}
}
else {
s_ReenumerationIntervalMs = qMin(s_ReenumerationIntervalMs * 2,
static_cast<uint32_t>(HOTPLUG_REENUMERATION_MAX_INTERVAL_MS));
}
}
}
}
void SdlInputHandler::cleanupDetachedGamepads()
{
for (int i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].controller == nullptr ||
SDL_GameControllerGetAttached(m_GamepadState[i].controller)) {
continue;
}
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Found detached gamepad in slot %d without removal event; cleaning up",
i);
SDL_ControllerDeviceEvent controllerEvent = {};
controllerEvent.type = SDL_CONTROLLERDEVICEREMOVED;
controllerEvent.which = m_GamepadState[i].jsId;
handleControllerDeviceEvent(&controllerEvent);
}
}
void SdlInputHandler::sendGamepadState(GamepadState* state)
{
SDL_assert(m_GamepadMask == 0x1 || m_MultiController);
@ -303,17 +101,15 @@ void SdlInputHandler::sendGamepadState(GamepadState* state)
}
}
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index,
m_GamepadMask,
buttons,
lt,
rt,
lsX,
lsY,
rsX,
rsY);
}
LiSendMultiControllerEvent(state->index,
m_GamepadMask,
buttons,
lt,
rt,
lsX,
lsY,
rsX,
rsY);
}
void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level)
@ -354,9 +150,7 @@ void SdlInputHandler::sendGamepadBatteryState(GamepadState* state, SDL_JoystickP
return;
}
if (isGamepadInputAllowed()) {
LiSendControllerBatteryEvent(state->index, batteryState, batteryPercentage);
}
LiSendControllerBatteryEvent(state->index, batteryState, batteryPercentage);
}
Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param)
@ -387,7 +181,7 @@ Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param
deltaX = qAbs(deltaX) > MOUSE_EMULATION_DEADZONE ? deltaX - MOUSE_EMULATION_DEADZONE : 0;
deltaY = qAbs(deltaY) > MOUSE_EMULATION_DEADZONE ? deltaY - MOUSE_EMULATION_DEADZONE : 0;
if ((deltaX != 0 || deltaY != 0) && isMouseInputAllowed()) {
if (deltaX != 0 || deltaY != 0) {
LiSendMouseMoveEvent((short)deltaX, (short)deltaY);
}
@ -397,7 +191,7 @@ Uint32 SdlInputHandler::mouseEmulationTimerCallback(Uint32 interval, void *param
void SdlInputHandler::handleControllerAxisEvent(SDL_ControllerAxisEvent* event)
{
SDL_JoystickID gameControllerId = event->which;
GamepadState* state = ensureStateForGamepad(gameControllerId);
GamepadState* state = findStateForGamepad(gameControllerId);
if (state == NULL) {
return;
}
@ -467,7 +261,7 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
return;
}
GamepadState* state = ensureStateForGamepad(event->which);
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
}
@ -497,49 +291,31 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
}
else if (state->mouseEmulationTimer != 0) {
if (event->button == SDL_CONTROLLER_BUTTON_A) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_B) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_X) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_MIDDLE);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_MIDDLE);
}
else if (event->button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X1);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X2);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_X2);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_UP) {
if (isMouseInputAllowed()) {
LiSendScrollEvent(1);
}
LiSendScrollEvent(1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_DOWN) {
if (isMouseInputAllowed()) {
LiSendScrollEvent(-1);
}
LiSendScrollEvent(-1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
if (isMouseInputAllowed()) {
LiSendHScrollEvent(1);
}
LiSendHScrollEvent(1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_DPAD_LEFT) {
if (isMouseInputAllowed()) {
LiSendHScrollEvent(-1);
}
LiSendHScrollEvent(-1);
}
}
}
@ -570,29 +346,19 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
}
else if (state->mouseEmulationTimer != 0) {
if (event->button == SDL_CONTROLLER_BUTTON_A) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_B) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
}
else if (event->button == SDL_CONTROLLER_BUTTON_X) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_MIDDLE);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_MIDDLE);
}
else if (event->button == SDL_CONTROLLER_BUTTON_LEFTSHOULDER) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X1);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X1);
}
else if (event->button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) {
if (isMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X2);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X2);
}
}
}
@ -609,10 +375,8 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
SDL_PushEvent(&event);
// Clear buttons down on this gamepad
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
return;
}
@ -626,67 +390,8 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
// Clear buttons down on this gamepad
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
return;
}
if (state->buttons == (BACK_FLAG | LB_FLAG | RB_FLAG | B_FLAG)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected control panel toggle gamepad combo");
Session::get()->toggleControlPanelVisibility();
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
return;
}
if (state->buttons == (BACK_FLAG | LB_FLAG | RB_FLAG | Y_FLAG)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected keyboard input toggle gamepad combo");
Session::get()->toggleKeyboardInputAllowed();
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
return;
}
if (state->buttons == (BACK_FLAG | LB_FLAG | RB_FLAG | UP_FLAG)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse input toggle gamepad combo");
Session::get()->toggleMouseInputAllowed();
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
return;
}
if (state->buttons == (BACK_FLAG | LB_FLAG | RB_FLAG | A_FLAG)) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected gamepad input toggle gamepad combo");
bool gamepadWasEnabled = isGamepadInputAllowed();
if (gamepadWasEnabled) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
Session::get()->toggleGamepadInputAllowed();
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
return;
}
@ -700,7 +405,7 @@ void SdlInputHandler::handleControllerButtonEvent(SDL_ControllerButtonEvent* eve
void SdlInputHandler::handleControllerSensorEvent(SDL_ControllerSensorEvent* event)
{
GamepadState* state = ensureStateForGamepad(event->which);
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
}
@ -713,9 +418,7 @@ void SdlInputHandler::handleControllerSensorEvent(SDL_ControllerSensorEvent* eve
memcpy(state->lastAccelEventData, event->data, sizeof(event->data));
state->lastAccelEventTime = event->timestamp;
if (isGamepadInputAllowed()) {
LiSendControllerMotionEvent((uint8_t)state->index, LI_MOTION_TYPE_ACCEL, event->data[0], event->data[1], event->data[2]);
}
LiSendControllerMotionEvent((uint8_t)state->index, LI_MOTION_TYPE_ACCEL, event->data[0], event->data[1], event->data[2]);
}
break;
case SDL_SENSOR_GYRO:
@ -726,12 +429,10 @@ void SdlInputHandler::handleControllerSensorEvent(SDL_ControllerSensorEvent* eve
state->lastGyroEventTime = event->timestamp;
// Convert rad/s to deg/s
if (isGamepadInputAllowed()) {
LiSendControllerMotionEvent((uint8_t)state->index, LI_MOTION_TYPE_GYRO,
event->data[0] * 57.2957795f,
event->data[1] * 57.2957795f,
event->data[2] * 57.2957795f);
}
LiSendControllerMotionEvent((uint8_t)state->index, LI_MOTION_TYPE_GYRO,
event->data[0] * 57.2957795f,
event->data[1] * 57.2957795f,
event->data[2] * 57.2957795f);
}
break;
}
@ -739,7 +440,7 @@ void SdlInputHandler::handleControllerSensorEvent(SDL_ControllerSensorEvent* eve
void SdlInputHandler::handleControllerTouchpadEvent(SDL_ControllerTouchpadEvent* event)
{
GamepadState* state = ensureStateForGamepad(event->which);
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
}
@ -759,9 +460,7 @@ void SdlInputHandler::handleControllerTouchpadEvent(SDL_ControllerTouchpadEvent*
return;
}
if (isGamepadInputAllowed()) {
LiSendControllerTouchEvent((uint8_t)state->index, eventType, event->finger, event->x, event->y, event->pressure);
}
LiSendControllerTouchEvent((uint8_t)state->index, eventType, event->finger, event->x, event->y, event->pressure);
}
#endif
@ -770,7 +469,7 @@ void SdlInputHandler::handleControllerTouchpadEvent(SDL_ControllerTouchpadEvent*
void SdlInputHandler::handleJoystickBatteryEvent(SDL_JoyBatteryEvent* event)
{
GamepadState* state = ensureStateForGamepad(event->which);
GamepadState* state = findStateForGamepad(event->which);
if (state == NULL) {
return;
}
@ -785,8 +484,6 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
GamepadState* state;
if (event->type == SDL_CONTROLLERDEVICEADDED) {
cleanupDetachedGamepads();
int i;
const char* name;
SDL_GameController* controller;
@ -802,17 +499,15 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
return;
}
SDL_JoystickID jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller));
// SDL_CONTROLLERDEVICEADDED can be reported multiple times for the same
// gamepad in rare cases, because SDL doesn't fixup the device index in
// the SDL_CONTROLLERDEVICEADDED event if an unopened gamepad disappears
// before we've processed the add event.
for (int i = 0; i < MAX_GAMEPADS; i++) {
if (m_GamepadState[i].controller != nullptr && m_GamepadState[i].jsId == jsId) {
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Received duplicate add event for joystick instance ID: %d",
jsId);
if (m_GamepadState[i].controller == controller) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Received duplicate add event for controller index: %d",
event->which);
SDL_GameControllerClose(controller);
return;
}
@ -868,7 +563,7 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
}
state->controller = controller;
state->jsId = jsId;
state->jsId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(state->controller));
hapticCaps = 0;
#if SDL_VERSION_ATLEAST(2, 0, 18)
@ -1013,9 +708,7 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
#endif
type == LI_CTYPE_PS;
if (isGamepadInputAllowed()) {
LiSendControllerArrivalEvent(state->index, m_GamepadMask, type, supportedButtonFlags, capabilities);
}
LiSendControllerArrivalEvent(state->index, m_GamepadMask, type, supportedButtonFlags, capabilities);
#else
// Send an empty event to tell the PC we've arrived
@ -1057,10 +750,8 @@ void SdlInputHandler::handleControllerDeviceEvent(SDL_ControllerDeviceEvent* eve
state->index);
// Send a final event to let the PC know this gamepad is gone
if (isGamepadInputAllowed()) {
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
}
LiSendMultiControllerEvent(state->index, m_GamepadMask,
0, 0, 0, 0, 0, 0, 0);
// Clear all remaining state from this slot
SDL_memset(state, 0, sizeof(*state));
@ -1072,49 +763,29 @@ void SdlInputHandler::handleJoystickArrivalEvent(SDL_JoyDeviceEvent* event)
{
SDL_assert(event->type == SDL_JOYDEVICEADDED);
if (SDL_IsGameController(event->which)) {
SDL_ControllerDeviceEvent controllerEvent = {};
controllerEvent.type = SDL_CONTROLLERDEVICEADDED;
controllerEvent.which = event->which;
handleControllerDeviceEvent(&controllerEvent);
return;
}
char guidStr[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(event->which),
guidStr, sizeof(guidStr));
const char* name = SDL_JoystickNameForIndex(event->which);
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Joystick discovered with no mapping: %s %s",
name ? name : "<UNKNOWN>",
guidStr);
SDL_Joystick* joy = SDL_JoystickOpen(event->which);
if (joy != nullptr) {
if (!SDL_IsGameController(event->which)) {
char guidStr[33];
SDL_JoystickGetGUIDString(SDL_JoystickGetDeviceGUID(event->which),
guidStr, sizeof(guidStr));
const char* name = SDL_JoystickNameForIndex(event->which);
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Number of axes: %d | Number of buttons: %d | Number of hats: %d",
SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy),
SDL_JoystickNumHats(joy));
SDL_JoystickClose(joy);
"Joystick discovered with no mapping: %s %s",
name ? name : "<UNKNOWN>",
guidStr);
SDL_Joystick* joy = SDL_JoystickOpen(event->which);
if (joy != nullptr) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Number of axes: %d | Number of buttons: %d | Number of hats: %d",
SDL_JoystickNumAxes(joy), SDL_JoystickNumButtons(joy),
SDL_JoystickNumHats(joy));
SDL_JoystickClose(joy);
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Unable to open joystick for query: %s",
SDL_GetError());
}
}
else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Unable to open joystick for query: %s",
SDL_GetError());
}
}
void SdlInputHandler::handleJoystickRemovalEvent(SDL_JoyDeviceEvent* event)
{
SDL_assert(event->type == SDL_JOYDEVICEREMOVED);
if (findStateForGamepad(event->which) == nullptr) {
return;
}
SDL_ControllerDeviceEvent controllerEvent = {};
controllerEvent.type = SDL_CONTROLLERDEVICEREMOVED;
controllerEvent.which = event->which;
handleControllerDeviceEvent(&controllerEvent);
}
void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short lowFreqMotor, unsigned short highFreqMotor)
@ -1249,21 +920,15 @@ QString SdlInputHandler::getUnmappedGamepads()
{
QString ret;
SdlInputSubsystems::LeaseOptions options = {};
options.joystick = true;
options.gameController = true;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
options.haptic = false;
#endif
options.applyMappings = true;
options.flushControllerDeviceEvents = false;
if (!SdlInputSubsystems::acquire("UnmappedGamepadProbe", options)) {
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to acquire SDL input subsystems for unmapped gamepad probe");
return ret;
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
SDL_GetError());
}
MappingManager mappingManager;
mappingManager.applyMappings();
int numJoysticks = SDL_NumJoysticks();
for (int i = 0; i < numJoysticks; i++) {
if (!SDL_IsGameController(i)) {
@ -1305,7 +970,7 @@ QString SdlInputHandler::getUnmappedGamepads()
}
}
SdlInputSubsystems::release("UnmappedGamepadProbe", options);
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
// Flush stale events so they aren't processed by the main session event loop
SDL_FlushEvents(SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED);

View file

@ -1,7 +1,7 @@
#include <Limelight.h>
#include "SDL_compat.h"
#include "streaming/session.h"
#include "streaming/input/sdlinputsubsystems.h"
#include "settings/mappingmanager.h"
#include "path.h"
#include "utils.h"
@ -9,23 +9,6 @@
#include <QDir>
#include <QGuiApplication>
namespace {
SdlInputSubsystems::LeaseOptions streamInputSubsystemLeaseOptions()
{
SdlInputSubsystems::LeaseOptions options = {};
options.joystick = true;
options.gameController = true;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
options.haptic = true;
#endif
options.applyMappings = true;
options.flushControllerDeviceEvents = true;
return options;
}
}
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, int streamHeight)
: m_MultiController(prefs.multiController),
m_GamepadMouse(prefs.gamepadMouse),
@ -36,8 +19,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
m_PointerRegionLockActive(false),
m_PointerRegionLockToggledByUser(false),
m_LastJoystickCount(-1),
m_LastNonZeroJoystickTick(0),
m_FakeCaptureActive(false),
m_CaptureSystemKeysMode(prefs.captureSysKeysMode),
m_MouseCursorCapturedVisibilityState(SDL_DISABLE),
@ -105,11 +86,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].scanCode = SDL_SCANCODE_S;
m_SpecialKeyCombos[KeyComboToggleStatsOverlay].enabled = true;
m_SpecialKeyCombos[KeyComboToggleControlPanel].keyCombo = KeyComboToggleControlPanel;
m_SpecialKeyCombos[KeyComboToggleControlPanel].keyCode = SDLK_p;
m_SpecialKeyCombos[KeyComboToggleControlPanel].scanCode = SDL_SCANCODE_P;
m_SpecialKeyCombos[KeyComboToggleControlPanel].enabled = true;
m_SpecialKeyCombos[KeyComboToggleMouseMode].keyCombo = KeyComboToggleMouseMode;
m_SpecialKeyCombos[KeyComboToggleMouseMode].keyCode = SDLK_m;
m_SpecialKeyCombos[KeyComboToggleMouseMode].scanCode = SDL_SCANCODE_M;
@ -130,36 +106,6 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
m_SpecialKeyCombos[KeyComboPasteText].scanCode = SDL_SCANCODE_V;
m_SpecialKeyCombos[KeyComboPasteText].enabled = true;
m_SpecialKeyCombos[KeyComboToggleKeyboardInput].keyCombo = KeyComboToggleKeyboardInput;
m_SpecialKeyCombos[KeyComboToggleKeyboardInput].keyCode = SDLK_k;
m_SpecialKeyCombos[KeyComboToggleKeyboardInput].scanCode = SDL_SCANCODE_K;
m_SpecialKeyCombos[KeyComboToggleKeyboardInput].enabled = true;
m_SpecialKeyCombos[KeyComboToggleMouseInput].keyCombo = KeyComboToggleMouseInput;
m_SpecialKeyCombos[KeyComboToggleMouseInput].keyCode = SDLK_o;
m_SpecialKeyCombos[KeyComboToggleMouseInput].scanCode = SDL_SCANCODE_O;
m_SpecialKeyCombos[KeyComboToggleMouseInput].enabled = true;
m_SpecialKeyCombos[KeyComboToggleGamepadInput].keyCombo = KeyComboToggleGamepadInput;
m_SpecialKeyCombos[KeyComboToggleGamepadInput].keyCode = SDLK_g;
m_SpecialKeyCombos[KeyComboToggleGamepadInput].scanCode = SDL_SCANCODE_G;
m_SpecialKeyCombos[KeyComboToggleGamepadInput].enabled = true;
m_SpecialKeyCombos[KeyComboVolumeUp].keyCombo = KeyComboVolumeUp;
m_SpecialKeyCombos[KeyComboVolumeUp].keyCode = SDLK_u;
m_SpecialKeyCombos[KeyComboVolumeUp].scanCode = SDL_SCANCODE_U;
m_SpecialKeyCombos[KeyComboVolumeUp].enabled = true;
m_SpecialKeyCombos[KeyComboVolumeDown].keyCombo = KeyComboVolumeDown;
m_SpecialKeyCombos[KeyComboVolumeDown].keyCode = SDLK_j;
m_SpecialKeyCombos[KeyComboVolumeDown].scanCode = SDL_SCANCODE_J;
m_SpecialKeyCombos[KeyComboVolumeDown].enabled = true;
m_SpecialKeyCombos[KeyComboToggleMute].keyCombo = KeyComboToggleMute;
m_SpecialKeyCombos[KeyComboToggleMute].keyCode = SDLK_n;
m_SpecialKeyCombos[KeyComboToggleMute].scanCode = SDL_SCANCODE_N;
m_SpecialKeyCombos[KeyComboToggleMute].enabled = true;
m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].keyCombo = KeyComboTogglePointerRegionLock;
m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].keyCode = SDLK_l;
m_SpecialKeyCombos[KeyComboTogglePointerRegionLock].scanCode = SDL_SCANCODE_L;
@ -201,11 +147,45 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, int streamWidth, i
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, streamIgnoreDevices.toUtf8());
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, streamIgnoreDevicesExcept.toUtf8());
if (!SdlInputSubsystems::acquire("StreamInput", streamInputSubsystemLeaseOptions())) {
// We must initialize joystick explicitly before gamecontroller in order
// to ensure we receive gamecontroller attach events for gamepads where
// SDL doesn't have a built-in mapping. By starting joystick first, we
// can allow mapping manager to update the mappings before GC attach
// events are generated.
SDL_assert(!SDL_WasInit(SDL_INIT_JOYSTICK));
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to acquire SDL input subsystems for streaming input");
"SDL_InitSubSystem(SDL_INIT_JOYSTICK) failed: %s",
SDL_GetError());
}
MappingManager mappingManager;
mappingManager.applyMappings();
// Flush gamepad arrival and departure events which may be queued before
// starting the gamecontroller subsystem again. This prevents us from
// receiving duplicate arrival and departure events for the same gamepad.
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
SDL_FlushEvent(SDL_CONTROLLERDEVICEREMOVED);
// We need to reinit this each time, since you only get
// an initial set of gamepad arrival events once per init.
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
SDL_GetError());
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"SDL_InitSubSystem(SDL_INIT_HAPTIC) failed: %s",
SDL_GetError());
}
#endif
// Initialize the gamepad mask with currently attached gamepads to avoid
// causing gamepads to unexpectedly disappear and reappear on the host
// during stream startup as we detect currently attached gamepads one at a time.
@ -239,7 +219,16 @@ SdlInputHandler::~SdlInputHandler()
SDL_RemoveTimer(m_RightButtonReleaseTimer);
SDL_RemoveTimer(m_DragTimer);
SdlInputSubsystems::release("StreamInput", streamInputSubsystemLeaseOptions());
#if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
#endif
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
SDL_assert(!SDL_WasInit(SDL_INIT_JOYSTICK));
// Return background event handling to off
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
@ -279,50 +268,6 @@ void SdlInputHandler::raiseAllKeys()
m_KeysDown.clear();
}
void SdlInputHandler::raiseAllMouseButtons()
{
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_MIDDLE);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X1);
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_X2);
}
void SdlInputHandler::raiseAllGamepadInputs()
{
uint16_t raisedIndexMask = 0;
for (auto &state : m_GamepadState) {
if (state.controller == nullptr) {
continue;
}
state.buttons = 0;
state.lt = 0;
state.rt = 0;
state.lsX = 0;
state.lsY = 0;
state.rsX = 0;
state.rsY = 0;
state.emulatedClickpadButtonDown = false;
if ((raisedIndexMask & (1 << state.index)) != 0) {
continue;
}
LiSendMultiControllerEvent(state.index,
m_GamepadMask,
0,
0,
0,
0,
0,
0,
0);
raisedIndexMask |= (1 << state.index);
}
}
void SdlInputHandler::notifyMouseLeave()
{
// SDL on Windows doesn't send the mouse button up until the mouse re-enters the window

View file

@ -116,10 +116,6 @@ public:
void handleJoystickArrivalEvent(SDL_JoyDeviceEvent* event);
void handleJoystickRemovalEvent(SDL_JoyDeviceEvent* event);
void pollForMissingGamepads();
void sendText(QString& string);
void rumble(uint16_t controllerNumber, uint16_t lowFreqMotor, uint16_t highFreqMotor);
@ -138,10 +134,6 @@ public:
void raiseAllKeys();
void raiseAllMouseButtons();
void raiseAllGamepadInputs();
void notifyMouseLeave();
void notifyFocusLost();
@ -169,17 +161,10 @@ private:
KeyComboUngrabInput,
KeyComboToggleFullScreen,
KeyComboToggleStatsOverlay,
KeyComboToggleControlPanel,
KeyComboToggleMouseMode,
KeyComboToggleCursorHide,
KeyComboToggleMinimize,
KeyComboPasteText,
KeyComboToggleKeyboardInput,
KeyComboToggleMouseInput,
KeyComboToggleGamepadInput,
KeyComboVolumeUp,
KeyComboVolumeDown,
KeyComboToggleMute,
KeyComboTogglePointerRegionLock,
KeyComboQuitAndExit,
KeyComboMax
@ -188,11 +173,6 @@ private:
GamepadState*
findStateForGamepad(SDL_JoystickID id);
GamepadState*
ensureStateForGamepad(SDL_JoystickID id);
void cleanupDetachedGamepads();
void sendGamepadState(GamepadState* state);
void sendGamepadBatteryState(GamepadState* state, SDL_JoystickPowerLevel level);
@ -235,8 +215,6 @@ private:
bool m_PointerRegionLockToggledByUser;
int m_GamepadMask;
int m_LastJoystickCount;
uint32_t m_LastNonZeroJoystickTick;
GamepadState m_GamepadState[MAX_GAMEPADS];
QSet<short> m_KeysDown;
bool m_FakeCaptureActive;

View file

@ -13,12 +13,6 @@
#define VK_NUMPAD0 0x60
#endif
static bool isKeyboardMouseInputAllowed()
{
auto session = Session::get();
return session == nullptr || session->isKeyboardInputAllowed();
}
void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
{
switch (combo) {
@ -64,14 +58,6 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
!Session::get()->getOverlayManager().isOverlayEnabled(Overlay::OverlayDebug));
break;
case KeyComboToggleControlPanel:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected control panel toggle combo");
if (auto session = Session::get(); session != nullptr) {
session->toggleControlPanelVisibility();
}
break;
case KeyComboToggleMouseMode:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse mode toggle combo");
@ -128,9 +114,7 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
}
// Send this text to the PC
if (isKeyboardMouseInputAllowed()) {
LiSendUtf8TextEvent(text, (unsigned int)strlen(text));
}
LiSendUtf8TextEvent(text, (unsigned int)strlen(text));
// SDL_GetClipboardText() allocates, so we must free
SDL_free((void*)text);
@ -142,54 +126,6 @@ void SdlInputHandler::performSpecialKeyCombo(KeyCombo combo)
break;
}
case KeyComboToggleKeyboardInput:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected keyboard input toggle combo");
if (auto session = Session::get(); session != nullptr) {
session->toggleKeyboardInputAllowed();
}
break;
case KeyComboToggleMouseInput:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected mouse input toggle combo");
if (auto session = Session::get(); session != nullptr) {
session->toggleMouseInputAllowed();
}
break;
case KeyComboToggleGamepadInput:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected gamepad input toggle combo");
if (auto session = Session::get(); session != nullptr) {
session->toggleGamepadInputAllowed();
}
break;
case KeyComboVolumeUp:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected volume up combo");
if (auto session = Session::get(); session != nullptr) {
session->adjustAudioVolume(0.05f);
}
break;
case KeyComboVolumeDown:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected volume down combo");
if (auto session = Session::get(); session != nullptr) {
session->adjustAudioVolume(-0.05f);
}
break;
case KeyComboToggleMute:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected audio mute toggle combo");
if (auto session = Session::get(); session != nullptr) {
session->toggleAudioMute();
}
break;
case KeyComboTogglePointerRegionLock:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
"Detected pointer region lock toggle combo");
@ -521,11 +457,9 @@ void SdlInputHandler::handleKeyEvent(SDL_KeyboardEvent* event)
m_KeysDown.remove(keyCode);
}
if (isKeyboardMouseInputAllowed()) {
LiSendKeyboardEvent2(0x8000 | keyCode,
event->state == SDL_PRESSED ?
KEY_ACTION_DOWN : KEY_ACTION_UP,
modifiers,
shouldNotConvertToScanCodeOnServer ? SS_KBE_FLAG_NON_NORMALIZED : 0);
}
LiSendKeyboardEvent2(0x8000 | keyCode,
event->state == SDL_PRESSED ?
KEY_ACTION_DOWN : KEY_ACTION_UP,
modifiers,
shouldNotConvertToScanCodeOnServer ? SS_KBE_FLAG_NON_NORMALIZED : 0);
}

View file

@ -1,16 +1,9 @@
#include "input.h"
#include "streaming/session.h"
#include <Limelight.h>
#include "SDL_compat.h"
#include "streaming/streamutils.h"
static bool isKeyboardMouseInputAllowed()
{
auto session = Session::get();
return session == nullptr || session->isMouseInputAllowed();
}
void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
{
int button;
@ -69,12 +62,10 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event)
button = BUTTON_RIGHT;
}
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(event->state == SDL_PRESSED ?
BUTTON_ACTION_PRESS :
BUTTON_ACTION_RELEASE,
button);
}
LiSendMouseButtonEvent(event->state == SDL_PRESSED ?
BUTTON_ACTION_PRESS :
BUTTON_ACTION_RELEASE,
button);
}
void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
@ -143,9 +134,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
}
}
if (mouseInVideoRegion || m_MouseWasInVideoRegion || m_PendingMouseButtonsAllUpOnVideoRegionLeave) {
if (isKeyboardMouseInputAllowed()) {
LiSendMousePositionEvent((short)x, (short)y, dst.w, dst.h);
}
LiSendMousePositionEvent((short)x, (short)y, dst.w, dst.h);
}
// Adjust the cursor visibility if applicable
@ -161,9 +150,7 @@ void SdlInputHandler::handleMouseMotionEvent(SDL_MouseMotionEvent* event)
m_MouseWasInVideoRegion = mouseInVideoRegion;
}
else {
if (isKeyboardMouseInputAllowed()) {
LiSendMouseMoveEvent(xrel, yrel);
}
LiSendMouseMoveEvent(xrel, yrel);
}
}
@ -200,9 +187,7 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
event->preciseY = SDL_clamp(event->preciseY, -1.0f, 1.0f);
#endif
if (isKeyboardMouseInputAllowed()) {
LiSendHighResScrollEvent((short)(event->preciseY * 120)); // WHEEL_DELTA
}
LiSendHighResScrollEvent((short)(event->preciseY * 120)); // WHEEL_DELTA
}
if (event->preciseX != 0.0f) {
@ -217,9 +202,7 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
event->preciseX = SDL_clamp(event->preciseX, -1.0f, 1.0f);
#endif
if (isKeyboardMouseInputAllowed()) {
LiSendHighResHScrollEvent((short)(event->preciseX * 120)); // WHEEL_DELTA
}
LiSendHighResHScrollEvent((short)(event->preciseX * 120)); // WHEEL_DELTA
}
#else
if (event->y != 0) {
@ -233,9 +216,7 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
event->y = SDL_clamp(event->y, -1, 1);
#endif
if (isKeyboardMouseInputAllowed()) {
LiSendScrollEvent((signed char)event->y);
}
LiSendScrollEvent((signed char)event->y);
}
if (event->x != 0) {
@ -249,9 +230,7 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event)
event->x = SDL_clamp(event->x, -1, 1);
#endif
if (isKeyboardMouseInputAllowed()) {
LiSendHScrollEvent((signed char)event->x);
}
LiSendHScrollEvent((signed char)event->x);
}
#endif
}

View file

@ -1,17 +1,10 @@
#include "input.h"
#include "streaming/session.h"
#include <Limelight.h>
#include "SDL_compat.h"
#include <QtMath>
static bool isKeyboardMouseInputAllowed()
{
auto session = Session::get();
return session == nullptr || session->isMouseInputAllowed();
}
// How long the mouse button will be pressed for a tap to click gesture
#define TAP_BUTTON_RELEASE_DELAY 100
@ -23,17 +16,13 @@ static bool isKeyboardMouseInputAllowed()
Uint32 SdlInputHandler::releaseLeftButtonTimerCallback(Uint32, void*)
{
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
return 0;
}
Uint32 SdlInputHandler::releaseRightButtonTimerCallback(Uint32, void*)
{
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT);
return 0;
}
@ -50,9 +39,7 @@ Uint32 SdlInputHandler::dragTimerCallback(Uint32, void *param)
me->m_DragButton = BUTTON_LEFT;
}
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, me->m_DragButton);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, me->m_DragButton);
return 0;
}
@ -112,7 +99,7 @@ void SdlInputHandler::handleRelativeFingerEvent(SDL_TouchFingerEvent* event)
// than the client window dimensions.
short deltaX = static_cast<short>(event->dx * m_StreamWidth);
short deltaY = static_cast<short>(event->dy * m_StreamHeight);
if ((deltaX != 0 || deltaY != 0) && isKeyboardMouseInputAllowed()) {
if (deltaX != 0 || deltaY != 0) {
LiSendMouseMoveEvent(deltaX, deltaY);
}
}
@ -146,9 +133,7 @@ void SdlInputHandler::handleRelativeFingerEvent(SDL_TouchFingerEvent* event)
// Release any drag
if (m_DragButton != 0) {
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, m_DragButton);
}
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, m_DragButton);
m_DragButton = 0;
}
// 2 finger tap
@ -158,9 +143,7 @@ void SdlInputHandler::handleRelativeFingerEvent(SDL_TouchFingerEvent* event)
m_TouchDownEvent[0].timestamp = 0;
// Press down the right mouse button
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
// Queue a timer to release it in 100 ms
SDL_RemoveTimer(m_RightButtonReleaseTimer);
@ -171,9 +154,7 @@ void SdlInputHandler::handleRelativeFingerEvent(SDL_TouchFingerEvent* event)
// 1 finger tap
else if (event->timestamp - m_TouchDownEvent[0].timestamp < 250) {
// Press down the left mouse button
if (isKeyboardMouseInputAllowed()) {
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
// Queue a timer to release it in 100 ms
SDL_RemoveTimer(m_LeftButtonReleaseTimer);

View file

@ -1,191 +0,0 @@
#include "streaming/input/sdlinputsubsystems.h"
#include "settings/mappingmanager.h"
namespace {
int s_JoystickRefs = 0;
int s_GameControllerRefs = 0;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
int s_HapticRefs = 0;
#endif
bool retainSubsystem(Uint32 subsystem, int& refs, const char* subsystemName, const char* ownerTag)
{
if (refs == 0 && SDL_InitSubSystem(subsystem) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to initialize %s subsystem for %s: %s",
subsystemName,
ownerTag,
SDL_GetError());
return false;
}
refs++;
return true;
}
void releaseSubsystem(Uint32 subsystem, int& refs, const char* subsystemName, const char* ownerTag)
{
if (refs <= 0) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Ignoring unbalanced release of %s subsystem for %s",
subsystemName,
ownerTag);
refs = 0;
return;
}
refs--;
if (refs == 0) {
SDL_QuitSubSystem(subsystem);
}
}
}
namespace SdlInputSubsystems {
bool acquire(const char* ownerTag, const LeaseOptions& options)
{
bool acquiredJoystick = false;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
bool acquiredGameController = false;
bool acquiredHaptic = false;
#endif
if (options.joystick) {
if (!retainSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag)) {
return false;
}
acquiredJoystick = true;
}
if (options.gameController) {
if (!retainSubsystem(SDL_INIT_GAMECONTROLLER, s_GameControllerRefs, "gamecontroller", ownerTag)) {
if (acquiredJoystick) {
releaseSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag);
}
return false;
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
acquiredGameController = true;
#endif
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
if (options.haptic) {
if (!retainSubsystem(SDL_INIT_HAPTIC, s_HapticRefs, "haptic", ownerTag)) {
if (acquiredGameController) {
releaseSubsystem(SDL_INIT_GAMECONTROLLER, s_GameControllerRefs, "gamecontroller", ownerTag);
}
if (acquiredJoystick) {
releaseSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag);
}
return false;
}
acquiredHaptic = true;
}
#endif
if (options.applyMappings) {
MappingManager mappingManager;
mappingManager.applyMappings();
}
if (options.flushControllerDeviceEvents) {
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
SDL_FlushEvent(SDL_CONTROLLERDEVICEREMOVED);
}
return true;
}
void release(const char* ownerTag, const LeaseOptions& options)
{
#if !SDL_VERSION_ATLEAST(2, 0, 9)
if (options.haptic) {
releaseSubsystem(SDL_INIT_HAPTIC, s_HapticRefs, "haptic", ownerTag);
}
#endif
if (options.gameController) {
releaseSubsystem(SDL_INIT_GAMECONTROLLER, s_GameControllerRefs, "gamecontroller", ownerTag);
}
if (options.joystick) {
releaseSubsystem(SDL_INIT_JOYSTICK, s_JoystickRefs, "joystick", ownerTag);
}
}
bool hasExclusiveGamepadOwnership()
{
if (s_JoystickRefs != 1 || s_GameControllerRefs != 1) {
return false;
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
if (s_HapticRefs != 1) {
return false;
}
#endif
return true;
}
bool reenumerateGamepadSubsystems(const char* ownerTag)
{
if (!hasExclusiveGamepadOwnership()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Skipping subsystem re-enumeration for %s due to shared ownership (joystick=%d gamecontroller=%d)",
ownerTag,
s_JoystickRefs,
s_GameControllerRefs);
return false;
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
#endif
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to reinitialize joystick subsystem for %s: %s",
ownerTag,
SDL_GetError());
return false;
}
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to reinitialize gamecontroller subsystem for %s: %s",
ownerTag,
SDL_GetError());
return false;
}
#if !SDL_VERSION_ATLEAST(2, 0, 9)
if (SDL_InitSubSystem(SDL_INIT_HAPTIC) != 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
"Failed to reinitialize haptic subsystem for %s: %s",
ownerTag,
SDL_GetError());
return false;
}
#endif
MappingManager mappingManager;
mappingManager.applyMappings();
SDL_JoystickUpdate();
SDL_GameControllerUpdate();
SDL_FlushEvents(SDL_JOYDEVICEADDED, SDL_JOYDEVICEREMOVED);
SDL_FlushEvents(SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMAPPED);
return true;
}
}

View file

@ -1,25 +0,0 @@
#pragma once
#include "SDL_compat.h"
namespace SdlInputSubsystems {
struct LeaseOptions {
bool joystick;
bool gameController;
#if !SDL_VERSION_ATLEAST(2, 0, 9)
bool haptic;
#endif
bool applyMappings;
bool flushControllerDeviceEvents;
};
bool acquire(const char* ownerTag, const LeaseOptions& options);
void release(const char* ownerTag, const LeaseOptions& options);
bool hasExclusiveGamepadOwnership();
bool reenumerateGamepadSubsystems(const char* ownerTag);
}

View file

@ -28,8 +28,6 @@
#define SDL_CODE_GAMECONTROLLER_SET_MOTION_EVENT_STATE 103
#define SDL_CODE_GAMECONTROLLER_SET_CONTROLLER_LED 104
#define SDL_CODE_GAMECONTROLLER_SET_ADAPTIVE_TRIGGERS 105
#define SDL_CODE_HIDE_STATUS_OVERLAY 106
#define SDL_CODE_SET_INPUT_POLICY 107
#include <openssl/rand.h>
@ -62,8 +60,7 @@ CONNECTION_LISTENER_CALLBACKS Session::k_ConnCallbacks = {
Session::clRumbleTriggers,
Session::clSetMotionEventState,
Session::clSetControllerLED,
Session::clSetAdaptiveTriggers,
Session::clSetInputPolicy
Session::clSetAdaptiveTriggers
};
Session* Session::s_ActiveSession;
@ -178,8 +175,27 @@ void Session::clConnectionStatusUpdate(int connectionStatus)
"Connection status update: %d",
connectionStatus);
s_ActiveSession->m_ConnectionStatus.store(connectionStatus, std::memory_order_relaxed);
s_ActiveSession->refreshControlPanelOverlay();
if (!s_ActiveSession->m_Preferences->connectionWarnings) {
return;
}
if (s_ActiveSession->m_MouseEmulationRefCount > 0) {
// Don't display the overlay if mouse emulation is already using it
return;
}
switch (connectionStatus)
{
case CONN_STATUS_POOR:
s_ActiveSession->m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate,
s_ActiveSession->m_StreamConfig.bitrate > 5000 ?
"Slow connection to PC\nReduce your bitrate" : "Poor connection to PC");
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
break;
case CONN_STATUS_OKAY:
s_ActiveSession->m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
break;
}
}
void Session::clSetHdrMode(bool enabled)
@ -258,20 +274,6 @@ void Session::clSetAdaptiveTriggers(uint16_t controllerNumber, uint8_t eventFlag
SDL_PushEvent(&setControllerLEDEvent);
}
void Session::clSetInputPolicy(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason)
{
SDL_Event setInputPolicyEvent = {};
setInputPolicyEvent.type = SDL_USEREVENT;
setInputPolicyEvent.user.code = SDL_CODE_SET_INPUT_POLICY;
setInputPolicyEvent.user.data1 = nullptr;
setInputPolicyEvent.user.data2 = (void*)(uintptr_t)(
((uintptr_t)allowKeyboard << 24) |
((uintptr_t)allowMouse << 16) |
((uintptr_t)allowGamepad << 8) |
(uintptr_t)reason);
SDL_PushEvent(&setInputPolicyEvent);
}
bool Session::chooseDecoder(StreamingPreferences::VideoDecoderSelection vds,
SDL_Window* window, int videoFormat, int width, int height,
@ -554,14 +556,6 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
m_VideoDecoder(nullptr),
m_DecoderLock(SDL_CreateMutex()),
m_AudioMuted(false),
m_ManualAudioMuted(false),
m_AudioVolumeScalar(1.0f),
m_AllowGamepadInput(true),
m_AllowKeyboardInput(false),
m_AllowMouseInput(false),
m_ControlPanelVisible(true),
m_ConnectionStatus(CONN_STATUS_OKAY),
m_StatusOverlayGeneration(0),
m_QtWindow(nullptr),
m_UnexpectedTermination(true), // Failure prior to streaming is unexpected
m_InputHandler(nullptr),
@ -575,285 +569,6 @@ Session::Session(NvComputer* computer, NvApp& app, StreamingPreferences *prefere
m_AudioSampleCount(0),
m_DropAudioEndTime(0)
{
m_TemporaryStatusOverlayText[0] = '\0';
}
void Session::setGamepadInputAllowed(bool allowed)
{
if (!allowed && m_InputHandler != nullptr) {
m_InputHandler->raiseAllGamepadInputs();
}
m_AllowGamepadInput.store(allowed, std::memory_order_relaxed);
sendInputPermissionStateToHost(LI_SESSION_INPUT_POLICY_REASON_USER_TOGGLE);
notifyInputPermissionState();
}
void Session::setKeyboardInputAllowed(bool allowed)
{
const bool previous = m_AllowKeyboardInput.exchange(allowed, std::memory_order_relaxed);
if (previous && !allowed && m_InputHandler != nullptr) {
m_InputHandler->raiseAllKeys();
}
sendInputPermissionStateToHost(LI_SESSION_INPUT_POLICY_REASON_USER_TOGGLE);
notifyInputPermissionState();
}
void Session::setMouseInputAllowed(bool allowed)
{
const bool previous = m_AllowMouseInput.exchange(allowed, std::memory_order_relaxed);
if (previous && !allowed && m_InputHandler != nullptr) {
m_InputHandler->raiseAllMouseButtons();
}
sendInputPermissionStateToHost(LI_SESSION_INPUT_POLICY_REASON_USER_TOGGLE);
notifyInputPermissionState();
}
void Session::setAudioVolumeScalar(float scalar)
{
scalar = SDL_clamp(scalar, 0.0f, 1.0f);
m_AudioVolumeScalar.store(scalar, std::memory_order_relaxed);
notifyAudioVolumeState();
}
void Session::adjustAudioVolume(float delta)
{
const float current = getAudioVolumeScalar();
const float next = SDL_clamp(current + delta, 0.0f, 1.0f);
setAudioVolumeScalar(next);
}
void Session::toggleAudioMute()
{
const bool muted = !m_ManualAudioMuted.load(std::memory_order_relaxed);
m_ManualAudioMuted.store(muted, std::memory_order_relaxed);
updateEffectiveAudioMuteState();
notifyAudioVolumeState();
}
void Session::notifyAudioVolumeState()
{
char buffer[64];
if (m_ManualAudioMuted.load(std::memory_order_relaxed)) {
SDL_snprintf(buffer, sizeof(buffer), "Volume: MUTED");
}
else {
const int percent = (int)SDL_roundf(getAudioVolumeScalar() * 100.0f);
SDL_snprintf(buffer, sizeof(buffer), "Volume: %d%%", percent);
}
showTemporaryStatusOverlay(buffer);
}
void Session::setKeyboardMouseInputAllowed(bool allowed)
{
const bool previousKeyboard = m_AllowKeyboardInput.exchange(allowed, std::memory_order_relaxed);
if (previousKeyboard && !allowed && m_InputHandler != nullptr) {
m_InputHandler->raiseAllKeys();
}
const bool previousMouse = m_AllowMouseInput.exchange(allowed, std::memory_order_relaxed);
if (previousMouse && !allowed && m_InputHandler != nullptr) {
m_InputHandler->raiseAllMouseButtons();
}
sendInputPermissionStateToHost(LI_SESSION_INPUT_POLICY_REASON_USER_TOGGLE);
notifyInputPermissionState();
}
void Session::toggleGamepadInputAllowed()
{
setGamepadInputAllowed(!isGamepadInputAllowed());
}
void Session::toggleKeyboardInputAllowed()
{
setKeyboardInputAllowed(!isKeyboardInputAllowed());
}
void Session::toggleMouseInputAllowed()
{
setMouseInputAllowed(!isMouseInputAllowed());
}
void Session::toggleKeyboardMouseInputAllowed()
{
setKeyboardMouseInputAllowed(!isKeyboardMouseInputAllowed());
}
void Session::toggleControlPanelVisibility()
{
const bool visible = !m_ControlPanelVisible.load(std::memory_order_relaxed);
m_ControlPanelVisible.store(visible, std::memory_order_relaxed);
showTemporaryStatusOverlay(visible ? "Control panel shown" : "Control panel hidden");
}
void Session::notifyInputPermissionState()
{
char buffer[160];
SDL_snprintf(buffer,
sizeof(buffer),
"Input policy: host controlled\nKeyboard: %s\nMouse: %s\nGamepad: %s",
isKeyboardInputAllowed() ? "ON" : "OFF",
isMouseInputAllowed() ? "ON" : "OFF",
isGamepadInputAllowed() ? "ON" : "OFF");
showTemporaryStatusOverlay(buffer);
}
void Session::sendInputPermissionStateToHost(uint8_t reason)
{
const bool allowKeyboard = isKeyboardInputAllowed();
const bool allowMouse = isMouseInputAllowed();
const bool allowGamepad = isGamepadInputAllowed();
const int err = LiSendSessionInputPolicy(allowKeyboard,
allowMouse,
allowGamepad,
reason);
if (err != 0 && err != LI_ERR_UNSUPPORTED) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"Failed to send input policy update to host: %d",
err);
}
}
Uint32 Session::statusOverlayTimeoutCallback(Uint32, void* param)
{
SDL_Event event = {};
event.type = SDL_USEREVENT;
event.user.code = SDL_CODE_HIDE_STATUS_OVERLAY;
event.user.data1 = param;
SDL_PushEvent(&event);
return 0;
}
void Session::refreshControlPanelOverlay()
{
if (!m_ControlPanelVisible.load(std::memory_order_relaxed)) {
if (m_TemporaryStatusOverlayText[0] != '\0') {
m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, m_TemporaryStatusOverlayText);
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
}
else {
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
}
return;
}
char volumeState[24];
if (m_ManualAudioMuted.load(std::memory_order_relaxed)) {
SDL_snprintf(volumeState, sizeof(volumeState), "MUTED");
}
else {
const int percent = (int)SDL_roundf(getAudioVolumeScalar() * 100.0f);
SDL_snprintf(volumeState, sizeof(volumeState), "%d%%", percent);
}
char panelText[640];
SDL_snprintf(panelText,
sizeof(panelText),
"Stream Controls\n"
"Keyboard: %s (KB: Ctrl+Alt+Shift+K, Pad: Select+L1+R1+Y)\n"
"Mouse: %s (KB: Ctrl+Alt+Shift+O, Pad: Select+L1+R1+DPad Up)\n"
"Pad: %s (KB: Ctrl+Alt+Shift+G, Pad: Select+L1+R1+A)\n"
"Vol: %s (KB: U/J, mute N; Pad: none)\n"
"UI: ON (KB: Ctrl+Alt+Shift+P, Pad: Select+L1+R1+B)\n"
"Input policy: host controlled",
isKeyboardInputAllowed() ? "ON" : "OFF",
isMouseInputAllowed() ? "ON" : "OFF",
isGamepadInputAllowed() ? "ON" : "OFF",
volumeState);
size_t used = SDL_strlen(panelText);
if (used < sizeof(panelText) && m_MouseEmulationRefCount > 0) {
SDL_snprintf(panelText + used,
sizeof(panelText) - used,
"\nMouse mode: ACTIVE (hold Start to disable)");
used = SDL_strlen(panelText);
}
if (used < sizeof(panelText) &&
m_Preferences->connectionWarnings &&
m_ConnectionStatus.load(std::memory_order_relaxed) == CONN_STATUS_POOR) {
SDL_snprintf(panelText + used,
sizeof(panelText) - used,
"\nNetwork: %s",
m_StreamConfig.bitrate > 5000 ? "Slow (reduce bitrate)" : "Poor");
used = SDL_strlen(panelText);
}
if (used < sizeof(panelText) && m_TemporaryStatusOverlayText[0] != '\0') {
SDL_snprintf(panelText + used,
sizeof(panelText) - used,
"\nStatus: %s",
m_TemporaryStatusOverlayText);
}
m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, panelText);
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
}
void Session::showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs)
{
if (text == nullptr || text[0] == '\0') {
return;
}
SDL_snprintf(m_TemporaryStatusOverlayText,
sizeof(m_TemporaryStatusOverlayText),
"%s",
text);
refreshControlPanelOverlay();
const uint32_t generation = m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed) + 1;
SDL_AddTimer(timeoutMs, statusOverlayTimeoutCallback, (void*)(uintptr_t)generation);
}
void Session::applyHostInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason)
{
const bool previousKeyboard = m_AllowKeyboardInput.exchange(allowKeyboard, std::memory_order_relaxed);
if (previousKeyboard && !allowKeyboard && m_InputHandler != nullptr) {
m_InputHandler->raiseAllKeys();
}
const bool previousMouse = m_AllowMouseInput.exchange(allowMouse, std::memory_order_relaxed);
if (previousMouse && !allowMouse && m_InputHandler != nullptr) {
m_InputHandler->raiseAllMouseButtons();
}
const bool previousGamepad = m_AllowGamepadInput.exchange(allowGamepad, std::memory_order_relaxed);
if (previousGamepad && !allowGamepad && m_InputHandler != nullptr) {
m_InputHandler->raiseAllGamepadInputs();
}
const bool policyChanged =
previousKeyboard != allowKeyboard ||
previousMouse != allowMouse ||
previousGamepad != allowGamepad;
if (reason == LI_SESSION_INPUT_POLICY_REASON_STREAM_START ||
reason == LI_SESSION_INPUT_POLICY_REASON_HOST_ACK ||
reason == LI_SESSION_INPUT_POLICY_REASON_HOST_OVERRIDE ||
!policyChanged) {
refreshControlPanelOverlay();
}
else {
notifyInputPermissionState();
}
}
void Session::updateEffectiveAudioMuteState()
{
bool muted = m_ManualAudioMuted.load(std::memory_order_relaxed);
if (!muted && m_Preferences->muteOnFocusLoss && m_Window != nullptr) {
muted = SDL_GetKeyboardFocus() != m_Window;
}
m_AudioMuted.store(muted, std::memory_order_relaxed);
}
Session::~Session()
@ -1826,8 +1541,14 @@ void Session::notifyMouseEmulationMode(bool enabled)
m_MouseEmulationRefCount += enabled ? 1 : -1;
SDL_assert(m_MouseEmulationRefCount >= 0);
showTemporaryStatusOverlay(enabled ? "Gamepad mouse mode enabled" : "Gamepad mouse mode disabled");
refreshControlPanelOverlay();
// We re-use the status update overlay for mouse mode notification
if (m_MouseEmulationRefCount > 0) {
m_OverlayManager.updateOverlayText(Overlay::OverlayStatusUpdate, "Gamepad mouse mode active\nLong press Start to deactivate");
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, true);
}
else {
m_OverlayManager.setOverlayState(Overlay::OverlayStatusUpdate, false);
}
}
class AsyncConnectionStartThread : public QThread
@ -2026,17 +1747,6 @@ void Session::start()
// We're now active
s_ActiveSession = this;
m_AllowGamepadInput.store(true, std::memory_order_relaxed);
m_AllowKeyboardInput.store(false, std::memory_order_relaxed);
m_AllowMouseInput.store(false, std::memory_order_relaxed);
m_ManualAudioMuted.store(false, std::memory_order_relaxed);
m_AudioMuted.store(false, std::memory_order_relaxed);
m_AudioVolumeScalar.store(1.0f, std::memory_order_relaxed);
m_ControlPanelVisible.store(true, std::memory_order_relaxed);
m_ConnectionStatus.store(CONN_STATUS_OKAY, std::memory_order_relaxed);
m_StatusOverlayGeneration.fetch_add(1, std::memory_order_relaxed);
m_TemporaryStatusOverlayText[0] = '\0';
// Initialize the gamepad code with our preferences
// NB: m_InputHandler must be initialize before starting the connection.
m_InputHandler = new SdlInputHandler(*m_Preferences, m_StreamConfig.width, m_StreamConfig.height);
@ -2239,7 +1949,6 @@ void Session::exec()
// Toggle the stats overlay if requested by the user
m_OverlayManager.setOverlayState(Overlay::OverlayDebug, m_Preferences->showPerformanceOverlay);
refreshControlPanelOverlay();
// Switch to async logging mode when we enter the SDL loop
StreamUtils::enterAsyncLoggingMode();
@ -2247,14 +1956,7 @@ void Session::exec()
// Hijack this thread to be the SDL main thread. We have to do this
// because we want to suspend all Qt processing until the stream is over.
SDL_Event event;
uint32_t nextGamepadPollTime = SDL_GetTicks();
for (;;) {
uint32_t now = SDL_GetTicks();
if (SDL_TICKS_PASSED(now, nextGamepadPollTime)) {
m_InputHandler->pollForMissingGamepads();
nextGamepadPollTime = now + 250;
}
#if SDL_VERSION_ATLEAST(2, 0, 18) && !defined(STEAM_LINK)
// SDL 2.0.18 has a proper wait event implementation that uses platform
// support to block on events rather than polling on Windows, macOS, X11,
@ -2266,7 +1968,6 @@ void Session::exec()
// issues that could cause indefinite timeouts, delayed joystick detection,
// and other problems.
if (!SDL_WaitEventTimeout(&event, 1000)) {
updateEffectiveAudioMuteState();
presence.runCallbacks();
continue;
}
@ -2283,14 +1984,10 @@ void Session::exec()
// ARM core in the Steam Link, so we will wait 10 ms instead.
SDL_Delay(10);
#endif
updateEffectiveAudioMuteState();
presence.runCallbacks();
continue;
}
#endif
updateEffectiveAudioMuteState();
switch (event.type) {
case SDL_QUIT:
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
@ -2332,21 +2029,6 @@ void Session::exec()
m_InputHandler->setAdaptiveTriggers((uint16_t)(uintptr_t)event.user.data1,
(DualSenseOutputReport *)event.user.data2);
break;
case SDL_CODE_SET_INPUT_POLICY: {
const uintptr_t packed = (uintptr_t)event.user.data2;
const bool allowKeyboard = ((packed >> 24) & 0xFF) != 0;
const bool allowMouse = ((packed >> 16) & 0xFF) != 0;
const bool allowGamepad = ((packed >> 8) & 0xFF) != 0;
const uint8_t reason = (uint8_t)(packed & 0xFF);
applyHostInputPolicy(allowKeyboard, allowMouse, allowGamepad, reason);
break;
}
case SDL_CODE_HIDE_STATUS_OVERLAY:
if ((uint32_t)(uintptr_t)event.user.data1 == m_StatusOverlayGeneration.load(std::memory_order_relaxed)) {
m_TemporaryStatusOverlayText[0] = '\0';
refreshControlPanelOverlay();
}
break;
default:
SDL_assert(false);
}
@ -2356,9 +2038,15 @@ void Session::exec()
// Early handling of some events
switch (event.window.event) {
case SDL_WINDOWEVENT_FOCUS_LOST:
if (m_Preferences->muteOnFocusLoss) {
m_AudioMuted = true;
}
m_InputHandler->notifyFocusLost();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
if (m_Preferences->muteOnFocusLoss) {
m_AudioMuted = false;
}
m_InputHandler->notifyFocusGained();
break;
case SDL_WINDOWEVENT_LEAVE:
@ -2545,7 +2233,6 @@ void Session::exec()
// After a window resize, we need to reset the pointer lock region
m_InputHandler->updatePointerRegionLock();
refreshControlPanelOverlay();
SDL_UnlockMutex(m_DecoderLock);
break;
@ -2596,9 +2283,6 @@ void Session::exec()
case SDL_JOYDEVICEADDED:
m_InputHandler->handleJoystickArrivalEvent(&event.jdevice);
break;
case SDL_JOYDEVICEREMOVED:
m_InputHandler->handleJoystickRemovalEvent(&event.jdevice);
break;
case SDL_FINGERDOWN:
case SDL_FINGERMOTION:
case SDL_FINGERUP:

View file

@ -2,7 +2,6 @@
#include <QSemaphore>
#include <QQuickWindow>
#include <atomic>
#include <Limelight.h>
#include <opus_multistream.h>
@ -126,47 +125,6 @@ public:
void setShouldExit(bool quitHostApp = false);
bool isGamepadInputAllowed() const
{
return m_AllowGamepadInput.load(std::memory_order_relaxed);
}
bool isKeyboardInputAllowed() const
{
return m_AllowKeyboardInput.load(std::memory_order_relaxed);
}
bool isMouseInputAllowed() const
{
return m_AllowMouseInput.load(std::memory_order_relaxed);
}
bool isKeyboardMouseInputAllowed() const
{
return isKeyboardInputAllowed() && isMouseInputAllowed();
}
void setGamepadInputAllowed(bool allowed);
void setKeyboardInputAllowed(bool allowed);
void setMouseInputAllowed(bool allowed);
void setKeyboardMouseInputAllowed(bool allowed);
void toggleGamepadInputAllowed();
void toggleKeyboardInputAllowed();
void toggleMouseInputAllowed();
void toggleKeyboardMouseInputAllowed();
void toggleControlPanelVisibility();
void notifyInputPermissionState();
float getAudioVolumeScalar() const
{
return m_AudioVolumeScalar.load(std::memory_order_relaxed);
}
void setAudioVolumeScalar(float scalar);
void adjustAudioVolume(float delta);
void toggleAudioMute();
void notifyAudioVolumeState();
signals:
void stageStarting(QString stage);
@ -264,9 +222,6 @@ private:
static
void clSetAdaptiveTriggers(uint16_t controllerNumber, uint8_t eventFlags, uint8_t typeLeft, uint8_t typeRight, uint8_t *left, uint8_t *right);
static
void clSetInputPolicy(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason);
static
int arInit(int audioConfiguration,
const POPUS_MULTISTREAM_CONFIGURATION opusConfig,
@ -287,15 +242,6 @@ private:
static
int drSubmitDecodeUnit(PDECODE_UNIT du);
static
Uint32 statusOverlayTimeoutCallback(Uint32 interval, void* param);
void refreshControlPanelOverlay();
void showTemporaryStatusOverlay(const char* text, Uint32 timeoutMs = 1500);
void applyHostInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason);
void sendInputPermissionStateToHost(uint8_t reason);
void updateEffectiveAudioMuteState();
StreamingPreferences* m_Preferences;
bool m_IsFullScreen;
SupportedVideoFormatList m_SupportedVideoFormats; // Sorted in order of descending priority
@ -308,15 +254,7 @@ private:
IVideoDecoder* m_VideoDecoder;
SDL_mutex* m_DecoderLock;
bool m_AudioDisabled;
std::atomic<bool> m_AudioMuted;
std::atomic<bool> m_ManualAudioMuted;
std::atomic<float> m_AudioVolumeScalar;
std::atomic<bool> m_AllowGamepadInput;
std::atomic<bool> m_AllowKeyboardInput;
std::atomic<bool> m_AllowMouseInput;
std::atomic<bool> m_ControlPanelVisible;
std::atomic<int> m_ConnectionStatus;
std::atomic<uint32_t> m_StatusOverlayGeneration;
bool m_AudioMuted;
Uint32 m_FullScreenFlag;
QQuickWindow* m_QtWindow;
bool m_UnexpectedTermination;
@ -340,7 +278,6 @@ private:
OPUS_MULTISTREAM_CONFIGURATION m_OriginalAudioConfig;
int m_AudioSampleCount;
Uint32 m_DropAudioEndTime;
char m_TemporaryStatusOverlayText[160];
Overlay::OverlayManager m_OverlayManager;

View file

@ -12,8 +12,8 @@ OverlayManager::OverlayManager() :
m_Overlays[OverlayType::OverlayDebug].color = {0xD0, 0xD0, 0x00, 0xFF};
m_Overlays[OverlayType::OverlayDebug].fontSize = 20;
m_Overlays[OverlayType::OverlayStatusUpdate].color = {0xF0, 0xF0, 0xF0, 0xFF};
m_Overlays[OverlayType::OverlayStatusUpdate].fontSize = 24;
m_Overlays[OverlayType::OverlayStatusUpdate].color = {0xCC, 0x00, 0x00, 0xFF};
m_Overlays[OverlayType::OverlayStatusUpdate].fontSize = 36;
// While TTF will usually not be initialized here, it is valid for that not to
// be the case, since Session destruction is deferred and could overlap with
@ -146,45 +146,16 @@ void OverlayManager::notifyOverlayUpdated(OverlayType type)
}
}
SDL_Surface* newSurface = nullptr;
if (m_Overlays[type].enabled) {
// The _Wrapped variant is required for line breaks to work
SDL_Surface* textSurface = TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
m_Overlays[type].text,
m_Overlays[type].color,
1024);
if (textSurface != nullptr && type == OverlayStatusUpdate) {
constexpr int kHorizontalPadding = 18;
constexpr int kVerticalPadding = 12;
SDL_Surface* panelSurface = SDL_CreateRGBSurfaceWithFormat(0,
textSurface->w + (kHorizontalPadding * 2),
textSurface->h + (kVerticalPadding * 2),
32,
SDL_PIXELFORMAT_ARGB8888);
if (panelSurface != nullptr) {
SDL_FillRect(panelSurface,
nullptr,
SDL_MapRGBA(panelSurface->format, 0x00, 0x00, 0x00, 0xA0));
SDL_Rect destination = {kHorizontalPadding, kVerticalPadding, textSurface->w, textSurface->h};
SDL_SetSurfaceBlendMode(textSurface, SDL_BLENDMODE_BLEND);
SDL_BlitSurface(textSurface, nullptr, panelSurface, &destination);
SDL_FreeSurface(textSurface);
newSurface = panelSurface;
}
else {
newSurface = textSurface;
}
}
else {
newSurface = textSurface;
}
}
// Exchange the old surface with the new one
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicSetPtr((void**)&m_Overlays[type].surface,
newSurface);
SDL_Surface* oldSurface = (SDL_Surface*)SDL_AtomicSetPtr(
(void**)&m_Overlays[type].surface,
m_Overlays[type].enabled ?
// The _Wrapped variant is required for line breaks to work
TTF_RenderText_Blended_Wrapped(m_Overlays[type].font,
m_Overlays[type].text,
m_Overlays[type].color,
1024)
: nullptr);
// Notify the renderer
m_Renderer->notifyOverlayUpdated(type);

@ -1 +1 @@
Subproject commit 611a2e7f8f6583d6d6aad30f0c8a02d6c07ab085
Subproject commit 6250fa29ee87873716045e3b64f1f229374324e8

View file

@ -1,79 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
JOBS="${JOBS:-$(nproc)}"
SKIP_BUILD=0
for arg in "$@"; do
case "$arg" in
--skip-build)
SKIP_BUILD=1
;;
--jobs=*)
JOBS="${arg#*=}"
;;
*)
echo "Unknown argument: $arg"
echo "Usage: $0 [--skip-build] [--jobs=N]"
exit 1
;;
esac
done
if [[ ! -f /run/.containerenv ]]; then
echo "Run this script inside your toolbox container."
exit 1
fi
if ! command -v dnf >/dev/null 2>&1; then
echo "This script expects a Fedora/RHEL-like toolbox with dnf."
exit 1
fi
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
FEDORA_VER="$(rpm -E %fedora)"
if ! rpm -q rpmfusion-free-release >/dev/null 2>&1; then
sudo dnf -y install "https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-${FEDORA_VER}.noarch.rpm"
fi
if ! rpm -q rpmfusion-nonfree-release >/dev/null 2>&1; then
sudo dnf -y install "https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-${FEDORA_VER}.noarch.rpm"
fi
sudo dnf -y install \
ffmpeg ffmpeg-devel \
git make gcc gcc-c++ pkgconf-pkg-config \
qt6-qtbase-devel qt6-qtdeclarative-devel qt6-qtquickcontrols2-devel qt6-qtsvg-devel qt6-qtwayland-devel \
SDL2-devel SDL2_ttf-devel \
opus-devel openssl-devel \
libva-utils libva-devel libdrm-devel mesa-libEGL-devel mesa-libGL-devel \
libX11-devel libXext-devel libXrandr-devel libXfixes-devel libXi-devel libXcursor-devel
if rpm -q libva-intel-media-driver >/dev/null 2>&1; then
sudo dnf -y swap libva-intel-media-driver intel-media-driver --allowerasing
elif ! rpm -q intel-media-driver >/dev/null 2>&1; then
sudo dnf -y install intel-media-driver
fi
git submodule update --init --recursive
if command -v vainfo >/dev/null 2>&1; then
if ! vainfo 2>/dev/null | grep -q 'VAProfileHEVCMain'; then
echo "Warning: HEVC decode profile not detected in VAAPI."
fi
if ! vainfo 2>/dev/null | grep -q 'VAProfileH264High'; then
echo "Warning: H264 decode profile not detected in VAAPI."
fi
fi
rm -rf Makefile */Makefile */Makefile.* app/release app/debug
qmake6 moonlight-qt.pro
if [[ "$SKIP_BUILD" -eq 0 ]]; then
make release -j"$JOBS"
fi
echo "Done."