mirror of
https://github.com/moonlight-stream/moonlight-qt
synced 2024-12-15 22:02:29 +00:00
260 lines
9.4 KiB
C++
260 lines
9.4 KiB
C++
#include <Limelight.h>
|
|
#include <SDL.h>
|
|
#include "streaming/session.h"
|
|
#include "settings/mappingmanager.h"
|
|
#include "path.h"
|
|
|
|
#include <QtGlobal>
|
|
#include <QDir>
|
|
|
|
#define MOUSE_POLLING_INTERVAL 5
|
|
|
|
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
|
|
: m_MultiController(prefs.multiController),
|
|
m_GamepadMouse(prefs.gamepadMouse),
|
|
m_MouseMoveTimer(0),
|
|
m_FakeCaptureActive(false),
|
|
m_LongPressTimer(0),
|
|
m_StreamWidth(streamWidth),
|
|
m_StreamHeight(streamHeight),
|
|
m_AbsoluteMouseMode(prefs.absoluteMouseMode)
|
|
{
|
|
// Allow gamepad input when the app doesn't have focus
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
|
|
|
// If absolute mouse mode is enabled, use relative mode warp (which
|
|
// is via normal motion events that are influenced by mouse acceleration).
|
|
// Otherwise, we'll use raw input capture which is straight from the device
|
|
// without modification by the OS.
|
|
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
|
prefs.absoluteMouseMode ? "1" : "0",
|
|
SDL_HINT_OVERRIDE);
|
|
|
|
#if defined(Q_OS_DARWIN) && !SDL_VERSION_ATLEAST(2, 0, 10)
|
|
// SDL 2.0.9 on macOS has a broken HIDAPI mapping for the older Xbox One S
|
|
// firmware, so we have to disable HIDAPI for Xbox gamepads on macOS until
|
|
// SDL 2.0.10 where the bug is fixed.
|
|
// https://github.com/moonlight-stream/moonlight-qt/issues/133
|
|
// https://bugzilla.libsdl.org/show_bug.cgi?id=4395
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0");
|
|
#endif
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
|
// Enabling extended input reports allows rumble to function on Bluetooth PS4
|
|
// controllers, but breaks DirectInput applications. We will enable it because
|
|
// it's likely that working rumble is what the user is expecting. If they don't
|
|
// want this behavior, they can override it with the environment variable.
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
|
#endif
|
|
|
|
// 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,
|
|
"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.
|
|
m_GamepadMask = getAttachedGamepadMask();
|
|
|
|
SDL_zero(m_GamepadState);
|
|
SDL_zero(m_LastTouchDownEvent);
|
|
SDL_zero(m_LastTouchUpEvent);
|
|
|
|
SDL_AtomicSet(&m_MouseDeltaX, 0);
|
|
SDL_AtomicSet(&m_MouseDeltaY, 0);
|
|
|
|
Uint32 pollingInterval = QString(qgetenv("MOUSE_POLLING_INTERVAL")).toUInt();
|
|
if (pollingInterval == 0) {
|
|
pollingInterval = MOUSE_POLLING_INTERVAL;
|
|
}
|
|
else {
|
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
"Using custom mouse polling interval: %u ms",
|
|
pollingInterval);
|
|
}
|
|
|
|
m_MouseMoveTimer = SDL_AddTimer(pollingInterval, SdlInputHandler::mouseMoveTimerCallback, this);
|
|
}
|
|
|
|
SdlInputHandler::~SdlInputHandler()
|
|
{
|
|
for (int i = 0; i < MAX_GAMEPADS; i++) {
|
|
if (m_GamepadState[i].mouseEmulationTimer != 0) {
|
|
Session::get()->notifyMouseEmulationMode(false);
|
|
SDL_RemoveTimer(m_GamepadState[i].mouseEmulationTimer);
|
|
}
|
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
|
if (m_GamepadState[i].haptic != nullptr) {
|
|
SDL_HapticClose(m_GamepadState[i].haptic);
|
|
}
|
|
#endif
|
|
if (m_GamepadState[i].controller != nullptr) {
|
|
SDL_GameControllerClose(m_GamepadState[i].controller);
|
|
}
|
|
}
|
|
|
|
SDL_RemoveTimer(m_MouseMoveTimer);
|
|
SDL_RemoveTimer(m_LongPressTimer);
|
|
|
|
#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");
|
|
|
|
#ifdef STEAM_LINK
|
|
// Hide SDL's cursor on Steam Link after quitting the stream.
|
|
// FIXME: We should also do this for other situations where SDL
|
|
// and Qt will draw their own mouse cursors like KMSDRM or RPi
|
|
// video backends.
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
#endif
|
|
}
|
|
|
|
void SdlInputHandler::setWindow(SDL_Window *window)
|
|
{
|
|
m_Window = window;
|
|
}
|
|
|
|
void SdlInputHandler::raiseAllKeys()
|
|
{
|
|
if (m_KeysDown.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
"Raising %d keys",
|
|
m_KeysDown.count());
|
|
|
|
for (auto keyDown : m_KeysDown) {
|
|
LiSendKeyboardEvent(keyDown, KEY_ACTION_UP, 0);
|
|
}
|
|
|
|
m_KeysDown.clear();
|
|
}
|
|
|
|
void SdlInputHandler::notifyFocusGained()
|
|
{
|
|
// Capture mouse cursor when user actives the window by clicking on
|
|
// window's client area (borders and title bar excluded).
|
|
// Without this you would have to click the window twice (once to
|
|
// activate it, second time to enable capture). With this you need to
|
|
// click it only once.
|
|
//
|
|
// On Linux, the button press event is delivered after the focus gain
|
|
// so this is not neccessary (and leads to a click sent to the host
|
|
// when focusing the window by clicking).
|
|
//
|
|
// By excluding window's borders and title bar out, lets user still
|
|
// interact with them without mouse capture kicking in.
|
|
#if defined(Q_OS_WIN32) || defined(Q_OS_DARWIN)
|
|
int mouseX, mouseY;
|
|
Uint32 mouseState = SDL_GetGlobalMouseState(&mouseX, &mouseY);
|
|
if (mouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) {
|
|
int x, y, width, height;
|
|
SDL_GetWindowPosition(m_Window, &x, &y);
|
|
SDL_GetWindowSize(m_Window, &width, &height);
|
|
if (mouseX > x && mouseX < x+width && mouseY > y && mouseY < y+height) {
|
|
setCaptureActive(true);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SdlInputHandler::notifyFocusLost()
|
|
{
|
|
// Release mouse cursor when another window is activated (e.g. by using ALT+TAB).
|
|
// This lets user to interact with our window's title bar and with the buttons in it.
|
|
// Doing this while the window is full-screen breaks the transition out of FS
|
|
// (desktop and exclusive), so we must check for that before releasing mouse capture.
|
|
if (!(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN) && !m_AbsoluteMouseMode) {
|
|
setCaptureActive(false);
|
|
}
|
|
|
|
// Raise all keys that are currently pressed. If we don't do this, certain keys
|
|
// used in shortcuts that cause focus loss (such as Alt+Tab) may get stuck down.
|
|
raiseAllKeys();
|
|
}
|
|
|
|
bool SdlInputHandler::isCaptureActive()
|
|
{
|
|
if (SDL_GetRelativeMouseMode()) {
|
|
return true;
|
|
}
|
|
|
|
// Some platforms don't support SDL_SetRelativeMouseMode
|
|
return m_FakeCaptureActive;
|
|
}
|
|
|
|
void SdlInputHandler::setCaptureActive(bool active)
|
|
{
|
|
if (active) {
|
|
// If we're in full-screen exclusive mode, grab the cursor so it can't accidentally leave our window.
|
|
if ((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
|
SDL_SetWindowGrab(m_Window, SDL_TRUE);
|
|
}
|
|
|
|
// If we're in relative mode, try to activate SDL's relative mouse mode
|
|
if (m_AbsoluteMouseMode || SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
|
|
// Relative mouse mode didn't work or was disabled, so we'll just hide the cursor
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
m_FakeCaptureActive = true;
|
|
}
|
|
}
|
|
else {
|
|
if (m_FakeCaptureActive) {
|
|
// Display the cursor again
|
|
SDL_ShowCursor(SDL_ENABLE);
|
|
m_FakeCaptureActive = false;
|
|
}
|
|
else {
|
|
SDL_SetRelativeMouseMode(SDL_FALSE);
|
|
}
|
|
|
|
// Allow the cursor to leave the bounds of our window again.
|
|
SDL_SetWindowGrab(m_Window, SDL_FALSE);
|
|
}
|
|
}
|