From 421bd74e0184a5cfd760efbf84433134c6f6e00b Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 22 Oct 2026 21:26:18 -0500 Subject: [PATCH] End mouse motion batches on mouse button events --- src/InputStream.c | 147 ++++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 63 deletions(-) diff --git a/src/InputStream.c b/src/InputStream.c index 421d62b..e953c6e 100644 --- a/src/InputStream.c +++ b/src/InputStream.c @@ -53,6 +53,9 @@ typedef struct _PACKET_HOLDER { uint32_t enetPacketFlags; uint8_t channelId; + int relMouseDeltaX; + int relMouseDeltaY; + // The union must be the last member since we abuse the NV_UNICODE_PACKET // text field to store variable length data which gets split before being // sent to the host. @@ -79,19 +82,12 @@ typedef struct _PACKET_HOLDER { static PLT_MUTEX batchedInputMutex; static PPACKET_HOLDER currentQueuedControllerPacket[MAX_GAMEPADS]; +static PPACKET_HOLDER currentQueuedRelativeMousePacket; +static PPACKET_HOLDER currentQueuedAbsoluteMousePacket; static struct { float x, y, z; bool dirty; // Update ready to send (queued packet holder in packetQueue) } currentGamepadSensorState[MAX_GAMEPADS][MAX_MOTION_EVENTS]; -static struct { - int deltaX, deltaY; - bool dirty; // Update ready to send (queued packet holder in packetQueue) -} currentRelativeMouseState; -static struct { - int x, y; - int width, height; - bool dirty; // Update ready to send (queued packet holder in packetQueue) -} currentAbsoluteMouseState; // Initializes the input stream int initializeInputStream(void) { @@ -121,9 +117,9 @@ int initializeInputStream(void) { absCurrentPosX = absCurrentPosY = 0.5f; memset(currentQueuedControllerPacket, 0, sizeof(currentQueuedControllerPacket)); + currentQueuedRelativeMousePacket = NULL; + currentQueuedAbsoluteMousePacket = NULL; memset(currentGamepadSensorState, 0, sizeof(currentGamepadSensorState)); - memset(¤tRelativeMouseState, 0, sizeof(currentRelativeMouseState)); - memset(¤tAbsoluteMouseState, 0, sizeof(currentAbsoluteMouseState)); PltCreateMutex(&batchedInputMutex); return 0; @@ -158,6 +154,8 @@ void destroyInputStream(void) { } memset(currentQueuedControllerPacket, 0, sizeof(currentQueuedControllerPacket)); + currentQueuedRelativeMousePacket = NULL; + currentQueuedAbsoluteMousePacket = NULL; PltDeleteMutex(&batchedInputMutex); } @@ -409,13 +407,12 @@ static void inputSendThreadProc(void* context) { PltLockMutex(&batchedInputMutex); - int deltaX = currentRelativeMouseState.deltaX; - int deltaY = currentRelativeMouseState.deltaY; - currentRelativeMouseState.deltaX = 0; - currentRelativeMouseState.deltaY = 0; + if (holder == currentQueuedRelativeMousePacket) { + currentQueuedRelativeMousePacket = NULL; + } - // The state change is no longer pending - currentRelativeMouseState.dirty = false; + int deltaX = holder->relMouseDeltaX; + int deltaY = holder->relMouseDeltaY; PltUnlockMutex(&batchedInputMutex); @@ -480,20 +477,9 @@ static void inputSendThreadProc(void* context) { PltLockMutex(&batchedInputMutex); - // Populate the packet with the latest state - holder->packet.mouseMoveAbs.x = BE16(currentAbsoluteMouseState.x); - holder->packet.mouseMoveAbs.y = BE16(currentAbsoluteMouseState.y); - - // There appears to be a rounding error in GFE's scaling calculation which prevents - // the cursor from reaching the far edge of the screen when streaming at smaller - // resolutions with a higher desktop resolution (like streaming 720p with a desktop - // resolution of 1080p, or streaming 720p/1080p with a desktop resolution of 4K). - // Subtracting one from the reference dimensions seems to work around this issue. - holder->packet.mouseMoveAbs.width = BE16(currentAbsoluteMouseState.width - 1); - holder->packet.mouseMoveAbs.height = BE16(currentAbsoluteMouseState.height - 1); - - // The state change is no longer pending - currentAbsoluteMouseState.dirty = false; + if (holder == currentQueuedAbsoluteMousePacket) { + currentQueuedAbsoluteMousePacket = NULL; + } PltUnlockMutex(&batchedInputMutex); @@ -760,6 +746,7 @@ int stopInputStream(void) { int LiSendMouseMoveEvent(short deltaX, short deltaY) { PPACKET_HOLDER holder; int err; + bool enqueueHolder = false; if (!initialized) { return -2; @@ -771,20 +758,15 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { PltLockMutex(&batchedInputMutex); - // Combine the previous deltas with the new one - currentRelativeMouseState.deltaX += deltaX; - currentRelativeMouseState.deltaY += deltaY; + // Switching to relative motion ends any pending absolute mouse batch + currentQueuedAbsoluteMousePacket = NULL; - // Queue a packet holder if this is the only pending relative mouse event - if (!currentRelativeMouseState.dirty) { - // Set the dirty flag to claim ownership of inserting the packet holder - // and unlock to allow other threads to enqueue or process input. - currentRelativeMouseState.dirty = true; + holder = currentQueuedRelativeMousePacket; + if (!holder) { PltUnlockMutex(&batchedInputMutex); holder = allocatePacketHolder(0); if (holder == NULL) { - currentRelativeMouseState.dirty = false; return -1; } @@ -802,21 +784,37 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { holder->packet.mouseMoveRel.header.magic = LE32(MOUSE_MOVE_REL_MAGIC); } - // Remaining fields are set in the input thread based on the latest currentRelativeMouseState values + holder->relMouseDeltaX = 0; + holder->relMouseDeltaY = 0; + enqueueHolder = true; + + PltLockMutex(&batchedInputMutex); + currentQueuedRelativeMousePacket = holder; + } + + // Combine previous deltas with the new one + holder->relMouseDeltaX += deltaX; + holder->relMouseDeltaY += deltaY; + + PltUnlockMutex(&batchedInputMutex); + + if (enqueueHolder) { err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); if (err != LBQ_SUCCESS) { LC_ASSERT(err == LBQ_BOUND_EXCEEDED); Limelog("Input queue reached maximum size limit\n"); - freePacketHolder(holder); - // We weren't able to insert the entry, so let the next call try again - currentRelativeMouseState.dirty = false; + PltLockMutex(&batchedInputMutex); + if (currentQueuedRelativeMousePacket == holder) { + currentQueuedRelativeMousePacket = NULL; + } + PltUnlockMutex(&batchedInputMutex); + + freePacketHolder(holder); } } else { - // There's already a packet holder queued to send this event - PltUnlockMutex(&batchedInputMutex); err = 0; } @@ -827,6 +825,7 @@ int LiSendMouseMoveEvent(short deltaX, short deltaY) { int LiSendMousePositionEvent(short x, short y, short referenceWidth, short referenceHeight) { PPACKET_HOLDER holder; int err; + bool enqueueHolder = false; if (!initialized) { return -2; @@ -834,22 +833,15 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer PltLockMutex(&batchedInputMutex); - // Overwrite the previous mouse location with the new one - currentAbsoluteMouseState.x = x; - currentAbsoluteMouseState.y = y; - currentAbsoluteMouseState.width = referenceWidth; - currentAbsoluteMouseState.height = referenceHeight; + // Switching to absolute position ends any pending relative mouse batch + currentQueuedRelativeMousePacket = NULL; - // Queue a packet holder if this is the only pending absolute mouse event - if (!currentAbsoluteMouseState.dirty) { - // Set the dirty flag to claim ownership of inserting the packet holder - // and unlock to allow other threads to enqueue or process input. - currentAbsoluteMouseState.dirty = true; + holder = currentQueuedAbsoluteMousePacket; + if (!holder) { PltUnlockMutex(&batchedInputMutex); holder = allocatePacketHolder(0); if (holder == NULL) { - currentAbsoluteMouseState.dirty = false; return -1; } @@ -862,21 +854,42 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer holder->packet.mouseMoveAbs.header.magic = LE32(MOUSE_MOVE_ABS_MAGIC); holder->packet.mouseMoveAbs.unused = 0; - // Remaining fields are set in the input thread based on the latest currentAbsoluteMouseState values + enqueueHolder = true; + PltLockMutex(&batchedInputMutex); + currentQueuedAbsoluteMousePacket = holder; + } + + // Overwrite the previous mouse location with the new one + holder->packet.mouseMoveAbs.x = BE16(x); + holder->packet.mouseMoveAbs.y = BE16(y); + + // There appears to be a rounding error in GFE's scaling calculation which prevents + // the cursor from reaching the far edge of the screen when streaming at smaller + // resolutions with a higher desktop resolution (like streaming 720p with a desktop + // resolution of 1080p, or streaming 720p/1080p with a desktop resolution of 4K). + // Subtracting one from the reference dimensions seems to work around this issue. + holder->packet.mouseMoveAbs.width = BE16(referenceWidth - 1); + holder->packet.mouseMoveAbs.height = BE16(referenceHeight - 1); + + PltUnlockMutex(&batchedInputMutex); + + if (enqueueHolder) { err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); if (err != LBQ_SUCCESS) { LC_ASSERT(err == LBQ_BOUND_EXCEEDED); Limelog("Input queue reached maximum size limit\n"); - freePacketHolder(holder); - // We weren't able to insert the entry, so let the next call try again - currentAbsoluteMouseState.dirty = false; + PltLockMutex(&batchedInputMutex); + if (currentQueuedAbsoluteMousePacket == holder) { + currentQueuedAbsoluteMousePacket = NULL; + } + PltUnlockMutex(&batchedInputMutex); + + freePacketHolder(holder); } } else { - // There's already a packet holder queued to send this event - PltUnlockMutex(&batchedInputMutex); err = 0; } @@ -925,6 +938,14 @@ int LiSendMouseButtonEvent(char action, int button) { holder->packet.mouseButton.header.magic = LE32(holder->packet.mouseButton.header.magic); holder->packet.mouseButton.button = (uint8_t)button; + // End any current mouse motion batches so that mouse movements that occurred + // before this button event are sent before it, and mouse movements that occur + // after this button event are sent after it. + PltLockMutex(&batchedInputMutex); + currentQueuedRelativeMousePacket = NULL; + currentQueuedAbsoluteMousePacket = NULL; + PltUnlockMutex(&batchedInputMutex); + err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry); if (err != LBQ_SUCCESS) { LC_ASSERT(err == LBQ_BOUND_EXCEEDED);