From 0c9d72a9a128a720f68bccf761e147fcd2a9f504 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 30 Apr 2020 20:55:15 -0700 Subject: [PATCH] Restore the option for relative touch mode --- app/app.pro | 1 + app/gui/SettingsView.qml | 16 +++ app/settings/streamingpreferences.cpp | 3 + app/settings/streamingpreferences.h | 3 + app/streaming/input/abstouch.cpp | 17 +-- app/streaming/input/input.cpp | 38 +++++- app/streaming/input/input.h | 22 ++++ app/streaming/input/reltouch.cpp | 178 ++++++++++++++++++++++++++ 8 files changed, 261 insertions(+), 17 deletions(-) create mode 100644 app/streaming/input/reltouch.cpp diff --git a/app/app.pro b/app/app.pro index 24ffc076..517d0c3d 100644 --- a/app/app.pro +++ b/app/app.pro @@ -151,6 +151,7 @@ SOURCES += \ streaming/input/input.cpp \ streaming/input/keyboard.cpp \ streaming/input/mouse.cpp \ + streaming/input/reltouch.cpp \ streaming/session.cpp \ streaming/audio/audio.cpp \ streaming/audio/renderers/sdlaud.cpp \ diff --git a/app/gui/SettingsView.qml b/app/gui/SettingsView.qml index 76b521ba..3cb83e80 100644 --- a/app/gui/SettingsView.qml +++ b/app/gui/SettingsView.qml @@ -596,6 +596,22 @@ Flickable { You can toggle this while streaming using Ctrl+Alt+Shift+M." } + CheckBox { + id: absoluteTouchCheck + hoverEnabled: true + text: "Use touchscreen as a trackpad" + font.pointSize: 12 + checked: !StreamingPreferences.absoluteTouchMode + onCheckedChanged: { + StreamingPreferences.absoluteTouchMode = !checked + } + + ToolTip.delay: 1000 + ToolTip.timeout: 5000 + ToolTip.visible: hovered + ToolTip.text: "When checked, the touchscreen acts like a trackpad. When unchecked, the touchscreen will directly control the mouse pointer." + } + CheckBox { id: gamepadMouseCheck hoverEnabled: true diff --git a/app/settings/streamingpreferences.cpp b/app/settings/streamingpreferences.cpp index 53713088..33313975 100644 --- a/app/settings/streamingpreferences.cpp +++ b/app/settings/streamingpreferences.cpp @@ -20,6 +20,7 @@ #define SER_MDNS "mdns" #define SER_QUITAPPAFTER "quitAppAfter" #define SER_ABSMOUSEMODE "mouseacceleration" +#define SER_ABSTOUCHMODE "abstouchmode" #define SER_STARTWINDOWED "startwindowed" #define SER_FRAMEPACING "framepacing" #define SER_CONNWARNINGS "connwarnings" @@ -60,6 +61,7 @@ void StreamingPreferences::reload() enableMdns = settings.value(SER_MDNS, true).toBool(); quitAppAfter = settings.value(SER_QUITAPPAFTER, false).toBool(); absoluteMouseMode = settings.value(SER_ABSMOUSEMODE, false).toBool(); + absoluteTouchMode = settings.value(SER_ABSTOUCHMODE, true).toBool(); startWindowed = settings.value(SER_STARTWINDOWED, true).toBool(); framePacing = settings.value(SER_FRAMEPACING, false).toBool(); connectionWarnings = settings.value(SER_CONNWARNINGS, true).toBool(); @@ -104,6 +106,7 @@ void StreamingPreferences::save() settings.setValue(SER_MDNS, enableMdns); settings.setValue(SER_QUITAPPAFTER, quitAppAfter); settings.setValue(SER_ABSMOUSEMODE, absoluteMouseMode); + settings.setValue(SER_ABSTOUCHMODE, absoluteTouchMode); settings.setValue(SER_STARTWINDOWED, startWindowed); settings.setValue(SER_FRAMEPACING, framePacing); settings.setValue(SER_CONNWARNINGS, connectionWarnings); diff --git a/app/settings/streamingpreferences.h b/app/settings/streamingpreferences.h index 8ec730d3..6d3efa16 100644 --- a/app/settings/streamingpreferences.h +++ b/app/settings/streamingpreferences.h @@ -62,6 +62,7 @@ public: Q_PROPERTY(bool enableMdns MEMBER enableMdns NOTIFY enableMdnsChanged) Q_PROPERTY(bool quitAppAfter MEMBER quitAppAfter NOTIFY quitAppAfterChanged) Q_PROPERTY(bool absoluteMouseMode MEMBER absoluteMouseMode NOTIFY absoluteMouseModeChanged) + Q_PROPERTY(bool absoluteTouchMode MEMBER absoluteTouchMode NOTIFY absoluteTouchModeChanged) Q_PROPERTY(bool startWindowed MEMBER startWindowed NOTIFY startWindowedChanged) Q_PROPERTY(bool framePacing MEMBER framePacing NOTIFY framePacingChanged) Q_PROPERTY(bool connectionWarnings MEMBER connectionWarnings NOTIFY connectionWarningsChanged) @@ -86,6 +87,7 @@ public: bool enableMdns; bool quitAppAfter; bool absoluteMouseMode; + bool absoluteTouchMode; bool startWindowed; bool framePacing; bool connectionWarnings; @@ -109,6 +111,7 @@ signals: void enableMdnsChanged(); void quitAppAfterChanged(); void absoluteMouseModeChanged(); + void absoluteTouchModeChanged(); void audioConfigChanged(); void videoCodecConfigChanged(); void videoDecoderSelectionChanged(); diff --git a/app/streaming/input/abstouch.cpp b/app/streaming/input/abstouch.cpp index dd0ef4b4..e9803431 100644 --- a/app/streaming/input/abstouch.cpp +++ b/app/streaming/input/abstouch.cpp @@ -27,23 +27,8 @@ Uint32 SdlInputHandler::longPressTimerCallback(Uint32, void*) return 0; } -void SdlInputHandler::handleTouchFingerEvent(SDL_TouchFingerEvent* event) +void SdlInputHandler::handleAbsoluteFingerEvent(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 - // Observations on Windows 10: x and y appear to be relative to 0,0 of the window client area. // Although SDL documentation states they are 0.0 - 1.0 float values, they can actually be higher // or lower than those values as touch events continue for touches started within the client area that diff --git a/app/streaming/input/input.cpp b/app/streaming/input/input.cpp index d3d5ab1a..cca3caf5 100644 --- a/app/streaming/input/input.cpp +++ b/app/streaming/input/input.cpp @@ -17,7 +17,13 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s m_LongPressTimer(0), m_StreamWidth(streamWidth), m_StreamHeight(streamHeight), - m_AbsoluteMouseMode(prefs.absoluteMouseMode) + m_AbsoluteMouseMode(prefs.absoluteMouseMode), + m_AbsoluteTouchMode(prefs.absoluteTouchMode), + m_LeftButtonReleaseTimer(0), + m_RightButtonReleaseTimer(0), + m_DragTimer(0), + m_DragButton(0), + m_NumFingersDown(0) { // Allow gamepad input when the app doesn't have focus SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); @@ -94,6 +100,8 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s SDL_zero(m_GamepadState); SDL_zero(m_LastTouchDownEvent); SDL_zero(m_LastTouchUpEvent); + SDL_zero(m_TouchDownEvent); + SDL_zero(m_CumulativeDelta); SDL_AtomicSet(&m_MouseDeltaX, 0); SDL_AtomicSet(&m_MouseDeltaY, 0); @@ -130,6 +138,9 @@ SdlInputHandler::~SdlInputHandler() SDL_RemoveTimer(m_MouseMoveTimer); SDL_RemoveTimer(m_LongPressTimer); + SDL_RemoveTimer(m_LeftButtonReleaseTimer); + SDL_RemoveTimer(m_RightButtonReleaseTimer); + SDL_RemoveTimer(m_DragTimer); #if !SDL_VERSION_ATLEAST(2, 0, 9) SDL_QuitSubSystem(SDL_INIT_HAPTIC); @@ -258,3 +269,28 @@ void SdlInputHandler::setCaptureActive(bool active) SDL_SetWindowGrab(m_Window, SDL_FALSE); } } + +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); + } +} diff --git a/app/streaming/input/input.h b/app/streaming/input/input.h index ea791a38..eedbcca3 100644 --- a/app/streaming/input/input.h +++ b/app/streaming/input/input.h @@ -86,6 +86,10 @@ private: void sendGamepadState(GamepadState* state); + void handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event); + + void handleRelativeFingerEvent(SDL_TouchFingerEvent* event); + static Uint32 longPressTimerCallback(Uint32 interval, void* param); @@ -95,6 +99,15 @@ private: static Uint32 mouseEmulationTimerCallback(Uint32 interval, void* param); + static + Uint32 releaseLeftButtonTimerCallback(Uint32 interval, void* param); + + static + Uint32 releaseRightButtonTimerCallback(Uint32 interval, void* param); + + static + Uint32 dragTimerCallback(Uint32 interval, void* param); + SDL_Window* m_Window; bool m_MultiController; bool m_GamepadMouse; @@ -112,6 +125,15 @@ private: int m_StreamWidth; int m_StreamHeight; bool m_AbsoluteMouseMode; + bool m_AbsoluteTouchMode; + + 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; static const int k_ButtonMap[]; }; diff --git a/app/streaming/input/reltouch.cpp b/app/streaming/input/reltouch.cpp new file mode 100644 index 00000000..914f415f --- /dev/null +++ b/app/streaming/input/reltouch.cpp @@ -0,0 +1,178 @@ +#include "input.h" + +#include +#include + +// 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 drag +#define DRAG_ACTIVATION_DELAY 650 + +// How far the finger can move before it cancels a drag or tap +#define DEAD_ZONE_DELTA 0.1f + +Uint32 SdlInputHandler::releaseLeftButtonTimerCallback(Uint32, void*) +{ + 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); + + return 0; +} + +void SdlInputHandler::handleRelativeFingerEvent(SDL_TouchFingerEvent* event) +{ + int fingerIndex = -1; + + // Observations on Windows 10: x and y appear to be relative to 0,0 of the window client area. + // Although SDL documentation states they are 0.0 - 1.0 float values, they can actually be higher + // or lower than those values as touch events continue for touches started within the client area that + // 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 + 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); + } + } + + // 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); + } + + 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); + + // 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; + } + } + + if (event->type == SDL_FINGERUP) { + // Cancel the drag timer on finger up + SDL_RemoveTimer(m_DragTimer); + m_DragTimer = 0; + + // 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; + + // 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; + } + else if (event->type == SDL_FINGERUP) { + m_TouchDownEvent[fingerIndex] = {}; + } +}