2018-06-24 01:46:59 +00:00
|
|
|
#include <Limelight.h>
|
|
|
|
#include <SDL.h>
|
2018-09-29 23:18:46 +00:00
|
|
|
#include "streaming/session.h"
|
2018-09-30 03:33:45 +00:00
|
|
|
#include "settings/mappingmanager.h"
|
2018-09-13 18:17:51 +00:00
|
|
|
#include "path.h"
|
2018-06-24 01:46:59 +00:00
|
|
|
|
2018-08-06 01:01:50 +00:00
|
|
|
#include <QtGlobal>
|
2018-09-13 18:17:51 +00:00
|
|
|
#include <QDir>
|
2020-04-29 03:06:30 +00:00
|
|
|
|
2018-11-16 04:52:07 +00:00
|
|
|
#define MOUSE_POLLING_INTERVAL 5
|
|
|
|
|
2021-01-12 05:43:32 +00:00
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
HHOOK g_KeyboardHook;
|
|
|
|
#endif
|
|
|
|
|
2018-11-16 04:52:07 +00:00
|
|
|
SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int streamWidth, int streamHeight)
|
|
|
|
: m_MultiController(prefs.multiController),
|
2019-06-30 01:24:59 +00:00
|
|
|
m_GamepadMouse(prefs.gamepadMouse),
|
2020-08-23 14:05:00 +00:00
|
|
|
m_SwapMouseButtons(prefs.swapMouseButtons),
|
2020-12-26 04:21:20 +00:00
|
|
|
m_ReverseScrollDirection(prefs.reverseScrollDirection),
|
|
|
|
m_SwapFaceButtons(prefs.swapFaceButtons),
|
2018-11-16 04:52:07 +00:00
|
|
|
m_MouseMoveTimer(0),
|
2020-05-31 20:34:50 +00:00
|
|
|
m_MousePositionLock(0),
|
2020-07-12 22:06:36 +00:00
|
|
|
m_MouseWasInVideoRegion(false),
|
|
|
|
m_PendingMouseButtonsAllUpOnVideoRegionLeave(false),
|
2019-07-03 05:17:18 +00:00
|
|
|
m_FakeCaptureActive(false),
|
2021-01-12 05:43:32 +00:00
|
|
|
m_CaptureSystemKeysEnabled(false),
|
2020-04-24 04:33:45 +00:00
|
|
|
m_LongPressTimer(0),
|
2018-09-30 08:03:26 +00:00
|
|
|
m_StreamWidth(streamWidth),
|
2020-04-11 23:12:18 +00:00
|
|
|
m_StreamHeight(streamHeight),
|
2020-05-01 03:55:15 +00:00
|
|
|
m_AbsoluteMouseMode(prefs.absoluteMouseMode),
|
|
|
|
m_AbsoluteTouchMode(prefs.absoluteTouchMode),
|
2020-05-08 02:26:02 +00:00
|
|
|
m_PendingMouseLeaveButtonUp(0),
|
2020-05-01 03:55:15 +00:00
|
|
|
m_LeftButtonReleaseTimer(0),
|
|
|
|
m_RightButtonReleaseTimer(0),
|
|
|
|
m_DragTimer(0),
|
|
|
|
m_DragButton(0),
|
|
|
|
m_NumFingersDown(0)
|
2018-06-28 08:44:43 +00:00
|
|
|
{
|
2020-12-25 21:32:11 +00:00
|
|
|
// Allow gamepad input when the app doesn't have focus if requested
|
|
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, prefs.backgroundGamepad ? "1" : "0");
|
2018-08-03 05:36:44 +00:00
|
|
|
|
2020-04-25 20:00:39 +00:00
|
|
|
// If absolute mouse mode is enabled, use relative mode warp (which
|
2018-09-09 20:21:11 +00:00
|
|
|
// 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.
|
2019-03-17 07:18:15 +00:00
|
|
|
SDL_SetHintWithPriority(SDL_HINT_MOUSE_RELATIVE_MODE_WARP,
|
2020-04-25 20:00:39 +00:00
|
|
|
prefs.absoluteMouseMode ? "1" : "0",
|
2019-03-17 07:18:15 +00:00
|
|
|
SDL_HINT_OVERRIDE);
|
2018-09-09 20:21:11 +00:00
|
|
|
|
2021-01-12 05:43:32 +00:00
|
|
|
// If we're grabbing system keys, enable the X11 keyboard grab in SDL
|
|
|
|
SDL_SetHintWithPriority(SDL_HINT_GRAB_KEYBOARD,
|
|
|
|
m_CaptureSystemKeysEnabled ? "1" : "0",
|
|
|
|
SDL_HINT_OVERRIDE);
|
|
|
|
|
2020-05-07 03:43:41 +00:00
|
|
|
// Allow clicks to pass through to us when focusing the window. If we're in
|
|
|
|
// absolute mouse mode, this will avoid the user having to click twice to
|
|
|
|
// trigger a click on the host if the Moonlight window is not focused. In
|
|
|
|
// relative mode, the click event will trigger the mouse to be recaptured.
|
|
|
|
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
|
|
|
|
2021-01-09 00:14:49 +00:00
|
|
|
// Enabling extended input reports allows rumble to function on Bluetooth PS4/PS5
|
2020-03-08 00:46:41 +00:00
|
|
|
// 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.
|
2021-01-09 00:14:49 +00:00
|
|
|
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS4_RUMBLE", "1");
|
|
|
|
SDL_SetHint("SDL_JOYSTICK_HIDAPI_PS5_RUMBLE", "1");
|
2020-03-08 00:46:41 +00:00
|
|
|
|
2020-08-22 19:02:26 +00:00
|
|
|
m_OldIgnoreDevices = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES);
|
|
|
|
m_OldIgnoreDevicesExcept = SDL_GetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT);
|
|
|
|
|
|
|
|
QString streamIgnoreDevices = qgetenv("STREAM_GAMECONTROLLER_IGNORE_DEVICES");
|
|
|
|
QString streamIgnoreDevicesExcept = qgetenv("STREAM_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT");
|
|
|
|
|
|
|
|
if (!streamIgnoreDevices.isEmpty() && !streamIgnoreDevices.endsWith(',')) {
|
|
|
|
streamIgnoreDevices += ',';
|
|
|
|
}
|
|
|
|
streamIgnoreDevices += m_OldIgnoreDevices;
|
|
|
|
|
|
|
|
// For SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, we use the union of SDL_GAMECONTROLLER_IGNORE_DEVICES
|
|
|
|
// and STREAM_GAMECONTROLLER_IGNORE_DEVICES while streaming. STREAM_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT
|
|
|
|
// overrides SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT while streaming.
|
|
|
|
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, streamIgnoreDevices.toUtf8());
|
|
|
|
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, streamIgnoreDevicesExcept.toUtf8());
|
|
|
|
|
2019-01-30 06:01:05 +00:00
|
|
|
// 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();
|
|
|
|
|
2019-05-19 20:24:06 +00:00
|
|
|
// 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);
|
|
|
|
|
2018-07-21 01:15:46 +00:00
|
|
|
// 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());
|
|
|
|
}
|
|
|
|
|
2019-12-08 00:17:01 +00:00
|
|
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
2019-02-12 05:39:55 +00:00
|
|
|
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());
|
|
|
|
}
|
2019-12-08 00:17:01 +00:00
|
|
|
#endif
|
2019-02-12 05:39:55 +00:00
|
|
|
|
2018-11-18 20:05:17 +00:00
|
|
|
// 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();
|
2018-06-28 09:04:51 +00:00
|
|
|
|
|
|
|
SDL_zero(m_GamepadState);
|
2020-04-24 04:33:45 +00:00
|
|
|
SDL_zero(m_LastTouchDownEvent);
|
|
|
|
SDL_zero(m_LastTouchUpEvent);
|
2020-05-01 03:55:15 +00:00
|
|
|
SDL_zero(m_TouchDownEvent);
|
2020-05-31 20:34:50 +00:00
|
|
|
SDL_zero(m_MousePositionReport);
|
2018-11-16 04:52:07 +00:00
|
|
|
|
|
|
|
SDL_AtomicSet(&m_MouseDeltaX, 0);
|
|
|
|
SDL_AtomicSet(&m_MouseDeltaY, 0);
|
2020-05-31 20:34:50 +00:00
|
|
|
SDL_AtomicSet(&m_MousePositionUpdated, 0);
|
2018-11-16 04:52:07 +00:00
|
|
|
|
2020-01-22 03:15:17 +00:00
|
|
|
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);
|
2021-01-12 05:43:32 +00:00
|
|
|
|
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
if (m_CaptureSystemKeysEnabled) {
|
|
|
|
// If system key capture is enabled, install the window hook required to intercept
|
|
|
|
// these key presses and block them from the OS itself.
|
|
|
|
g_KeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, SdlInputHandler::keyboardHookProc, GetModuleHandle(NULL), 0);
|
|
|
|
}
|
|
|
|
#endif
|
2018-06-28 09:04:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SdlInputHandler::~SdlInputHandler()
|
|
|
|
{
|
2021-01-12 05:43:32 +00:00
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
if (g_KeyboardHook != nullptr) {
|
|
|
|
UnhookWindowsHookEx(g_KeyboardHook);
|
|
|
|
g_KeyboardHook = nullptr;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-06-28 09:04:51 +00:00
|
|
|
for (int i = 0; i < MAX_GAMEPADS; i++) {
|
2019-05-19 20:10:42 +00:00
|
|
|
if (m_GamepadState[i].mouseEmulationTimer != 0) {
|
|
|
|
Session::get()->notifyMouseEmulationMode(false);
|
|
|
|
SDL_RemoveTimer(m_GamepadState[i].mouseEmulationTimer);
|
|
|
|
}
|
2019-12-08 00:17:01 +00:00
|
|
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
2019-02-12 05:39:55 +00:00
|
|
|
if (m_GamepadState[i].haptic != nullptr) {
|
|
|
|
SDL_HapticClose(m_GamepadState[i].haptic);
|
|
|
|
}
|
2019-12-08 00:17:01 +00:00
|
|
|
#endif
|
2018-06-28 09:04:51 +00:00
|
|
|
if (m_GamepadState[i].controller != nullptr) {
|
|
|
|
SDL_GameControllerClose(m_GamepadState[i].controller);
|
|
|
|
}
|
|
|
|
}
|
2018-07-21 01:15:46 +00:00
|
|
|
|
2018-11-16 04:52:07 +00:00
|
|
|
SDL_RemoveTimer(m_MouseMoveTimer);
|
2020-04-24 04:33:45 +00:00
|
|
|
SDL_RemoveTimer(m_LongPressTimer);
|
2020-05-01 03:55:15 +00:00
|
|
|
SDL_RemoveTimer(m_LeftButtonReleaseTimer);
|
|
|
|
SDL_RemoveTimer(m_RightButtonReleaseTimer);
|
|
|
|
SDL_RemoveTimer(m_DragTimer);
|
2018-09-30 05:43:28 +00:00
|
|
|
|
2019-12-08 00:17:01 +00:00
|
|
|
#if !SDL_VERSION_ATLEAST(2, 0, 9)
|
2019-02-12 05:39:55 +00:00
|
|
|
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
|
|
|
|
SDL_assert(!SDL_WasInit(SDL_INIT_HAPTIC));
|
2019-12-08 00:17:01 +00:00
|
|
|
#endif
|
2019-02-12 05:39:55 +00:00
|
|
|
|
2018-07-21 01:15:46 +00:00
|
|
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
|
|
|
SDL_assert(!SDL_WasInit(SDL_INIT_GAMECONTROLLER));
|
2019-01-30 06:01:05 +00:00
|
|
|
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
|
|
|
SDL_assert(!SDL_WasInit(SDL_INIT_JOYSTICK));
|
2019-03-27 04:11:24 +00:00
|
|
|
|
|
|
|
// Return background event handling to off
|
|
|
|
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0");
|
2019-05-20 00:43:46 +00:00
|
|
|
|
2020-08-22 19:02:26 +00:00
|
|
|
// Restore the ignored devices
|
|
|
|
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES, m_OldIgnoreDevices.toUtf8());
|
|
|
|
SDL_SetHint(SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT, m_OldIgnoreDevicesExcept.toUtf8());
|
|
|
|
|
2019-05-20 00:43:46 +00:00
|
|
|
#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
|
2018-06-28 08:44:43 +00:00
|
|
|
}
|
|
|
|
|
2020-04-29 03:24:23 +00:00
|
|
|
void SdlInputHandler::setWindow(SDL_Window *window)
|
|
|
|
{
|
|
|
|
m_Window = window;
|
|
|
|
}
|
|
|
|
|
2021-01-12 05:43:32 +00:00
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
LRESULT CALLBACK SdlInputHandler::keyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
|
|
{
|
|
|
|
if (nCode < 0 || nCode != HC_ACTION) {
|
|
|
|
return CallNextHookEx(g_KeyboardHook, nCode, wParam, lParam);
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_Event event = {};
|
|
|
|
KBDLLHOOKSTRUCT* hookData = (KBDLLHOOKSTRUCT*)lParam;
|
|
|
|
switch (hookData->vkCode) {
|
|
|
|
case VK_LWIN:
|
|
|
|
event.key.keysym.sym = SDLK_LGUI;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_LGUI;
|
|
|
|
break;
|
|
|
|
case VK_RWIN:
|
|
|
|
event.key.keysym.sym = SDLK_RGUI;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_RGUI;
|
|
|
|
break;
|
|
|
|
case VK_LMENU:
|
|
|
|
event.key.keysym.sym = SDLK_LALT;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_LALT;
|
|
|
|
break;
|
|
|
|
case VK_RMENU:
|
|
|
|
event.key.keysym.sym = SDLK_RALT;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_RALT;
|
|
|
|
break;
|
|
|
|
case VK_LCONTROL:
|
|
|
|
event.key.keysym.sym = SDLK_LCTRL;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_LCTRL;
|
|
|
|
break;
|
|
|
|
case VK_RCONTROL:
|
|
|
|
event.key.keysym.sym = SDLK_RCTRL;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_RCTRL;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// Bail quickly if it's not a key we care about
|
|
|
|
goto NextHook;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure we're in a state where we actually want to steal this event (and it is safe to do so)
|
|
|
|
Session* session = Session::get();
|
|
|
|
if (session == nullptr || session->m_InputHandler == nullptr || !session->m_InputHandler->isSystemKeyCaptureActive()) {
|
|
|
|
goto NextHook;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is a key we're going to intercept, create the synthetic SDL event.
|
|
|
|
// This is necessary because we are also hiding this key event from ourselves too.
|
|
|
|
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
|
|
|
|
event.type = SDL_KEYDOWN;
|
|
|
|
event.key.state = SDL_PRESSED;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
event.type = SDL_KEYUP;
|
|
|
|
event.key.state = SDL_RELEASED;
|
|
|
|
}
|
|
|
|
|
|
|
|
event.key.timestamp = SDL_GetTicks();
|
|
|
|
event.key.windowID = SDL_GetWindowID(session->m_Window);
|
|
|
|
|
|
|
|
event.key.keysym.mod = SDL_GetModState();
|
|
|
|
switch (hookData->vkCode) {
|
|
|
|
case VK_LWIN:
|
|
|
|
if (event.key.state == SDL_PRESSED) {
|
|
|
|
event.key.keysym.mod |= KMOD_LGUI;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
event.key.keysym.mod &= ~KMOD_LGUI;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_RWIN:
|
|
|
|
if (event.key.state == SDL_PRESSED) {
|
|
|
|
event.key.keysym.mod |= KMOD_RGUI;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
event.key.keysym.mod &= ~KMOD_RGUI;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_LMENU:
|
|
|
|
if (event.key.state == SDL_PRESSED) {
|
|
|
|
event.key.keysym.mod |= KMOD_LALT;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
event.key.keysym.mod &= ~KMOD_LALT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_RMENU:
|
|
|
|
if (event.key.state == SDL_PRESSED) {
|
|
|
|
event.key.keysym.mod |= KMOD_RALT;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
event.key.keysym.mod &= ~KMOD_RALT;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_LCONTROL:
|
|
|
|
if (event.key.state == SDL_PRESSED) {
|
|
|
|
event.key.keysym.mod |= KMOD_LCTRL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
event.key.keysym.mod &= ~KMOD_LCTRL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case VK_RCONTROL:
|
|
|
|
if (event.key.state == SDL_PRESSED) {
|
|
|
|
event.key.keysym.mod |= KMOD_RCTRL;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
event.key.keysym.mod &= ~KMOD_RCTRL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: event.key.repeat is not populated in this path!
|
|
|
|
SDL_PushEvent(&event);
|
|
|
|
|
|
|
|
// Synchronize SDL's modifier state with the current state.
|
|
|
|
// SDL won't do this on its own because it will never see the
|
|
|
|
// WM_KEYUP/WM_KEYDOWN events.
|
|
|
|
SDL_SetModState((SDL_Keymod)event.key.keysym.mod);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
NextHook:
|
|
|
|
return CallNextHookEx(g_KeyboardHook, nCode, wParam, lParam);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-10-04 01:27:12 +00:00
|
|
|
void SdlInputHandler::raiseAllKeys()
|
|
|
|
{
|
|
|
|
if (m_KeysDown.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION,
|
|
|
|
"Raising %d keys",
|
2020-11-08 01:23:23 +00:00
|
|
|
(int)m_KeysDown.count());
|
2018-10-04 01:27:12 +00:00
|
|
|
|
|
|
|
for (auto keyDown : m_KeysDown) {
|
|
|
|
LiSendKeyboardEvent(keyDown, KEY_ACTION_UP, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_KeysDown.clear();
|
|
|
|
}
|
|
|
|
|
2020-05-08 02:26:02 +00:00
|
|
|
void SdlInputHandler::notifyMouseLeave()
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN32
|
|
|
|
// SDL on Windows doesn't send the mouse button up until the mouse re-enters the window
|
|
|
|
// after leaving it. This breaks some of the Aero snap gestures, so we'll fake it here.
|
|
|
|
if (m_AbsoluteMouseMode && isCaptureActive()) {
|
|
|
|
// NB: Not using SDL_GetGlobalMouseState() because we want our state not the system's
|
|
|
|
Uint32 mouseState = SDL_GetMouseState(nullptr, nullptr);
|
|
|
|
for (Uint32 button = SDL_BUTTON_LEFT; button <= SDL_BUTTON_X2; button++) {
|
|
|
|
if (mouseState & SDL_BUTTON(button)) {
|
|
|
|
m_PendingMouseLeaveButtonUp = button;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-04-29 03:24:23 +00:00
|
|
|
void SdlInputHandler::notifyFocusLost()
|
2020-04-25 22:37:33 +00:00
|
|
|
{
|
|
|
|
// 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.
|
2020-04-29 03:24:23 +00:00
|
|
|
if (!(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN) && !m_AbsoluteMouseMode) {
|
2020-04-25 22:37:33 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2019-07-03 05:17:18 +00:00
|
|
|
bool SdlInputHandler::isCaptureActive()
|
|
|
|
{
|
|
|
|
if (SDL_GetRelativeMouseMode()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Some platforms don't support SDL_SetRelativeMouseMode
|
|
|
|
return m_FakeCaptureActive;
|
|
|
|
}
|
|
|
|
|
2021-01-12 05:43:32 +00:00
|
|
|
bool SdlInputHandler::isSystemKeyCaptureActive()
|
|
|
|
{
|
|
|
|
if (!m_CaptureSystemKeysEnabled) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_Window == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Uint32 windowFlags = SDL_GetWindowFlags(m_Window);
|
|
|
|
return (windowFlags & SDL_WINDOW_INPUT_FOCUS) &&
|
|
|
|
(windowFlags & SDL_WINDOW_INPUT_GRABBED) &&
|
|
|
|
(windowFlags & SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
|
|
}
|
|
|
|
|
2019-07-03 05:17:18 +00:00
|
|
|
void SdlInputHandler::setCaptureActive(bool active)
|
|
|
|
{
|
|
|
|
if (active) {
|
2020-04-29 03:34:24 +00:00
|
|
|
// If we're in full-screen exclusive mode, grab the cursor so it can't accidentally leave our window.
|
2021-01-12 05:43:32 +00:00
|
|
|
// If we're in full-screen desktop mode but system key capture is enabled, also grab the cursor (will grab the keyboard too on X11).
|
|
|
|
if (((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0 && m_CaptureSystemKeysEnabled) ||
|
|
|
|
(SDL_GetWindowFlags(m_Window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN) {
|
2020-04-29 03:34:24 +00:00
|
|
|
SDL_SetWindowGrab(m_Window, SDL_TRUE);
|
|
|
|
}
|
|
|
|
|
2020-05-02 01:44:22 +00:00
|
|
|
if (!m_AbsoluteMouseMode) {
|
|
|
|
// If our window is occluded when mouse is captured, the mouse may
|
|
|
|
// get stuck on top of the occluding window and not be properly
|
|
|
|
// captured. We can avoid this by raising our window before we
|
|
|
|
// capture the mouse.
|
|
|
|
SDL_RaiseWindow(m_Window);
|
|
|
|
}
|
|
|
|
|
2020-04-11 23:12:18 +00:00
|
|
|
// If we're in relative mode, try to activate SDL's relative mouse mode
|
|
|
|
if (m_AbsoluteMouseMode || SDL_SetRelativeMouseMode(SDL_TRUE) < 0) {
|
2020-04-29 03:34:24 +00:00
|
|
|
// Relative mouse mode didn't work or was disabled, so we'll just hide the cursor
|
2019-07-03 05:17:18 +00:00
|
|
|
SDL_ShowCursor(SDL_DISABLE);
|
|
|
|
m_FakeCaptureActive = true;
|
|
|
|
}
|
2020-07-12 22:03:08 +00:00
|
|
|
|
|
|
|
// Synchronize the client and host cursor when activating absolute capture
|
|
|
|
if (m_AbsoluteMouseMode) {
|
|
|
|
int mouseX, mouseY;
|
|
|
|
int windowX, windowY;
|
|
|
|
|
|
|
|
// We have to use SDL_GetGlobalMouseState() because macOS may not reflect
|
|
|
|
// the new position of the mouse when outside the window.
|
|
|
|
SDL_GetGlobalMouseState(&mouseX, &mouseY);
|
|
|
|
|
|
|
|
// Convert global mouse state to window-relative
|
|
|
|
SDL_GetWindowPosition(m_Window, &windowX, &windowY);
|
|
|
|
mouseX -= windowX;
|
|
|
|
mouseY -= windowY;
|
|
|
|
|
|
|
|
if (isMouseInVideoRegion(mouseX, mouseY)) {
|
|
|
|
updateMousePositionReport(mouseX, mouseY);
|
|
|
|
}
|
|
|
|
}
|
2019-07-03 05:17:18 +00:00
|
|
|
}
|
|
|
|
else {
|
2020-04-29 03:34:24 +00:00
|
|
|
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);
|
2019-07-03 05:17:18 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-01 03:55:15 +00:00
|
|
|
|
|
|
|
void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event)
|
|
|
|
{
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 10)
|
|
|
|
if (SDL_GetTouchDeviceType(event->touchId) != SDL_TOUCH_DEVICE_DIRECT) {
|
|
|
|
// Ignore anything that isn't a touchscreen. We may get callbacks
|
|
|
|
// for trackpads, but we want to handle those in the mouse path.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#elif defined(Q_OS_DARWIN)
|
|
|
|
// SDL2 sends touch events from trackpads by default on
|
|
|
|
// macOS. This totally screws our actual mouse handling,
|
|
|
|
// so we must explicitly ignore touch events on macOS
|
|
|
|
// until SDL 2.0.10 where we have SDL_GetTouchDeviceType()
|
|
|
|
// to tell them apart.
|
|
|
|
return;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (m_AbsoluteTouchMode) {
|
|
|
|
handleAbsoluteFingerEvent(event);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
handleRelativeFingerEvent(event);
|
|
|
|
}
|
|
|
|
}
|