From 611a2e7f8f6583d6d6aad30f0c8a02d6c07ab085 Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Thu, 12 Feb 2026 00:37:54 -0700 Subject: [PATCH] Add session input policy control stream support --- src/ControlStream.c | 56 ++++++++++++++++++++++++++++++++++++++++++++- src/FakeCallbacks.c | 5 ++++ src/Limelight.h | 9 ++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/ControlStream.c b/src/ControlStream.c index 2b5182e..fd8e7a7 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -83,6 +83,12 @@ typedef struct _QUEUED_ASYNC_CALLBACK { uint8_t left[DS_EFFECT_PAYLOAD_SIZE]; uint8_t right[DS_EFFECT_PAYLOAD_SIZE]; } dsAdaptiveTrigger; + struct { + uint8_t allowKeyboard; + uint8_t allowMouse; + uint8_t allowGamepad; + uint8_t reason; + } setInputPolicy; } data; LINKED_BLOCKING_QUEUE_ENTRY entry; } QUEUED_ASYNC_CALLBACK, *PQUEUED_ASYNC_CALLBACK; @@ -140,6 +146,7 @@ static PPLT_CRYPTO_CONTEXT decryptionCtx; #define IDX_SET_MOTION_EVENT 10 #define IDX_SET_RGB_LED 11 #define IDX_DS_ADAPTIVE_TRIGGERS 12 +#define IDX_SET_INPUT_POLICY 13 #define CONTROL_STREAM_TIMEOUT_SEC 10 #define CONTROL_STREAM_LINGER_TIMEOUT_SEC 2 @@ -157,6 +164,7 @@ static const short packetTypesGen3[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen4[] = { 0x0606, // Request IDR frame @@ -171,6 +179,7 @@ static const short packetTypesGen4[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen5[] = { 0x0305, // Start A @@ -185,6 +194,7 @@ static const short packetTypesGen5[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen7[] = { 0x0305, // Start A @@ -199,6 +209,7 @@ static const short packetTypesGen7[] = { -1, // Rumble triggers (unused) -1, // Set motion event (unused) -1, // Set RGB LED (unused) + -1, }; static const short packetTypesGen7Enc[] = { 0x0302, // Request IDR frame @@ -214,6 +225,7 @@ static const short packetTypesGen7Enc[] = { 0x5501, // Set motion event (Sunshine protocol extension) 0x5502, // Set RGB LED (Sunshine protocol extension) 0x5503, // Set Adaptive Triggers (Sunshine protocol extension) + 0x5504, }; static const char requestIdrFrameGen3[] = { 0, 0 }; @@ -1010,6 +1022,12 @@ static void asyncCallbackThreadFunc(void* context) { queuedCb->data.dsAdaptiveTrigger.left, queuedCb->data.dsAdaptiveTrigger.right); break; + case IDX_SET_INPUT_POLICY: + ListenerCallbacks.setInputPolicy(queuedCb->data.setInputPolicy.allowKeyboard, + queuedCb->data.setInputPolicy.allowMouse, + queuedCb->data.setInputPolicy.allowGamepad, + queuedCb->data.setInputPolicy.reason); + break; default: // Unhandled packet type from queueAsyncCallback() LC_ASSERT(false); @@ -1026,7 +1044,8 @@ static bool needsAsyncCallback(unsigned short packetType) { packetType == packetTypes[IDX_SET_MOTION_EVENT] || packetType == packetTypes[IDX_SET_RGB_LED] || packetType == packetTypes[IDX_HDR_INFO] || - packetType == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS]; + packetType == packetTypes[IDX_DS_ADAPTIVE_TRIGGERS] || + packetType == packetTypes[IDX_SET_INPUT_POLICY]; } static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLength) { @@ -1087,6 +1106,13 @@ static void queueAsyncCallback(PNVCTL_ENET_PACKET_HEADER_V1 ctlHdr, int packetLe BbGetBytes(&bb, queuedCb->data.dsAdaptiveTrigger.right, DS_EFFECT_PAYLOAD_SIZE); queuedCb->typeIndex = IDX_DS_ADAPTIVE_TRIGGERS; } + else if (ctlHdr->type == packetTypes[IDX_SET_INPUT_POLICY]) { + BbGet8(&bb, &queuedCb->data.setInputPolicy.allowKeyboard); + BbGet8(&bb, &queuedCb->data.setInputPolicy.allowMouse); + BbGet8(&bb, &queuedCb->data.setInputPolicy.allowGamepad); + BbGet8(&bb, &queuedCb->data.setInputPolicy.reason); + queuedCb->typeIndex = IDX_SET_INPUT_POLICY; + } else { // Unhandled packet type from needsAsyncCallback() LC_ASSERT(false); @@ -1701,6 +1727,34 @@ int sendInputPacketOnControlStream(unsigned char* data, int length, uint8_t chan return 0; } +int LiSendSessionInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason) { + struct { + uint8_t allowKeyboard; + uint8_t allowMouse; + uint8_t allowGamepad; + uint8_t reason; + } payload; + + if (!IS_SUNSHINE() || AppVersionQuad[0] < 7) { + return LI_ERR_UNSUPPORTED; + } + + if (client == NULL || peer == NULL || stopping) { + return -2; + } + + payload.allowKeyboard = allowKeyboard ? 1 : 0; + payload.allowMouse = allowMouse ? 1 : 0; + payload.allowGamepad = allowGamepad ? 1 : 0; + payload.reason = reason; + + if (sendMessageAndForget(0x5504, sizeof(payload), &payload, CTRL_CHANNEL_GENERIC, ENET_PACKET_FLAG_RELIABLE, false) == 0) { + return -1; + } + + return 0; +} + // Called by the input stream to flush queued packets before a batching wait void flushInputOnControlStream(void) { if (AppVersionQuad[0] >= 5) { diff --git a/src/FakeCallbacks.c b/src/FakeCallbacks.c index 29fdf18..50095a0 100644 --- a/src/FakeCallbacks.c +++ b/src/FakeCallbacks.c @@ -41,6 +41,7 @@ static void fakeClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTrigger static void fakeClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz) {} static void fakeClSetAdaptiveTriggers(uint16_t controllerNumber, uint8_t eventFlags, uint8_t typeLeft, uint8_t typeRight, uint8_t *left, uint8_t *right) {}; static void fakeClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b) {} +static void fakeClSetInputPolicy(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason) {} static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .stageStarting = fakeClStageStarting, @@ -56,6 +57,7 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .setMotionEventState = fakeClSetMotionEventState, .setControllerLED = fakeClSetControllerLED, .setAdaptiveTriggers = fakeClSetAdaptiveTriggers, + .setInputPolicy = fakeClSetInputPolicy, }; void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks, @@ -146,5 +148,8 @@ void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_REND if ((*clCallbacks)->setAdaptiveTriggers == NULL) { (*clCallbacks)->setAdaptiveTriggers = fakeClSetAdaptiveTriggers; } + if ((*clCallbacks)->setInputPolicy == NULL) { + (*clCallbacks)->setInputPolicy = fakeClSetInputPolicy; + } } } diff --git a/src/Limelight.h b/src/Limelight.h index ac87a4b..ba4e5c5 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -483,6 +483,8 @@ typedef void(*ConnListenerSetAdaptiveTriggers)(uint16_t controllerNumber, uint8_ // This callback is invoked to set a controller's RGB LED (if present). typedef void(*ConnListenerSetControllerLED)(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b); +typedef void(*ConnListenerSetInputPolicy)(uint8_t allowKeyboard, uint8_t allowMouse, uint8_t allowGamepad, uint8_t reason); + typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerStageStarting stageStarting; ConnListenerStageComplete stageComplete; @@ -497,6 +499,7 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerSetMotionEventState setMotionEventState; ConnListenerSetControllerLED setControllerLED; ConnListenerSetAdaptiveTriggers setAdaptiveTriggers; + ConnListenerSetInputPolicy setInputPolicy; } CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS; // Use this function to zero the connection callbacks when allocated on the stack or heap @@ -837,6 +840,12 @@ int LiSendHighResScrollEvent(short scrollAmount); int LiSendHScrollEvent(signed char scrollClicks); int LiSendHighResHScrollEvent(short scrollAmount); +#define LI_SESSION_INPUT_POLICY_REASON_STREAM_START 0x00 +#define LI_SESSION_INPUT_POLICY_REASON_USER_TOGGLE 0x01 +#define LI_SESSION_INPUT_POLICY_REASON_HOST_ACK 0x02 +#define LI_SESSION_INPUT_POLICY_REASON_HOST_OVERRIDE 0x03 +int LiSendSessionInputPolicy(bool allowKeyboard, bool allowMouse, bool allowGamepad, uint8_t reason); + // This function returns a time in microseconds with an implementation-defined epoch. // It should only ever be compared with the return value from a previous call to itself. uint64_t LiGetMicroseconds(void);