Files
moonlight-common-c/src/InputStream.c
T
Cameron Gutman 615b5e2bba Rewrite input batching for mouse and gamepad sensors
The old method was too inflexible (depending on consecutive events to batch) that it couldn't
really handle stressful cases like high polling rate mice combined with multiple gamepads
reporting motion sensor events.
2023-10-16 21:43:05 -05:00

1608 lines
61 KiB
C

#include "Limelight-internal.h"
static SOCKET inputSock = INVALID_SOCKET;
static unsigned char currentAesIv[16];
static bool initialized;
static bool encryptedControlStream;
static bool needsBatchedScroll;
static int batchedScrollDelta;
static PPLT_CRYPTO_CONTEXT cryptoContext;
static LINKED_BLOCKING_QUEUE packetQueue;
static LINKED_BLOCKING_QUEUE packetHolderFreeList;
static PLT_THREAD inputSendThread;
static float absCurrentPosX;
static float absCurrentPosY;
// Limited by number of bits in activeGamepadMask
#define MAX_GAMEPADS 16
// Accelerometer and gyro
#define MAX_MOTION_EVENTS 2
static uint8_t currentPenButtonState;
static PLT_MUTEX batchedInputMutex;
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;
#define CLAMP(val, min, max) (((val) < (min)) ? (min) : (((val) > (max)) ? (max) : (val)))
#define MAX_INPUT_PACKET_SIZE 128
#define INPUT_STREAM_TIMEOUT_SEC 10
#define MAX_QUEUED_INPUT_PACKETS 150
#define PAYLOAD_SIZE(x) BE32((x)->packet.header.size)
#define PACKET_SIZE(x) (PAYLOAD_SIZE(x) + sizeof(uint32_t))
// Matches Win32 WHEEL_DELTA definition
#define LI_WHEEL_DELTA 120
// If we try to send more than one gamepad or mouse motion event
// per millisecond, we'll wait a little bit to try to batch with
// the next one. This batching wait paradoxically _decreases_
// effective input latency by avoiding packet queuing in ENet.
#define CONTROLLER_BATCHING_INTERVAL_MS 1
#define MOUSE_BATCHING_INTERVAL_MS 1
#define PEN_BATCHING_INTERVAL_MS 1
#define MOTION_BATCHING_INTERVAL_MS 1
// Don't batch up/down/cancel events
#define TOUCH_EVENT_IS_BATCHABLE(x) ((x) == LI_TOUCH_EVENT_HOVER || (x) == LI_TOUCH_EVENT_MOVE)
// Contains input stream packets
typedef struct _PACKET_HOLDER {
LINKED_BLOCKING_QUEUE_ENTRY entry;
uint32_t enetPacketFlags;
uint8_t channelId;
// 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.
union {
NV_INPUT_HEADER header;
NV_KEYBOARD_PACKET keyboard;
NV_REL_MOUSE_MOVE_PACKET mouseMoveRel;
NV_ABS_MOUSE_MOVE_PACKET mouseMoveAbs;
NV_MOUSE_BUTTON_PACKET mouseButton;
NV_CONTROLLER_PACKET controller;
NV_MULTI_CONTROLLER_PACKET multiController;
NV_SCROLL_PACKET scroll;
SS_HSCROLL_PACKET hscroll;
NV_HAPTICS_PACKET haptics;
SS_TOUCH_PACKET touch;
SS_PEN_PACKET pen;
SS_CONTROLLER_ARRIVAL_PACKET controllerArrival;
SS_CONTROLLER_TOUCH_PACKET controllerTouch;
SS_CONTROLLER_MOTION_PACKET controllerMotion;
SS_CONTROLLER_BATTERY_PACKET controllerBattery;
NV_UNICODE_PACKET unicode;
} packet;
} PACKET_HOLDER, *PPACKET_HOLDER;
// Initializes the input stream
int initializeInputStream(void) {
memcpy(currentAesIv, StreamConfig.remoteInputAesIv, sizeof(currentAesIv));
// Set a high maximum queue size limit to ensure input isn't dropped
// while the input send thread is blocked for short periods.
LbqInitializeLinkedBlockingQueue(&packetQueue, MAX_QUEUED_INPUT_PACKETS);
LbqInitializeLinkedBlockingQueue(&packetHolderFreeList, MAX_QUEUED_INPUT_PACKETS);
cryptoContext = PltCreateCryptoContext();
encryptedControlStream = APP_VERSION_AT_LEAST(7, 1, 431);
// FIXME: Unsure if this is exactly right, but it's probably good enough.
//
// GFE 3.13.1.30 is not using NVVHCI for mouse/keyboard (and is confirmed unaffected)
// GFE 3.15.0.164 seems to be the first release using NVVHCI for mouse/keyboard
//
// Sunshine also uses SendInput() so it's not affected either.
needsBatchedScroll = APP_VERSION_AT_LEAST(7, 1, 409) && !IS_SUNSHINE();
batchedScrollDelta = 0;
currentPenButtonState = 0;
// Start with the virtual mouse centered
absCurrentPosX = absCurrentPosY = 0.5f;
memset(currentGamepadSensorState, 0, sizeof(currentGamepadSensorState));
memset(&currentRelativeMouseState, 0, sizeof(currentRelativeMouseState));
memset(&currentAbsoluteMouseState, 0, sizeof(currentAbsoluteMouseState));
PltCreateMutex(&batchedInputMutex);
return 0;
}
// Destroys and cleans up the input stream
void destroyInputStream(void) {
PLINKED_BLOCKING_QUEUE_ENTRY entry, nextEntry;
PltDestroyCryptoContext(cryptoContext);
entry = LbqDestroyLinkedBlockingQueue(&packetQueue);
while (entry != NULL) {
nextEntry = entry->flink;
// The entry is stored in the data buffer
free(entry->data);
entry = nextEntry;
}
entry = LbqDestroyLinkedBlockingQueue(&packetHolderFreeList);
while (entry != NULL) {
nextEntry = entry->flink;
// The entry is stored in the data buffer
free(entry->data);
entry = nextEntry;
}
PltDeleteMutex(&batchedInputMutex);
}
static int encryptData(unsigned char* plaintext, int plaintextLen,
unsigned char* ciphertext, int* ciphertextLen) {
// Starting in Gen 7, AES GCM is used for encryption
if (AppVersionQuad[0] >= 7) {
if (!PltEncryptMessage(cryptoContext, ALGORITHM_AES_GCM, 0,
(unsigned char*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey),
currentAesIv, sizeof(currentAesIv),
ciphertext, 16,
plaintext, plaintextLen,
&ciphertext[16], ciphertextLen)) {
return -1;
}
// Increment the ciphertextLen to account for the tag
*ciphertextLen += 16;
return 0;
}
else {
// PKCS7 padding may need to be added in-place, so we must copy this into a buffer
// that can safely be modified.
unsigned char paddedData[ROUND_TO_PKCS7_PADDED_LEN(MAX_INPUT_PACKET_SIZE)];
memcpy(paddedData, plaintext, plaintextLen);
// Prior to Gen 7, 128-bit AES CBC is used for encryption with each message padded
// to the block size to ensure messages are not delayed within the cipher.
return PltEncryptMessage(cryptoContext, ALGORITHM_AES_CBC, CIPHER_FLAG_PAD_TO_BLOCK_SIZE,
(unsigned char*)StreamConfig.remoteInputAesKey, sizeof(StreamConfig.remoteInputAesKey),
currentAesIv, sizeof(currentAesIv),
NULL, 0,
paddedData, plaintextLen,
ciphertext, ciphertextLen) ? 0 : -1;
}
}
static void freePacketHolder(PPACKET_HOLDER holder) {
LC_ASSERT(holder->packet.header.size != 0);
// Place the packet holder back into the free list if it's a standard size entry
if (PACKET_SIZE(holder) > (int)sizeof(*holder) || LbqOfferQueueItem(&packetHolderFreeList, holder, &holder->entry) != LBQ_SUCCESS) {
free(holder);
}
}
static PPACKET_HOLDER allocatePacketHolder(int extraLength) {
PPACKET_HOLDER holder;
int err;
// If we're using an extended packet holder, we can't satisfy
// this allocation from the packet holder free list.
if (extraLength > 0) {
// We over-allocate here a bit since we're always adding sizeof(*holder),
// but this is on purpose. It allows us assume we have a full holder even
// if packetLength < sizeof(*holder) and put this allocation into the free
// list.
return malloc(sizeof(*holder) + extraLength);
}
// Grab an entry from the free list (if available)
err = LbqPollQueueElement(&packetHolderFreeList, (void**)&holder);
if (err == LBQ_SUCCESS) {
return holder;
}
else if (err == LBQ_INTERRUPTED) {
// We're shutting down. Don't bother allocating.
return NULL;
}
else {
LC_ASSERT(err == LBQ_NO_ELEMENT);
// Otherwise we'll have to allocate
return malloc(sizeof(*holder));
}
}
static bool sendInputPacket(PPACKET_HOLDER holder, bool moreData) {
SOCK_RET err;
// On GFE 3.22, the entire control stream is encrypted (and support for separate RI encrypted)
// has been removed. We send the plaintext packet through and the control stream code will do
// the encryption.
if (encryptedControlStream) {
err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*)&holder->packet,
PACKET_SIZE(holder),
holder->channelId,
holder->enetPacketFlags,
moreData);
if (err < 0) {
Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err);
ListenerCallbacks.connectionTerminated(err);
return false;
}
}
else {
char encryptedBuffer[MAX_INPUT_PACKET_SIZE];
uint32_t encryptedSize;
uint32_t encryptedLengthPrefix;
// Encrypt the message into the output buffer while leaving room for the length
encryptedSize = sizeof(encryptedBuffer) - sizeof(encryptedLengthPrefix);
err = encryptData((unsigned char*)&holder->packet, PACKET_SIZE(holder),
(unsigned char*)&encryptedBuffer[sizeof(encryptedLengthPrefix)], (int*)&encryptedSize);
if (err != 0) {
Limelog("Input: Encryption failed: %d\n", (int)err);
ListenerCallbacks.connectionTerminated(err);
return false;
}
// Prepend the length to the message
encryptedLengthPrefix = BE32(encryptedSize);
memcpy(&encryptedBuffer[0], &encryptedLengthPrefix, sizeof(encryptedLengthPrefix));
if (AppVersionQuad[0] < 5) {
// Send the encrypted payload
err = send(inputSock, (const char*) encryptedBuffer,
(int) (encryptedSize + sizeof(encryptedLengthPrefix)), 0);
if (err <= 0) {
Limelog("Input: send() failed: %d\n", (int) LastSocketError());
ListenerCallbacks.connectionTerminated(LastSocketFail());
return false;
}
}
else {
// For reasons that I can't understand, NVIDIA decides to use the last 16
// bytes of ciphertext in the most recent game controller packet as the IV for
// future encryption. I think it may be a buffer overrun on their end but we'll have
// to mimic it to work correctly.
if (AppVersionQuad[0] >= 7 && encryptedSize >= 16 + sizeof(currentAesIv)) {
memcpy(currentAesIv,
&encryptedBuffer[4 + encryptedSize - sizeof(currentAesIv)],
sizeof(currentAesIv));
}
err = (SOCK_RET)sendInputPacketOnControlStream((unsigned char*) encryptedBuffer,
(int)(encryptedSize + sizeof(encryptedLengthPrefix)),
holder->channelId,
holder->enetPacketFlags,
moreData);
if (err < 0) {
Limelog("Input: sendInputPacketOnControlStream() failed: %d\n", (int) err);
ListenerCallbacks.connectionTerminated(err);
return false;
}
}
}
return true;
}
static void floatToNetfloat(float in, netfloat out) {
if (IS_LITTLE_ENDIAN()) {
memcpy(out, &in, sizeof(in));
}
else {
uint8_t* inb = (uint8_t*)&in;
out[0] = inb[3];
out[1] = inb[2];
out[2] = inb[1];
out[3] = inb[0];
}
}
// Input thread proc
static void inputSendThreadProc(void* context) {
SOCK_RET err;
PPACKET_HOLDER holder;
uint32_t multiControllerMagicLE;
uint32_t relMouseMagicLE;
if (AppVersionQuad[0] >= 5) {
multiControllerMagicLE = LE32(MULTI_CONTROLLER_MAGIC_GEN5);
relMouseMagicLE = LE32(MOUSE_MOVE_REL_MAGIC_GEN5);
}
else {
multiControllerMagicLE = LE32(MULTI_CONTROLLER_MAGIC);
relMouseMagicLE = LE32(MOUSE_MOVE_REL_MAGIC);
}
uint64_t lastControllerPacketTime = 0;
uint64_t lastMousePacketTime = 0;
uint64_t lastPenPacketTime = 0;
uint64_t lastMotionPacketTime = 0;
while (!PltIsThreadInterrupted(&inputSendThread)) {
err = LbqWaitForQueueElement(&packetQueue, (void**)&holder);
if (err != LBQ_SUCCESS) {
return;
}
// If it's a multi-controller packet we can do batching
if (holder->packet.header.magic == multiControllerMagicLE) {
PPACKET_HOLDER controllerBatchHolder;
PNV_MULTI_CONTROLLER_PACKET origPkt;
uint64_t now = PltGetMillis();
// Delay for batching if required
if (now < lastControllerPacketTime + CONTROLLER_BATCHING_INTERVAL_MS) {
flushInputOnControlStream();
PltSleepMs((int)(lastControllerPacketTime + CONTROLLER_BATCHING_INTERVAL_MS - now));
now = PltGetMillis();
}
origPkt = &holder->packet.multiController;
for (;;) {
PNV_MULTI_CONTROLLER_PACKET newPkt;
// Peek at the next packet
if (LbqPeekQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) {
break;
}
// If it's not a controller packet, we're done
if (controllerBatchHolder->packet.header.magic != multiControllerMagicLE) {
break;
}
// Check if it's able to be batched
// NB: GFE does some discarding of gamepad packets received very soon after another.
// Thus, this batching is needed for correctness in some cases, as GFE will inexplicably
// drop *newer* packets in that scenario. The brokenness can be tested with consecutive
// calls to LiSendMultiControllerEvent() with different values for analog sticks (max -> zero).
newPkt = &controllerBatchHolder->packet.multiController;
if (newPkt->buttonFlags != origPkt->buttonFlags ||
newPkt->buttonFlags2 != origPkt->buttonFlags2 ||
newPkt->controllerNumber != origPkt->controllerNumber ||
newPkt->activeGamepadMask != origPkt->activeGamepadMask) {
// Batching not allowed
break;
}
// Remove the batchable controller packet
if (LbqPollQueueElement(&packetQueue, (void**)&controllerBatchHolder) != LBQ_SUCCESS) {
break;
}
// Update the original packet
origPkt->leftTrigger = newPkt->leftTrigger;
origPkt->rightTrigger = newPkt->rightTrigger;
origPkt->leftStickX = newPkt->leftStickX;
origPkt->leftStickY = newPkt->leftStickY;
origPkt->rightStickX = newPkt->rightStickX;
origPkt->rightStickY = newPkt->rightStickY;
// Free the batched packet holder
freePacketHolder(controllerBatchHolder);
}
lastControllerPacketTime = now;
}
// If it's a relative mouse move packet, we can also do batching
else if (holder->packet.header.magic == relMouseMagicLE) {
uint64_t now = PltGetMillis();
// Delay for batching if required
if (now < lastMousePacketTime + MOUSE_BATCHING_INTERVAL_MS) {
flushInputOnControlStream();
PltSleepMs((int)(lastMousePacketTime + MOUSE_BATCHING_INTERVAL_MS - now));
now = PltGetMillis();
}
PltLockMutex(&batchedInputMutex);
// Send as many packets as it takes to get the entire delta through
while (currentRelativeMouseState.deltaX != 0 || currentRelativeMouseState.deltaY != 0) {
bool more = false;
if (currentRelativeMouseState.deltaX < INT16_MIN) {
holder->packet.mouseMoveRel.deltaX = BE16(INT16_MIN);
currentRelativeMouseState.deltaX -= INT16_MIN;
more = true;
}
else if (currentRelativeMouseState.deltaX > INT16_MAX) {
holder->packet.mouseMoveRel.deltaX = BE16(INT16_MAX);
currentRelativeMouseState.deltaX -= INT16_MAX;
more = true;
}
else {
holder->packet.mouseMoveRel.deltaX = BE16(currentRelativeMouseState.deltaX);
currentRelativeMouseState.deltaX = 0;
}
if (currentRelativeMouseState.deltaY < INT16_MIN) {
holder->packet.mouseMoveRel.deltaY = BE16(INT16_MIN);
currentRelativeMouseState.deltaY -= INT16_MIN;
more = true;
}
else if (currentRelativeMouseState.deltaY > INT16_MAX) {
holder->packet.mouseMoveRel.deltaY = BE16(INT16_MAX);
currentRelativeMouseState.deltaY -= INT16_MAX;
more = true;
}
else {
holder->packet.mouseMoveRel.deltaY = BE16(currentRelativeMouseState.deltaY);
currentRelativeMouseState.deltaY = 0;
}
// Don't hold the batching lock while we're doing network I/O
PltUnlockMutex(&batchedInputMutex);
// Encrypt and send the split packet
if (!sendInputPacket(holder, more)) {
freePacketHolder(holder);
return;
}
PltLockMutex(&batchedInputMutex);
}
// The state change is no longer pending
currentRelativeMouseState.dirty = false;
PltUnlockMutex(&batchedInputMutex);
lastMousePacketTime = now;
// We sent everything we needed in the loop above, so we can just free the
// holder of the original packet and wait for another input event.
freePacketHolder(holder);
continue;
}
// If it's an absolute mouse move packet, we should only send the latest
else if (holder->packet.header.magic == LE32(MOUSE_MOVE_ABS_MAGIC)) {
uint64_t now = PltGetMillis();
// Delay for batching if required
if (now < lastMousePacketTime + MOUSE_BATCHING_INTERVAL_MS) {
flushInputOnControlStream();
PltSleepMs((int)(lastMousePacketTime + MOUSE_BATCHING_INTERVAL_MS - now));
now = PltGetMillis();
}
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;
PltUnlockMutex(&batchedInputMutex);
lastMousePacketTime = now;
}
// If it's a pen packet, we should only send the latest move or hover events
else if (holder->packet.header.magic == LE32(SS_PEN_MAGIC) && TOUCH_EVENT_IS_BATCHABLE(holder->packet.pen.eventType)) {
uint64_t now = PltGetMillis();
// Delay for batching if required
if (now < lastPenPacketTime + PEN_BATCHING_INTERVAL_MS) {
flushInputOnControlStream();
PltSleepMs((int)(lastPenPacketTime + PEN_BATCHING_INTERVAL_MS - now));
now = PltGetMillis();
}
for (;;) {
PPACKET_HOLDER penBatchHolder;
// Peek at the next packet
if (LbqPeekQueueElement(&packetQueue, (void**)&penBatchHolder) != LBQ_SUCCESS) {
break;
}
// If it's not a pen packet, we're done
if (penBatchHolder->packet.header.magic != LE32(SS_PEN_MAGIC)) {
break;
}
// If the buttons or event type is different, we cannot batch
if (holder->packet.pen.penButtons != penBatchHolder->packet.pen.penButtons ||
holder->packet.pen.eventType != penBatchHolder->packet.pen.eventType) {
break;
}
// Remove the next packet
if (LbqPollQueueElement(&packetQueue, (void**)&penBatchHolder) != LBQ_SUCCESS) {
break;
}
// Replace the current packet with the new one
freePacketHolder(holder);
holder = penBatchHolder;
}
lastPenPacketTime = now;
}
// If it's a motion packet, only send the latest for each sensor type
else if (holder->packet.header.magic == LE32(SS_CONTROLLER_MOTION_MAGIC)) {
uint64_t now = PltGetMillis();
// Delay for batching if required
if (now < lastMotionPacketTime + MOTION_BATCHING_INTERVAL_MS) {
flushInputOnControlStream();
PltSleepMs((int)(lastMotionPacketTime + MOTION_BATCHING_INTERVAL_MS - now));
now = PltGetMillis();
}
PltLockMutex(&batchedInputMutex);
// LI_MOTION_TYPE_* values are 1-based, so we have to subtract 1 to index into our state array
float x = currentGamepadSensorState[holder->packet.controllerMotion.controllerNumber][holder->packet.controllerMotion.motionType - 1].x;
float y = currentGamepadSensorState[holder->packet.controllerMotion.controllerNumber][holder->packet.controllerMotion.motionType - 1].y;
float z = currentGamepadSensorState[holder->packet.controllerMotion.controllerNumber][holder->packet.controllerMotion.motionType - 1].z;
// Motion events are so rapid that we can just drop any events that are lost in transit,
// but we will treat (0, 0, 0) as a special value for gyro events to allow clients to
// reliably set the gyro to a null state when sensor events are halted due to focus loss
// or similar client-side constraints.
if (holder->packet.controllerMotion.motionType == LI_MOTION_TYPE_GYRO && x == 0.0f && y == 0.0f && z == 0.0f) {
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
}
else {
holder->enetPacketFlags = 0;
}
// Populate the packet with the latest state
floatToNetfloat(x, holder->packet.controllerMotion.x);
floatToNetfloat(y, holder->packet.controllerMotion.y);
floatToNetfloat(z, holder->packet.controllerMotion.z);
// The state change is no longer pending
currentGamepadSensorState[holder->packet.controllerMotion.controllerNumber][holder->packet.controllerMotion.motionType - 1].dirty = false;
PltUnlockMutex(&batchedInputMutex);
lastMotionPacketTime = now;
}
// If it's a UTF-8 text packet, we may need to split it into a several packets to send
else if (holder->packet.header.magic == LE32(UTF8_TEXT_EVENT_MAGIC)) {
PACKET_HOLDER splitPacket;
uint32_t totalLength = PAYLOAD_SIZE(holder) - sizeof(uint32_t);
uint32_t i = 0;
// HACK: This is a workaround for the fact that GFE doesn't appear to synchronize keyboard
// and UTF-8 text events with each other. We need to make sure any previous keyboard events
// have been processed prior to sending these UTF-8 events to avoid interference between
// the two (especially with modifier keys).
flushInputOnControlStream();
while (!PltIsThreadInterrupted(&inputSendThread) && isControlDataInTransit()) {
PltSleepMs(10);
}
// Finally, sleep an additional 50 ms to allow the events to be processed by Windows
PltSleepMs(50);
// We send each Unicode code point individually. This way we can always ensure they will
// never straddle a packet boundary (which will cause a parsing error on the host).
while (i < totalLength && !PltIsThreadInterrupted(&inputSendThread)) {
uint32_t codePointLength;
uint8_t firstByte = (uint8_t)holder->packet.unicode.text[i];
if ((firstByte & 0x80) == 0x00) {
// 1 byte code point
codePointLength = 1;
}
else if ((firstByte & 0xE0) == 0xC0) {
// 2 byte code point
codePointLength = 2;
}
else if ((firstByte & 0xF0) == 0xE0) {
// 3 byte code point
codePointLength = 3;
}
else if ((firstByte & 0xF8) == 0xF0) {
// 4 byte code point
codePointLength = 4;
}
else {
Limelog("Invalid unicode code point starting byte: %02x\n", firstByte);
break;
}
// Use the original packet as a template and fixup to send one code point at a time
splitPacket = *holder;
splitPacket.packet.unicode.header.size = BE32(sizeof(uint32_t) + codePointLength);
memcpy(splitPacket.packet.unicode.text, &holder->packet.unicode.text[i], codePointLength);
// Encrypt and send the split packet
if (!sendInputPacket(&splitPacket, i + 1 < totalLength)) {
freePacketHolder(holder);
return;
}
i += codePointLength;
}
freePacketHolder(holder);
continue;
}
// Encrypt and send the input packet
if (!sendInputPacket(holder, LbqGetItemCount(&packetQueue) > 0)) {
freePacketHolder(holder);
return;
}
freePacketHolder(holder);
}
}
// This function tells GFE that we support haptics and it should send rumble events to us
static int sendEnableHaptics(void) {
PPACKET_HOLDER holder;
int err;
// Avoid sending this on earlier server versions, since they may terminate
// the connection upon receiving an unexpected packet.
if (!APP_VERSION_AT_LEAST(7, 1, 0)) {
return 0;
}
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_GENERIC;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.haptics.header.size = BE32(sizeof(NV_HAPTICS_PACKET) - sizeof(uint32_t));
holder->packet.haptics.header.magic = LE32(ENABLE_HAPTICS_MAGIC);
holder->packet.haptics.enable = LE16(1);
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);
}
return err;
}
// Begin the input stream
int startInputStream(void) {
int err;
// After Gen 5, we send input on the control stream
if (AppVersionQuad[0] < 5) {
inputSock = connectTcpSocket(&RemoteAddr, RemoteAddrLen,
35043, INPUT_STREAM_TIMEOUT_SEC);
if (inputSock == INVALID_SOCKET) {
return LastSocketFail();
}
enableNoDelay(inputSock);
}
err = PltCreateThread("InputSend", inputSendThreadProc, NULL, &inputSendThread);
if (err != 0) {
if (inputSock != INVALID_SOCKET) {
closeSocket(inputSock);
inputSock = INVALID_SOCKET;
}
return err;
}
// Allow input packets to be queued now
initialized = true;
// GFE will not send haptics events without this magic packet first
sendEnableHaptics();
return err;
}
// Stops the input stream
int stopInputStream(void) {
// No more packets should be queued now
initialized = false;
LbqSignalQueueShutdown(&packetHolderFreeList);
// Signal the input send thread to drain all pending
// input packets before shutting down.
LbqSignalQueueDrain(&packetQueue);
PltJoinThread(&inputSendThread);
PltCloseThread(&inputSendThread);
if (inputSock != INVALID_SOCKET) {
shutdownTcpSocket(inputSock);
}
if (inputSock != INVALID_SOCKET) {
closeSocket(inputSock);
inputSock = INVALID_SOCKET;
}
return 0;
}
// Send a mouse move event to the streaming machine
int LiSendMouseMoveEvent(short deltaX, short deltaY) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
if (deltaX == 0 && deltaY == 0) {
return 0;
}
PltLockMutex(&batchedInputMutex);
// Combine the previous deltas with the new one
currentRelativeMouseState.deltaX += deltaX;
currentRelativeMouseState.deltaY += deltaY;
// Queue a packet holder if this is the only pending relative mouse event
if (!currentRelativeMouseState.dirty) {
holder = allocatePacketHolder(0);
if (holder == NULL) {
PltUnlockMutex(&batchedInputMutex);
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
// TODO: Send this as unreliable sequenced when we have a delayed reliable retransmission thread
// and protocol updates to allow us to determine which unreliable messages were dropped.
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.mouseMoveRel.header.size = BE32(sizeof(NV_REL_MOUSE_MOVE_PACKET) - sizeof(uint32_t));
if (AppVersionQuad[0] >= 5) {
holder->packet.mouseMoveRel.header.magic = LE32(MOUSE_MOVE_REL_MAGIC_GEN5);
}
else {
holder->packet.mouseMoveRel.header.magic = LE32(MOUSE_MOVE_REL_MAGIC);
}
// Remaining fields are set in the input thread based on the latest currentRelativeMouseState values
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err == LBQ_SUCCESS) {
currentRelativeMouseState.dirty = true;
}
else {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
}
}
else {
// There's already a packet holder queued to send this event
err = 0;
}
PltUnlockMutex(&batchedInputMutex);
return err;
}
// Send a mouse position update to the streaming machine
int LiSendMousePositionEvent(short x, short y, short referenceWidth, short referenceHeight) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
PltLockMutex(&batchedInputMutex);
// Overwrite the previous mouse location with the new one
currentAbsoluteMouseState.x = x;
currentAbsoluteMouseState.y = y;
currentAbsoluteMouseState.width = referenceWidth;
currentAbsoluteMouseState.height = referenceHeight;
// Queue a packet holder if this is the only pending absolute mouse event
if (!currentAbsoluteMouseState.dirty) {
holder = allocatePacketHolder(0);
if (holder == NULL) {
PltUnlockMutex(&batchedInputMutex);
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
// TODO: Send this as unreliable sequenced when we have a delayed reliable retransmission thread
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.mouseMoveAbs.header.size = BE32(sizeof(NV_ABS_MOUSE_MOVE_PACKET) - sizeof(uint32_t));
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
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err == LBQ_SUCCESS) {
currentAbsoluteMouseState.dirty = true;
}
else {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
}
}
else {
// There's already a packet holder queued to send this event
err = 0;
}
PltUnlockMutex(&batchedInputMutex);
// This is not thread safe, but it's not a big deal because callers that want to
// use LiSendRelativeMotionAsMousePositionEvent() must not mix these function
// without synchronization (otherwise the state of the cursor on the host is
// undefined anyway).
absCurrentPosX = CLAMP(x, 0, referenceWidth - 1) / (float)(referenceWidth - 1);
absCurrentPosY = CLAMP(y, 0, referenceHeight - 1) / (float)(referenceHeight - 1);
return err;
}
// Send a relative motion event using absolute position to the streaming machine
int LiSendMouseMoveAsMousePositionEvent(short deltaX, short deltaY, short referenceWidth, short referenceHeight) {
// Convert the current position to be relative to the provided reference dimensions
short oldPositionX = (short)(absCurrentPosX * referenceWidth);
short oldPositionY = (short)(absCurrentPosY * referenceHeight);
return LiSendMousePositionEvent(CLAMP(oldPositionX + deltaX, 0, referenceWidth),
CLAMP(oldPositionY + deltaY, 0, referenceHeight),
referenceWidth, referenceHeight);
}
// Send a mouse button event to the streaming machine
int LiSendMouseButtonEvent(char action, int button) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.mouseButton.header.size = BE32(sizeof(NV_MOUSE_BUTTON_PACKET) - sizeof(uint32_t));
holder->packet.mouseButton.header.magic = (uint8_t)action;
if (AppVersionQuad[0] >= 5) {
holder->packet.mouseButton.header.magic++;
}
holder->packet.mouseButton.header.magic = LE32(holder->packet.mouseButton.header.magic);
holder->packet.mouseButton.button = (uint8_t)button;
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);
}
return err;
}
// Send a key press event to the streaming machine
int LiSendKeyboardEvent2(short keyCode, char keyAction, char modifiers, char flags) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_KEYBOARD;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
// For proper behavior, the MODIFIER flag must not be set on the modifier key down event itself
// for the extended modifiers on the right side of the keyboard. If the MODIFIER flag is set,
// GFE will synthesize an errant key down event for the non-extended key, causing that key to be
// stuck down after the extended modifier key is raised. For non-extended keys, we must set the
// MODIFIER flag for correct behavior.
if (!IS_SUNSHINE()) {
switch (keyCode & 0xFF) {
case 0x5B: // VK_LWIN
case 0x5C: // VK_RWIN
// Any keyboard event with the META modifier flag is dropped by all known GFE versions.
// This prevents us from sending shortcuts involving the meta key (Win+X, Win+Tab, etc).
// The catch is that the meta key event itself would actually work if it didn't set its
// own modifier flag, so we'll clear that here. This should be safe even if a new GFE
// release comes out that stops dropping events with MODIFIER_META flag.
modifiers &= ~MODIFIER_META;
break;
case 0xA0: // VK_LSHIFT
modifiers |= MODIFIER_SHIFT;
break;
case 0xA1: // VK_RSHIFT
modifiers &= ~MODIFIER_SHIFT;
break;
case 0xA2: // VK_LCONTROL
modifiers |= MODIFIER_CTRL;
break;
case 0xA3: // VK_RCONTROL
modifiers &= ~MODIFIER_CTRL;
break;
case 0xA4: // VK_LMENU
modifiers |= MODIFIER_ALT;
break;
case 0xA5: // VK_RMENU
modifiers &= ~MODIFIER_ALT;
break;
default:
// No fixups
break;
}
}
holder->packet.keyboard.header.size = BE32(sizeof(NV_KEYBOARD_PACKET) - sizeof(uint32_t));
holder->packet.keyboard.header.magic = LE32((uint32_t)keyAction);
holder->packet.keyboard.flags = IS_SUNSHINE() ? flags : 0;
holder->packet.keyboard.keyCode = LE16(keyCode);
holder->packet.keyboard.modifiers = modifiers;
holder->packet.keyboard.zero2 = 0;
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);
}
return err;
}
int LiSendKeyboardEvent(short keyCode, char keyAction, char modifiers) {
return LiSendKeyboardEvent2(keyCode, keyAction, modifiers, 0);
}
int LiSendUtf8TextEvent(const char *text, unsigned int length) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
holder = allocatePacketHolder(length);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_UTF8;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
// Magic + string length
holder->packet.unicode.header.size = BE32(sizeof(uint32_t) + length);
holder->packet.unicode.header.magic = LE32(UTF8_TEXT_EVENT_MAGIC);
memcpy(holder->packet.unicode.text, text, length);
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);
}
return err;
}
static int sendControllerEventInternal(short controllerNumber, short activeGamepadMask,
int buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// HACK: We previously used a short for the buttonFlags argument, but we switched to an
// int to support additional buttons with Sunshine. Unfortunately, some clients still pass
// a short, which gets sign extended to an int. This causes all the new button flags to be
// set any time the user presses the Y button on their gamepad (since Y is 0x8000). To deal
// with these clients, we will detect this condition by checking if the sign bit is set.
// Since we know there's no valid button flag that uses the 31st bit, any case where the
// input value is negative is an instance of bug so only the botton 16 bits are valid.
if (buttonFlags < 0) {
buttonFlags &= 0xFFFF;
}
if (!IS_SUNSHINE()) {
// GFE only supports a maximum of 4 controllers
controllerNumber %= 4;
activeGamepadMask &= 0xF;
// GFE doesn't support buttons that aren't present on an Xbox 360 controller,
// so the extended button flags won't even be sent. For convenience, let's
// map the MISC button to the SPECIAL (Guide) button. Some platforms reserve
// the Guide button for OS functionality (Game Bar, Home button, etc.), so
// this allows otherwise unused buttons to activate that functionality.
if (buttonFlags & MISC_FLAG) {
buttonFlags |= SPECIAL_FLAG;
}
}
else {
// Sunshine supports up to 16 (max number of bits in activeGamepadMask)
controllerNumber %= MAX_GAMEPADS;
}
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
// TODO: Send this as unreliable sequenced when we have a delayed reliable retransmission thread
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
if (AppVersionQuad[0] == 3) {
// Generation 3 servers don't support multiple controllers so we send
// the legacy packet
holder->packet.controller.header.size = BE32(sizeof(NV_CONTROLLER_PACKET) - sizeof(uint32_t));
holder->packet.controller.header.magic = LE32(CONTROLLER_MAGIC);
holder->packet.controller.headerB = LE16(C_HEADER_B);
holder->packet.controller.buttonFlags = LE16(buttonFlags);
holder->packet.controller.leftTrigger = leftTrigger;
holder->packet.controller.rightTrigger = rightTrigger;
holder->packet.controller.leftStickX = LE16(leftStickX);
holder->packet.controller.leftStickY = LE16(leftStickY);
holder->packet.controller.rightStickX = LE16(rightStickX);
holder->packet.controller.rightStickY = LE16(rightStickY);
holder->packet.controller.tailA = LE32(C_TAIL_A);
holder->packet.controller.tailB = LE16(C_TAIL_B);
}
else {
// Generation 4+ servers support passing the controller number
holder->packet.multiController.header.size = BE32(sizeof(NV_MULTI_CONTROLLER_PACKET) - sizeof(uint32_t));
// On Gen 5 servers, the header code is decremented by one
if (AppVersionQuad[0] >= 5) {
holder->packet.multiController.header.magic = LE32(MULTI_CONTROLLER_MAGIC_GEN5);
}
else {
holder->packet.multiController.header.magic = LE32(MULTI_CONTROLLER_MAGIC);
}
holder->packet.multiController.headerB = LE16(MC_HEADER_B);
holder->packet.multiController.controllerNumber = LE16(controllerNumber);
holder->packet.multiController.activeGamepadMask = LE16(activeGamepadMask);
holder->packet.multiController.midB = LE16(MC_MID_B);
holder->packet.multiController.buttonFlags = LE16((short)buttonFlags);
holder->packet.multiController.leftTrigger = leftTrigger;
holder->packet.multiController.rightTrigger = rightTrigger;
holder->packet.multiController.leftStickX = LE16(leftStickX);
holder->packet.multiController.leftStickY = LE16(leftStickY);
holder->packet.multiController.rightStickX = LE16(rightStickX);
holder->packet.multiController.rightStickY = LE16(rightStickY);
holder->packet.multiController.tailA = LE16(MC_TAIL_A);
holder->packet.multiController.buttonFlags2 = IS_SUNSHINE() ? LE16((short)(buttonFlags >> 16)) : 0;
holder->packet.multiController.tailB = LE16(MC_TAIL_B);
}
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);
}
return err;
}
// Send a controller event to the streaming machine
int LiSendControllerEvent(int buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
return sendControllerEventInternal(0, 0x1, buttonFlags, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
// Send a controller event to the streaming machine
int LiSendMultiControllerEvent(short controllerNumber, short activeGamepadMask,
int buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger,
short leftStickX, short leftStickY, short rightStickX, short rightStickY)
{
return sendControllerEventInternal(controllerNumber, activeGamepadMask,
buttonFlags, leftTrigger, rightTrigger,
leftStickX, leftStickY, rightStickX, rightStickY);
}
// Send a high resolution scroll event to the streaming machine
int LiSendHighResScrollEvent(short scrollAmount) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
if (scrollAmount == 0) {
return 0;
}
// Newer version of GFE that use virtual HID devices have a bug that requires
// the scroll events to be batched to WHEEL_DELTA. Due to the their HID report
// descriptor, they don't actually support smooth scrolling. _Any_ scroll gets
// converted into a full WHEEL_DELTA scroll, even if the actual delta is tiny.
// Similarly, large scrolls are capped at +/- WHEEL_DELTA too so we'll need to
// split those up too.
if (needsBatchedScroll) {
if ((batchedScrollDelta < 0 && scrollAmount > 0) ||
(batchedScrollDelta > 0 && scrollAmount < 0)) {
// Reset the accumulated scroll delta when the direction changes
// FIXME: Maybe reset accumulated delta based on time too?
batchedScrollDelta = 0;
}
batchedScrollDelta += scrollAmount;
while (abs(batchedScrollDelta) >= LI_WHEEL_DELTA) {
scrollAmount = batchedScrollDelta > 0 ? LI_WHEEL_DELTA : -LI_WHEEL_DELTA;
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.scroll.header.size = BE32(sizeof(NV_SCROLL_PACKET) - sizeof(uint32_t));
if (AppVersionQuad[0] >= 5) {
holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC_GEN5);
}
else {
holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC);
}
holder->packet.scroll.scrollAmt1 = BE16(scrollAmount);
holder->packet.scroll.scrollAmt2 = holder->packet.scroll.scrollAmt1;
holder->packet.scroll.zero3 = 0;
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);
return err;
}
batchedScrollDelta -= scrollAmount;
}
err = 0;
}
else {
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.scroll.header.size = BE32(sizeof(NV_SCROLL_PACKET) - sizeof(uint32_t));
if (AppVersionQuad[0] >= 5) {
holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC_GEN5);
}
else {
holder->packet.scroll.header.magic = LE32(SCROLL_MAGIC);
}
holder->packet.scroll.scrollAmt1 = BE16(scrollAmount);
holder->packet.scroll.scrollAmt2 = holder->packet.scroll.scrollAmt1;
holder->packet.scroll.zero3 = 0;
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);
}
}
return err;
}
// Send a scroll event to the streaming machine
int LiSendScrollEvent(signed char scrollClicks) {
return LiSendHighResScrollEvent(scrollClicks * LI_WHEEL_DELTA);
}
// Send a high resolution horizontal scroll event
int LiSendHighResHScrollEvent(short scrollAmount) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// This is a protocol extension only supported with Sunshine
if (!IS_SUNSHINE()) {
return LI_ERR_UNSUPPORTED;
}
if (scrollAmount == 0) {
return 0;
}
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_MOUSE;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.hscroll.header.size = BE32(sizeof(SS_HSCROLL_PACKET) - sizeof(uint32_t));
holder->packet.hscroll.header.magic = LE32(SS_HSCROLL_MAGIC);
holder->packet.hscroll.scrollAmount = BE16(scrollAmount);
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);
}
return err;
}
int LiSendHScrollEvent(signed char scrollClicks) {
return LiSendHighResHScrollEvent(scrollClicks * LI_WHEEL_DELTA);
}
int LiSendTouchEvent(uint8_t eventType, uint32_t pointerId, float x, float y, float pressureOrDistance,
float contactAreaMajor, float contactAreaMinor, uint16_t rotation) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// This is a protocol extension only supported with Sunshine
if (!(SunshineFeatureFlags & LI_FF_PEN_TOUCH_EVENTS)) {
return LI_ERR_UNSUPPORTED;
}
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_TOUCH;
// Allow move and hover events to be dropped if a newer one arrives, but don't allow
// state changing events like up/down/leave events to be dropped.
holder->enetPacketFlags = TOUCH_EVENT_IS_BATCHABLE(eventType) ? 0 : ENET_PACKET_FLAG_RELIABLE;
holder->packet.touch.header.size = BE32(sizeof(SS_TOUCH_PACKET) - sizeof(uint32_t));
holder->packet.touch.header.magic = LE32(SS_TOUCH_MAGIC);
holder->packet.touch.eventType = eventType;
holder->packet.touch.pointerId = LE32(pointerId);
holder->packet.touch.rotation = LE16(rotation);
memset(holder->packet.touch.zero, 0, sizeof(holder->packet.touch.zero));
floatToNetfloat(x, holder->packet.touch.x);
floatToNetfloat(y, holder->packet.touch.y);
floatToNetfloat(pressureOrDistance, holder->packet.touch.pressureOrDistance);
floatToNetfloat(contactAreaMajor, holder->packet.touch.contactAreaMajor);
floatToNetfloat(contactAreaMinor, holder->packet.touch.contactAreaMinor);
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);
}
return err;
}
int LiSendPenEvent(uint8_t eventType, uint8_t toolType, uint8_t penButtons,
float x, float y, float pressureOrDistance,
float contactAreaMajor, float contactAreaMinor,
uint16_t rotation, uint8_t tilt) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// This is a protocol extension only supported with Sunshine
if (!(SunshineFeatureFlags & LI_FF_PEN_TOUCH_EVENTS)) {
return LI_ERR_UNSUPPORTED;
}
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
holder->channelId = CTRL_CHANNEL_PEN;
// Allow move and hover events to be dropped if a newer one arrives (if no buttons changed),
// but don't allow state changing events like up/down/leave events to be dropped.
holder->enetPacketFlags = (TOUCH_EVENT_IS_BATCHABLE(eventType) && !(penButtons ^ currentPenButtonState)) ? 0 : ENET_PACKET_FLAG_RELIABLE;
currentPenButtonState = penButtons;
holder->packet.pen.header.size = BE32(sizeof(SS_PEN_PACKET) - sizeof(uint32_t));
holder->packet.pen.header.magic = LE32(SS_PEN_MAGIC);
holder->packet.pen.eventType = eventType;
holder->packet.pen.toolType = toolType;
holder->packet.pen.penButtons = penButtons;
memset(holder->packet.pen.zero, 0, sizeof(holder->packet.pen.zero));
floatToNetfloat(x, holder->packet.pen.x);
floatToNetfloat(y, holder->packet.pen.y);
floatToNetfloat(pressureOrDistance, holder->packet.pen.pressureOrDistance);
holder->packet.pen.rotation = LE16(rotation);
holder->packet.pen.tilt = tilt;
memset(holder->packet.pen.zero2, 0, sizeof(holder->packet.pen.zero2));
floatToNetfloat(contactAreaMajor, holder->packet.pen.contactAreaMajor);
floatToNetfloat(contactAreaMinor, holder->packet.pen.contactAreaMinor);
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);
}
return err;
}
int LiSendControllerArrivalEvent(uint8_t controllerNumber, uint16_t activeGamepadMask, uint8_t type,
uint32_t supportedButtonFlags, uint16_t capabilities) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// Sunshine supports up to 16 controllers
controllerNumber %= MAX_GAMEPADS;
// The arrival event is only supported by Sunshine
if (IS_SUNSHINE()) {
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.controllerArrival.header.size = BE32(sizeof(SS_CONTROLLER_ARRIVAL_PACKET) - sizeof(uint32_t));
holder->packet.controllerArrival.header.magic = LE32(SS_CONTROLLER_ARRIVAL_MAGIC);
holder->packet.controllerArrival.controllerNumber = controllerNumber;
holder->packet.controllerArrival.type = type;
holder->packet.controllerArrival.capabilities = LE16(capabilities);
holder->packet.controllerArrival.supportedButtonFlags = LE32(supportedButtonFlags);
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);
return err;
}
}
// Send a MC event just in case the host software doesn't support arrival events.
return LiSendMultiControllerEvent(controllerNumber, activeGamepadMask, 0, 0, 0, 0, 0, 0, 0);
}
int LiSendControllerTouchEvent(uint8_t controllerNumber, uint8_t eventType, uint32_t pointerId, float x, float y, float pressure) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// This is a protocol extension only supported with Sunshine
if (!(SunshineFeatureFlags & LI_FF_CONTROLLER_TOUCH_EVENTS)) {
return LI_ERR_UNSUPPORTED;
}
// Sunshine supports up to 16 controllers
controllerNumber %= MAX_GAMEPADS;
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
// Allow move and hover events to be dropped if a newer one arrives, but don't allow
// state changing events like up/down/leave events to be dropped.
holder->enetPacketFlags = TOUCH_EVENT_IS_BATCHABLE(eventType) ? 0 : ENET_PACKET_FLAG_RELIABLE;
holder->packet.controllerTouch.header.size = BE32(sizeof(SS_CONTROLLER_TOUCH_PACKET) - sizeof(uint32_t));
holder->packet.controllerTouch.header.magic = LE32(SS_CONTROLLER_TOUCH_MAGIC);
holder->packet.controllerTouch.controllerNumber = controllerNumber;
holder->packet.controllerTouch.eventType = eventType;
memset(holder->packet.controllerTouch.zero, 0, sizeof(holder->packet.controllerTouch.zero));
holder->packet.controllerTouch.pointerId = LE32(pointerId);
floatToNetfloat(x, holder->packet.controllerTouch.x);
floatToNetfloat(y, holder->packet.controllerTouch.y);
floatToNetfloat(pressure, holder->packet.controllerTouch.pressure);
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);
}
return err;
}
int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, float x, float y, float z) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// Check for valid motion type values
if (motionType - 1 >= MAX_MOTION_EVENTS) {
LC_ASSERT(motionType - 1 < MAX_MOTION_EVENTS);
return -3;
}
// This is a protocol extension only supported with Sunshine
if (!(SunshineFeatureFlags & LI_FF_CONTROLLER_TOUCH_EVENTS)) {
return LI_ERR_UNSUPPORTED;
}
// Sunshine supports up to 16 controllers
controllerNumber %= MAX_GAMEPADS;
PltLockMutex(&batchedInputMutex);
currentGamepadSensorState[controllerNumber][motionType - 1].x = x;
currentGamepadSensorState[controllerNumber][motionType - 1].y = y;
currentGamepadSensorState[controllerNumber][motionType - 1].z = z;
// Queue a packet holder if this is the only pending sensor event
if (!currentGamepadSensorState[controllerNumber][motionType - 1].dirty) {
holder = allocatePacketHolder(0);
if (holder == NULL) {
PltUnlockMutex(&batchedInputMutex);
return -1;
}
// Send each controller on a separate channel specific to motion sensors
holder->channelId = CTRL_CHANNEL_SENSOR_BASE + controllerNumber;
holder->packet.controllerMotion.header.size = BE32(sizeof(SS_CONTROLLER_MOTION_PACKET) - sizeof(uint32_t));
holder->packet.controllerMotion.header.magic = LE32(SS_CONTROLLER_MOTION_MAGIC);
holder->packet.controllerMotion.controllerNumber = controllerNumber;
holder->packet.controllerMotion.motionType = motionType;
memset(holder->packet.controllerMotion.zero, 0, sizeof(holder->packet.controllerMotion.zero));
// Remaining fields are set in the input thread based on the latest currentGamepadSensorState values
err = LbqOfferQueueItem(&packetQueue, holder, &holder->entry);
if (err == LBQ_SUCCESS) {
currentGamepadSensorState[controllerNumber][motionType - 1].dirty = true;
}
else {
LC_ASSERT(err == LBQ_BOUND_EXCEEDED);
Limelog("Input queue reached maximum size limit\n");
freePacketHolder(holder);
}
}
else {
// There's already a packet holder queued to send this event
err = 0;
}
PltUnlockMutex(&batchedInputMutex);
return err;
}
int LiSendControllerBatteryEvent(uint8_t controllerNumber, uint8_t batteryState, uint8_t batteryPercentage) {
PPACKET_HOLDER holder;
int err;
if (!initialized) {
return -2;
}
// This is a protocol extension only supported with Sunshine
if (!IS_SUNSHINE()) {
return LI_ERR_UNSUPPORTED;
}
// Sunshine supports up to 16 controllers
controllerNumber %= MAX_GAMEPADS;
holder = allocatePacketHolder(0);
if (holder == NULL) {
return -1;
}
// Send each controller on a separate channel
holder->channelId = CTRL_CHANNEL_GAMEPAD_BASE + controllerNumber;
holder->enetPacketFlags = ENET_PACKET_FLAG_RELIABLE;
holder->packet.controllerBattery.header.size = BE32(sizeof(SS_CONTROLLER_BATTERY_PACKET) - sizeof(uint32_t));
holder->packet.controllerBattery.header.magic = LE32(SS_CONTROLLER_BATTERY_MAGIC);
holder->packet.controllerBattery.controllerNumber = controllerNumber;
holder->packet.controllerBattery.batteryState = batteryState;
holder->packet.controllerBattery.batteryPercentage = batteryPercentage;
memset(holder->packet.controllerBattery.zero, 0, sizeof(holder->packet.controllerBattery.zero));
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);
}
return err;
}