diff --git a/app/streaming/input.cpp b/app/streaming/input.cpp index dad503a3..032a4a36 100644 --- a/app/streaming/input.cpp +++ b/app/streaming/input.cpp @@ -22,14 +22,17 @@ #define MOUSE_POLLING_INTERVAL 5 -// How long the mouse button will be pressed for a tap to click gesture -#define TAP_BUTTON_RELEASE_DELAY 100 +// How long the fingers must be stationary to start a right click +#define LONG_PRESS_ACTIVATION_DELAY 650 -// How long the fingers must be stationary to start a drag -#define DRAG_ACTIVATION_DELAY 650 +// How far the finger can move before it cancels a right click +#define LONG_PRESS_ACTIVATION_DELTA 0.01f -// How far the finger can move before it cancels a drag or tap -#define DEAD_ZONE_DELTA 0.1f +// How long the double tap deadzone stays in effect between touch up and touch down +#define DOUBLE_TAP_DEAD_ZONE_DELAY 250 + +// How far the finger can move before it can override the double tap deadzone +#define DOUBLE_TAP_DEAD_ZONE_DELTA 0.025f // How long the Start button must be pressed to toggle mouse emulation #define MOUSE_EMULATION_LONG_PRESS_TIME 750 @@ -60,11 +63,7 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s m_GamepadMouse(prefs.gamepadMouse), m_MouseMoveTimer(0), m_FakeCaptureActive(false), - m_LeftButtonReleaseTimer(0), - m_RightButtonReleaseTimer(0), - m_DragTimer(0), - m_DragButton(0), - m_NumFingersDown(0), + m_LongPressTimer(0), m_StreamWidth(streamWidth), m_StreamHeight(streamHeight), m_AbsoluteMouseMode(false) @@ -142,8 +141,8 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s m_GamepadMask = getAttachedGamepadMask(); SDL_zero(m_GamepadState); - SDL_zero(m_TouchDownEvent); - SDL_zero(m_CumulativeDelta); + SDL_zero(m_LastTouchDownEvent); + SDL_zero(m_LastTouchUpEvent); SDL_AtomicSet(&m_MouseDeltaX, 0); SDL_AtomicSet(&m_MouseDeltaY, 0); @@ -179,9 +178,7 @@ SdlInputHandler::~SdlInputHandler() } SDL_RemoveTimer(m_MouseMoveTimer); - SDL_RemoveTimer(m_LeftButtonReleaseTimer); - SDL_RemoveTimer(m_RightButtonReleaseTimer); - SDL_RemoveTimer(m_DragTimer); + SDL_RemoveTimer(m_LongPressTimer); #if !SDL_VERSION_ATLEAST(2, 0, 9) SDL_QuitSubSystem(SDL_INIT_HAPTIC); @@ -755,32 +752,11 @@ void SdlInputHandler::sendGamepadState(GamepadState* state) state->rsY); } -Uint32 SdlInputHandler::releaseLeftButtonTimerCallback(Uint32, void*) +Uint32 SdlInputHandler::longPressTimerCallback(Uint32, void *param) { + // Raise the left click and start a right click LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT); - return 0; -} - -Uint32 SdlInputHandler::releaseRightButtonTimerCallback(Uint32, void*) -{ - LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT); - return 0; -} - -Uint32 SdlInputHandler::dragTimerCallback(Uint32, void *param) -{ - auto me = reinterpret_cast(param); - - // Check how many fingers are down now to decide - // which button to hold down - if (me->m_NumFingersDown == 2) { - me->m_DragButton = BUTTON_RIGHT; - } - else if (me->m_NumFingersDown == 1) { - me->m_DragButton = BUTTON_LEFT; - } - - LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, me->m_DragButton); + LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT); return 0; } @@ -1254,10 +1230,8 @@ void SdlInputHandler::rumble(unsigned short controllerNumber, unsigned short low #endif } -void SdlInputHandler::handleTouchFingerEvent(SDL_Window*, SDL_TouchFingerEvent* event) +void SdlInputHandler::handleTouchFingerEvent(SDL_Window* window, SDL_TouchFingerEvent* event) { - int fingerIndex = -1; - #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 @@ -1279,128 +1253,74 @@ void SdlInputHandler::handleTouchFingerEvent(SDL_Window*, SDL_TouchFingerEvent* // leave the client area during a drag motion. // dx and dy are deltas from the last touch event, not the first touch down. - // Determine the index of this finger using our list - // of fingers that are currently active on screen. - // This is also required to handle finger up which - // where the finger will not be in SDL_GetTouchFinger() - // anymore. - if (event->type != SDL_FINGERDOWN) { - for (int i = 0; i < MAX_FINGERS; i++) { - if (event->fingerId == m_TouchDownEvent[i].fingerId) { - fingerIndex = i; - break; - } - } - } - else { - // Resolve the new finger by determining the ID of each - // finger on the display. - int numTouchFingers = SDL_GetNumTouchFingers(event->touchId); - for (int i = 0; i < numTouchFingers; i++) { - SDL_Finger* finger = SDL_GetTouchFinger(event->touchId, i); - SDL_assert(finger != nullptr); - if (finger != nullptr) { - if (finger->id == event->fingerId) { - fingerIndex = i; - break; - } - } - } - } - - if (fingerIndex < 0 || fingerIndex >= MAX_FINGERS) { - // Too many fingers + // Ignore touch down events with more than one finger + if (event->type == SDL_FINGERDOWN && SDL_GetNumTouchFingers(event->touchId) > 1) { return; } - // Handle cursor motion based on the position of the - // primary finger on screen - if (fingerIndex == 0) { - // The event x and y values are relative to our window width - // and height. However, we want to scale them to be relative - // to the host resolution. Fortunately this is easy since we - // already have normalized values. We'll just multiply them - // by the stream dimensions to get real X and Y values rather - // than the client window dimensions. - short deltaX = static_cast(event->dx * m_StreamWidth); - short deltaY = static_cast(event->dy * m_StreamHeight); - if (deltaX != 0 || deltaY != 0) { - LiSendMouseMoveEvent(deltaX, deltaY); - } + // Ignore touch move and touch up events from the non-primary finger + if (event->type != SDL_FINGERDOWN && event->fingerId != m_LastTouchDownEvent.fingerId) { + return; } - // Start a drag timer when primary or secondary - // fingers go down - if (event->type == SDL_FINGERDOWN && - (fingerIndex == 0 || fingerIndex == 1)) { - SDL_RemoveTimer(m_DragTimer); - m_DragTimer = SDL_AddTimer(DRAG_ACTIVATION_DELAY, - dragTimerCallback, - this); + SDL_Rect src, dst; + int windowWidth, windowHeight; + + SDL_GetWindowSize(window, &windowWidth, &windowHeight); + + src.x = src.y = 0; + src.w = m_StreamWidth; + src.h = m_StreamHeight; + + dst.x = dst.y = 0; + dst.w = windowWidth; + dst.h = windowHeight; + + // Use the stream and window sizes to determine the video region + StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + + if (qSqrt(qPow(event->x - m_LastTouchDownEvent.x, 2) + qPow(event->y - m_LastTouchDownEvent.y, 2)) > LONG_PRESS_ACTIVATION_DELTA) { + // Moved too far since touch down. Cancel the long press timer. + SDL_RemoveTimer(m_LongPressTimer); + m_LongPressTimer = 0; } - if (event->type == SDL_FINGERMOTION) { - // Count the total cumulative dx/dy that the finger - // has moved. - m_CumulativeDelta[fingerIndex] += qAbs(event->x); - m_CumulativeDelta[fingerIndex] += qAbs(event->y); + // Don't reposition for finger down events within the deadzone. This makes double-clicking easier. + if (event->type != SDL_FINGERDOWN || + event->timestamp - m_LastTouchUpEvent.timestamp > DOUBLE_TAP_DEAD_ZONE_DELAY || + qSqrt(qPow(event->x - m_LastTouchUpEvent.x, 2) + qPow(event->y - m_LastTouchUpEvent.y, 2)) > DOUBLE_TAP_DEAD_ZONE_DELTA) { + // Scale window-relative events to be video-relative and clamp to video region + short x = qMin(qMax((int)(event->x * windowWidth), dst.x), dst.x + dst.w); + short y = qMin(qMax((int)(event->y * windowHeight), dst.y), dst.y + dst.h); - // If it's outside the deadzone delta, cancel drags and taps - if (m_CumulativeDelta[fingerIndex] > DEAD_ZONE_DELTA) { - SDL_RemoveTimer(m_DragTimer); - m_DragTimer = 0; - - // This effectively cancels the tap logic below - m_TouchDownEvent[fingerIndex].timestamp = 0; - } + // Update the cursor position relative to the video region + LiSendMousePositionEvent(x - dst.x, y - dst.y, dst.w, dst.h); } - if (event->type == SDL_FINGERUP) { - // Cancel the drag timer on finger up - SDL_RemoveTimer(m_DragTimer); - m_DragTimer = 0; + if (event->type == SDL_FINGERDOWN) { + m_LastTouchDownEvent = *event; - // Release any drag - if (m_DragButton != 0) { - LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, m_DragButton); - m_DragButton = 0; - } - // 2 finger tap - else if (event->timestamp - m_TouchDownEvent[1].timestamp < 250) { - // Zero timestamp of the primary finger to ensure we won't - // generate a left click if the primary finger comes up soon. - m_TouchDownEvent[0].timestamp = 0; + // Start/restart the long press timer + SDL_RemoveTimer(m_LongPressTimer); + m_LongPressTimer = SDL_AddTimer(LONG_PRESS_ACTIVATION_DELAY, + longPressTimerCallback, + nullptr); - // Press down the right mouse button - LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT); - - // Queue a timer to release it in 100 ms - SDL_RemoveTimer(m_RightButtonReleaseTimer); - m_RightButtonReleaseTimer = SDL_AddTimer(TAP_BUTTON_RELEASE_DELAY, - releaseRightButtonTimerCallback, - nullptr); - } - // 1 finger tap - else if (event->timestamp - m_TouchDownEvent[0].timestamp < 250) { - // Press down the left mouse button - LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT); - - // Queue a timer to release it in 100 ms - SDL_RemoveTimer(m_LeftButtonReleaseTimer); - m_LeftButtonReleaseTimer = SDL_AddTimer(TAP_BUTTON_RELEASE_DELAY, - releaseLeftButtonTimerCallback, - nullptr); - } - } - - m_NumFingersDown = SDL_GetNumTouchFingers(event->touchId); - - if (event->type == SDL_FINGERDOWN) { - m_TouchDownEvent[fingerIndex] = *event; - m_CumulativeDelta[fingerIndex] = 0; + // Left button down on finger down + LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT); } else if (event->type == SDL_FINGERUP) { - m_TouchDownEvent[fingerIndex] = {}; + m_LastTouchUpEvent = *event; + + // Cancel the long press timer + SDL_RemoveTimer(m_LongPressTimer); + m_LongPressTimer = 0; + + // Left button up on finger up + LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT); + + // Raise right button too in case we triggered a long press gesture + LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_RIGHT); } } diff --git a/app/streaming/input.h b/app/streaming/input.h index eb1b65a3..a90d3d4e 100644 --- a/app/streaming/input.h +++ b/app/streaming/input.h @@ -81,13 +81,7 @@ private: void sendGamepadState(GamepadState* state); static - Uint32 releaseLeftButtonTimerCallback(Uint32 interval, void* param); - - static - Uint32 releaseRightButtonTimerCallback(Uint32 interval, void* param); - - static - Uint32 dragTimerCallback(Uint32 interval, void* param); + Uint32 longPressTimerCallback(Uint32 interval, void* param); static Uint32 mouseMoveTimerCallback(Uint32 interval, void* param); @@ -105,13 +99,9 @@ private: QSet m_KeysDown; bool m_FakeCaptureActive; - SDL_TouchFingerEvent m_TouchDownEvent[MAX_FINGERS]; - float m_CumulativeDelta[MAX_FINGERS]; - SDL_TimerID m_LeftButtonReleaseTimer; - SDL_TimerID m_RightButtonReleaseTimer; - SDL_TimerID m_DragTimer; - char m_DragButton; - int m_NumFingersDown; + SDL_TouchFingerEvent m_LastTouchDownEvent; + SDL_TouchFingerEvent m_LastTouchUpEvent; + SDL_TimerID m_LongPressTimer; int m_StreamWidth; int m_StreamHeight; bool m_AbsoluteMouseMode;