diff --git a/app/streaming/input/input.cpp b/app/streaming/input/input.cpp index f962eece..cdff4b0b 100644 --- a/app/streaming/input/input.cpp +++ b/app/streaming/input/input.cpp @@ -14,6 +14,8 @@ SdlInputHandler::SdlInputHandler(StreamingPreferences& prefs, NvComputer*, int s m_GamepadMouse(prefs.gamepadMouse), m_MouseMoveTimer(0), m_MousePositionLock(0), + m_MouseWasInVideoRegion(false), + m_PendingMouseButtonsAllUpOnVideoRegionLeave(false), m_FakeCaptureActive(false), m_LongPressTimer(0), m_StreamWidth(streamWidth), diff --git a/app/streaming/input/input.h b/app/streaming/input/input.h index 4cf55cf7..f882e81b 100644 --- a/app/streaming/input/input.h +++ b/app/streaming/input/input.h @@ -5,6 +5,9 @@ #include +#define SDL_CODE_HIDE_CURSOR 1 +#define SDL_CODE_SHOW_CURSOR 2 + struct GamepadState { SDL_GameController* controller; SDL_JoystickID jsId; @@ -125,6 +128,8 @@ private: int windowWidth, windowHeight; } m_MousePositionReport; SDL_atomic_t m_MousePositionUpdated; + bool m_MouseWasInVideoRegion; + bool m_PendingMouseButtonsAllUpOnVideoRegionLeave; int m_GamepadMask; GamepadState m_GamepadState[MAX_GAMEPADS]; diff --git a/app/streaming/input/mouse.cpp b/app/streaming/input/mouse.cpp index 3adc4360..feb6d67b 100644 --- a/app/streaming/input/mouse.cpp +++ b/app/streaming/input/mouse.cpp @@ -13,7 +13,8 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event) return; } else if (!isCaptureActive()) { - if (event->button == SDL_BUTTON_LEFT && event->state == SDL_RELEASED) { + if (event->button == SDL_BUTTON_LEFT && event->state == SDL_RELEASED && + isMouseInVideoRegion(event->x, event->y)) { // Capture the mouse again if clicked when unbound. // We start capture on left button released instead of // pressed to avoid sending an errant mouse button released @@ -25,6 +26,10 @@ void SdlInputHandler::handleMouseButtonEvent(SDL_MouseButtonEvent* event) // Not capturing return; } + else if (m_AbsoluteMouseMode && !isMouseInVideoRegion(event->x, event->y) && event->state == SDL_PRESSED) { + // Ignore button presses outside the video region, but allow button releases + return; + } switch (event->button) { @@ -105,6 +110,15 @@ void SdlInputHandler::handleMouseWheelEvent(SDL_MouseWheelEvent* event) return; } + if (m_AbsoluteMouseMode) { + int mouseX, mouseY; + SDL_GetMouseState(&mouseX, &mouseY); + if (!isMouseInVideoRegion(mouseX, mouseY)) { + // Ignore scroll events outside the video region + return; + } + } + if (event->y != 0) { LiSendScrollEvent((signed char)event->y); } @@ -150,6 +164,7 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param) // the mouse position. We'll pick up the new position next time. if (SDL_AtomicTryLock(&me->m_MousePositionLock)) { SDL_Rect src, dst; + bool mouseInVideoRegion; src.x = src.y = 0; src.w = me->m_StreamWidth; @@ -162,6 +177,11 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param) // Use the stream and window sizes to determine the video region StreamUtils::scaleSourceToDestinationSurface(&src, &dst); + mouseInVideoRegion = me->isMouseInVideoRegion(me->m_MousePositionReport.x, + me->m_MousePositionReport.y, + me->m_MousePositionReport.windowWidth, + me->m_MousePositionReport.windowHeight); + // Clamp motion to the video region short x = qMin(qMax(me->m_MousePositionReport.x - dst.x, 0), dst.w); short y = qMin(qMax(me->m_MousePositionReport.y - dst.y, 0), dst.h); @@ -169,8 +189,36 @@ Uint32 SdlInputHandler::mouseMoveTimerCallback(Uint32 interval, void *param) // Release the spinlock to unblock the main thread SDL_AtomicUnlock(&me->m_MousePositionLock); - // Send the mouse position update - LiSendMousePositionEvent(x, y, dst.w, dst.h); + // Send the mouse position update if one of the following is true: + // a) it is in the video region now + // b) it just left the video region (to ensure the mouse is clamped to the video boundary) + // c) a mouse button is still down from before the cursor left the video region (to allow smooth dragging) + Uint32 buttonState = SDL_GetMouseState(nullptr, nullptr); + if (buttonState == 0) { + me->m_PendingMouseButtonsAllUpOnVideoRegionLeave = false; + } + if (mouseInVideoRegion || me->m_MouseWasInVideoRegion || me->m_PendingMouseButtonsAllUpOnVideoRegionLeave) { + LiSendMousePositionEvent(x, y, dst.w, dst.h); + } + + // Adjust the cursor visibility if applicable + if (mouseInVideoRegion ^ me->m_MouseWasInVideoRegion) { + // We must push an event for the main thread to process, because it's not safe + // to directly can SDL_ShowCursor() on the arbitrary thread on which this timer + // executes. + SDL_Event event; + event.type = SDL_USEREVENT; + event.user.code = mouseInVideoRegion ? SDL_CODE_HIDE_CURSOR : SDL_CODE_SHOW_CURSOR; + SDL_PushEvent(&event); + + if (!mouseInVideoRegion && buttonState != 0) { + // If we still have a button pressed on leave, wait for that to come up + // before we stop sending mouse position events. + me->m_PendingMouseButtonsAllUpOnVideoRegionLeave = true; + } + } + + me->m_MouseWasInVideoRegion = mouseInVideoRegion; } } diff --git a/app/streaming/session.cpp b/app/streaming/session.cpp index 4ee57d92..5838432b 100644 --- a/app/streaming/session.cpp +++ b/app/streaming/session.cpp @@ -1195,8 +1195,19 @@ void Session::exec(int displayOriginX, int displayOriginY) goto DispatchDeferredCleanup; case SDL_USEREVENT: - SDL_assert(event.user.code == SDL_CODE_FRAME_READY); - m_VideoDecoder->renderFrameOnMainThread(); + switch (event.user.code) { + case SDL_CODE_FRAME_READY: + m_VideoDecoder->renderFrameOnMainThread(); + break; + case SDL_CODE_HIDE_CURSOR: + SDL_ShowCursor(SDL_DISABLE); + break; + case SDL_CODE_SHOW_CURSOR: + SDL_ShowCursor(SDL_ENABLE); + break; + default: + SDL_assert(false); + } break; case SDL_WINDOWEVENT: