moonlight-qt/app/streaming/input/abstouch.cpp

205 lines
7.1 KiB
C++
Raw Normal View History

2020-05-01 03:22:43 +00:00
#include "input.h"
#include <Limelight.h>
#include <SDL.h>
#include <SDL_syswm.h>
2020-05-01 03:22:43 +00:00
#include "streaming/streamutils.h"
#include <QtMath>
// How long the fingers must be stationary to start a right click
#define LONG_PRESS_ACTIVATION_DELAY 650
// How far the finger can move before it cancels a right click
#define LONG_PRESS_ACTIVATION_DELTA 0.01f
// 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
Uint32 SdlInputHandler::longPressTimerCallback(Uint32, void*)
{
// Raise the left click and start a right click
LiSendMouseButtonEvent(BUTTON_ACTION_RELEASE, BUTTON_LEFT);
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_RIGHT);
return 0;
}
void SdlInputHandler::disableTouchFeedback()
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(m_Window, &info);
#ifdef Q_OS_WIN32
if (info.subsystem == SDL_SYSWM_WINDOWS) {
auto fnSetWindowFeedbackSetting = (decltype(SetWindowFeedbackSetting)*)GetProcAddress(GetModuleHandleW(L"user32.dll"), "SetWindowFeedbackSetting");
if (fnSetWindowFeedbackSetting) {
constexpr FEEDBACK_TYPE feedbackTypes[] = {
FEEDBACK_TOUCH_CONTACTVISUALIZATION,
FEEDBACK_PEN_BARRELVISUALIZATION,
FEEDBACK_PEN_TAP,
FEEDBACK_PEN_DOUBLETAP,
FEEDBACK_PEN_PRESSANDHOLD,
FEEDBACK_PEN_RIGHTTAP,
FEEDBACK_TOUCH_TAP,
FEEDBACK_TOUCH_DOUBLETAP,
FEEDBACK_TOUCH_PRESSANDHOLD,
FEEDBACK_TOUCH_RIGHTTAP,
FEEDBACK_GESTURE_PRESSANDTAP,
};
for (FEEDBACK_TYPE ft : feedbackTypes) {
BOOL val = FALSE;
fnSetWindowFeedbackSetting(info.info.win.window, ft, 0, sizeof(val), &val);
}
}
}
#endif
}
void SdlInputHandler::handleAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
{
SDL_Rect src, dst;
int windowWidth, windowHeight;
SDL_GetWindowSize(m_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;
// Scale window-relative events to be video-relative and clamp to video region
2023-07-23 06:13:54 +00:00
StreamUtils::scaleSourceToDestinationSurface(&src, &dst);
float vidrelx = qMin(qMax((int)(event->x * windowWidth), dst.x), dst.x + dst.w) - dst.x;
float vidrely = qMin(qMax((int)(event->y * windowHeight), dst.y), dst.y + dst.h) - dst.y;
uint8_t eventType;
switch (event->type) {
case SDL_FINGERDOWN:
eventType = LI_TOUCH_EVENT_DOWN;
break;
case SDL_FINGERMOTION:
eventType = LI_TOUCH_EVENT_MOVE;
break;
case SDL_FINGERUP:
eventType = LI_TOUCH_EVENT_UP;
break;
default:
return;
}
uint32_t pointerId;
// If the pointer ID is larger than we can fit, just CRC it and use that as the ID.
if ((uint64_t)event->fingerId > UINT32_MAX) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QByteArrayView bav((char*)&event->fingerId, sizeof(event->fingerId));
pointerId = qChecksum(bav);
#else
pointerId = qChecksum((char*)&event->fingerId, sizeof(event->fingerId));
#endif
}
else {
pointerId = (uint32_t)event->fingerId;
}
// Try to send it as a native touch event, otherwise fall back to our touch emulation
2023-07-22 22:23:09 +00:00
if (LiSendTouchEvent(eventType, pointerId, vidrelx / dst.w, vidrely / dst.h, event->pressure,
0.0f, 0.0f, LI_ROT_UNKNOWN) == LI_ERR_UNSUPPORTED) {
emulateAbsoluteFingerEvent(event);
}
else if (!m_DisabledTouchFeedback) {
// Disable touch feedback when passing touch natively
disableTouchFeedback();
m_DisabledTouchFeedback = true;
}
}
void SdlInputHandler::emulateAbsoluteFingerEvent(SDL_TouchFingerEvent* event)
2020-05-01 03:22:43 +00:00
{
// 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.
// Ignore touch down events with more than one finger
if (event->type == SDL_FINGERDOWN && SDL_GetNumTouchFingers(event->touchId) > 1) {
return;
}
// Ignore touch move and touch up events from the non-primary finger
if (event->type != SDL_FINGERDOWN && event->fingerId != m_LastTouchDownEvent.fingerId) {
return;
}
SDL_Rect src, dst;
int windowWidth, windowHeight;
SDL_GetWindowSize(m_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;
}
// 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);
// Update the cursor position relative to the video region
LiSendMousePositionEvent(x - dst.x, y - dst.y, dst.w, dst.h);
}
if (event->type == SDL_FINGERDOWN) {
m_LastTouchDownEvent = *event;
// Start/restart the long press timer
SDL_RemoveTimer(m_LongPressTimer);
m_LongPressTimer = SDL_AddTimer(LONG_PRESS_ACTIVATION_DELAY,
longPressTimerCallback,
nullptr);
// Left button down on finger down
LiSendMouseButtonEvent(BUTTON_ACTION_PRESS, BUTTON_LEFT);
}
else if (event->type == SDL_FINGERUP) {
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);
}
}