From a0f8c060c0b2d9fa306ff924a050a0222d3d4cd7 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Sun, 2 Jul 2023 14:38:54 -0500 Subject: [PATCH] Introduce new protocol extensions for controller RGB LEDs and battery state --- src/ControlStream.c | 21 +++++++++++++++++++++ src/FakeCallbacks.c | 5 +++++ src/Input.h | 9 +++++++++ src/InputStream.c | 36 ++++++++++++++++++++++++++++++++++++ src/Limelight.h | 29 +++++++++++++++++++++++------ 5 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/ControlStream.c b/src/ControlStream.c index 93c26ca..bd05b53 100644 --- a/src/ControlStream.c +++ b/src/ControlStream.c @@ -87,6 +87,7 @@ static PPLT_CRYPTO_CONTEXT decryptionCtx; #define IDX_HDR_INFO 8 #define IDX_RUMBLE_TRIGGER_DATA 9 #define IDX_SET_MOTION_EVENT 10 +#define IDX_SET_RGB_LED 11 #define CONTROL_STREAM_TIMEOUT_SEC 10 #define CONTROL_STREAM_LINGER_TIMEOUT_SEC 2 @@ -103,6 +104,7 @@ static const short packetTypesGen3[] = { -1, // HDR mode (unused) -1, // Rumble triggers (unused) -1, // Set motion event (unused) + -1, // Set RGB LED (unused) }; static const short packetTypesGen4[] = { 0x0606, // Request IDR frame @@ -116,6 +118,7 @@ static const short packetTypesGen4[] = { -1, // HDR mode (unused) -1, // Rumble triggers (unused) -1, // Set motion event (unused) + -1, // Set RGB LED (unused) }; static const short packetTypesGen5[] = { 0x0305, // Start A @@ -129,6 +132,7 @@ static const short packetTypesGen5[] = { -1, // HDR mode (unknown) -1, // Rumble triggers (unused) -1, // Set motion event (unused) + -1, // Set RGB LED (unused) }; static const short packetTypesGen7[] = { 0x0305, // Start A @@ -142,6 +146,7 @@ static const short packetTypesGen7[] = { 0x010e, // HDR mode -1, // Rumble triggers (unused) -1, // Set motion event (unused) + -1, // Set RGB LED (unused) }; static const short packetTypesGen7Enc[] = { 0x0302, // Request IDR frame @@ -155,6 +160,7 @@ static const short packetTypesGen7Enc[] = { 0x010e, // HDR mode 0x5500, // Rumble triggers (Sunshine protocol extension) 0x5501, // Set motion event (Sunshine protocol extension) + 0x5502, // Set RGB LED (Sunshine protocol extension) }; static const char requestIdrFrameGen3[] = { 0, 0 }; @@ -941,6 +947,21 @@ static void controlReceiveThreadFunc(void* context) { ListenerCallbacks.setMotionEventState(controllerNumber, motionType, reportRateHz); } + else if (ctlHdr->type == packetTypes[IDX_SET_RGB_LED]) { + BYTE_BUFFER bb; + + BbInitializeWrappedBuffer(&bb, (char*)ctlHdr, sizeof(*ctlHdr), packetLength - sizeof(*ctlHdr), BYTE_ORDER_LITTLE); + + uint16_t controllerNumber; + uint8_t r, g, b; + + BbGet16(&bb, &controllerNumber); + BbGet8(&bb, &r); + BbGet8(&bb, &g); + BbGet8(&bb, &b); + + ListenerCallbacks.setControllerLED(controllerNumber, r, g, b); + } else if (ctlHdr->type == packetTypes[IDX_HDR_INFO]) { BYTE_BUFFER bb; uint8_t enableByte; diff --git a/src/FakeCallbacks.c b/src/FakeCallbacks.c index 7b437ae..62260b8 100644 --- a/src/FakeCallbacks.c +++ b/src/FakeCallbacks.c @@ -39,6 +39,7 @@ static void fakeClConnectionStatusUpdate(int connectionStatus) {} static void fakeClSetHdrMode(bool enabled) {} static void fakeClRumbleTriggers(uint16_t controllerNumber, uint16_t leftTriggerMotor, uint16_t rightTriggerMotor) {} static void fakeClSetMotionEventState(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz) {} +static void fakeClSetControllerLED(uint16_t controllerNumber, uint8_t r, uint8_t g, uint8_t b) {} static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .stageStarting = fakeClStageStarting, @@ -52,6 +53,7 @@ static CONNECTION_LISTENER_CALLBACKS fakeClCallbacks = { .setHdrMode = fakeClSetHdrMode, .rumbleTriggers = fakeClRumbleTriggers, .setMotionEventState = fakeClSetMotionEventState, + .setControllerLED = fakeClSetControllerLED, }; void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_RENDERER_CALLBACKS* arCallbacks, @@ -136,5 +138,8 @@ void fixupMissingCallbacks(PDECODER_RENDERER_CALLBACKS* drCallbacks, PAUDIO_REND if ((*clCallbacks)->setMotionEventState == NULL) { (*clCallbacks)->setMotionEventState = fakeClSetMotionEventState; } + if ((*clCallbacks)->setControllerLED == NULL) { + (*clCallbacks)->setControllerLED = fakeClSetControllerLED; + } } } diff --git a/src/Input.h b/src/Input.h index baa79a3..cacd411 100644 --- a/src/Input.h +++ b/src/Input.h @@ -181,4 +181,13 @@ typedef struct _SS_CONTROLLER_MOTION_PACKET { netfloat z; } SS_CONTROLLER_MOTION_PACKET, *PSS_CONTROLLER_MOTION_PACKET; +#define SS_CONTROLLER_BATTERY_MAGIC 0x55000007 +typedef struct _SS_CONTROLLER_BATTERY_PACKET { + NV_INPUT_HEADER header; + uint8_t controllerNumber; + uint8_t batteryState; + uint8_t batteryPercentage; + uint8_t zero[1]; // Alignment/reserved +} SS_CONTROLLER_BATTERY_PACKET, *PSS_CONTROLLER_BATTERY_PACKET; + #pragma pack(pop) diff --git a/src/InputStream.c b/src/InputStream.c index 5ca0aa4..c751c68 100644 --- a/src/InputStream.c +++ b/src/InputStream.c @@ -63,6 +63,7 @@ typedef struct _PACKET_HOLDER { 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; @@ -1344,3 +1345,38 @@ int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, fl 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; + } + + holder = allocatePacketHolder(0); + if (holder == NULL) { + return -1; + } + + 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; +} diff --git a/src/Limelight.h b/src/Limelight.h index fff727b..0bbe20f 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -465,6 +465,9 @@ typedef void(*ConnListenerRumbleTriggers)(uint16_t controllerNumber, uint16_t le // If reportRateHz is 0, the host is asking for motion event reporting to stop. typedef void(*ConnListenerSetMotionEventState)(uint16_t controllerNumber, uint8_t motionType, uint16_t reportRateHz); +// 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 struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerStageStarting stageStarting; ConnListenerStageComplete stageComplete; @@ -477,6 +480,7 @@ typedef struct _CONNECTION_LISTENER_CALLBACKS { ConnListenerSetHdrMode setHdrMode; ConnListenerRumbleTriggers rumbleTriggers; ConnListenerSetMotionEventState setMotionEventState; + ConnListenerSetControllerLED setControllerLED; } CONNECTION_LISTENER_CALLBACKS, *PCONNECTION_LISTENER_CALLBACKS; // Use this function to zero the connection callbacks when allocated on the stack or heap @@ -692,12 +696,14 @@ int LiSendMultiControllerEvent(short controllerNumber, short activeGamepadMask, #define LI_CTYPE_XBOX 0x01 #define LI_CTYPE_PS 0x02 #define LI_CTYPE_NINTENDO 0x03 -#define LI_CCAP_ANALOG_TRIGGERS 0x01 -#define LI_CCAP_RUMBLE 0x02 -#define LI_CCAP_TRIGGER_RUMBLE 0x04 -#define LI_CCAP_TOUCHPAD 0x08 -#define LI_CCAP_ACCEL 0x10 -#define LI_CCAP_GYRO 0x20 +#define LI_CCAP_ANALOG_TRIGGERS 0x01 // Reports values between 0x00 and 0xFF for trigger axes +#define LI_CCAP_RUMBLE 0x02 // Can rumble in response to ConnListenerRumble() callback +#define LI_CCAP_TRIGGER_RUMBLE 0x04 // Can rumble triggers in response to ConnListenerRumbleTriggers() callback +#define LI_CCAP_TOUCHPAD 0x08 // Reports touchpad events via LiSendControllerTouchEvent() +#define LI_CCAP_ACCEL 0x10 // Can report accelerometer events via LiSendControllerMotionEvent() +#define LI_CCAP_GYRO 0x20 // Can report gyroscope events via LiSendControllerMotionEvent() +#define LI_CCAP_BATTERY_STATE 0x40 // Reports battery state via LiSendControllerBatteryEvent() +#define LI_CCAP_RGB_LED 0x80 // Can set RGB LED state via ConnListenerSetControllerLED() int LiSendControllerArrivalEvent(uint8_t controllerNumber, uint16_t activeGamepadMask, uint8_t type, uint32_t supportedButtonFlags, uint16_t capabilities); @@ -719,6 +725,17 @@ int LiSendControllerTouchEvent(uint8_t controllerNumber, uint8_t eventType, uint #define LI_MOTION_TYPE_GYRO 0x02 int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, float x, float y, float z); +// This function allows clients to send controller battery state to a supported host. If the +// host can adjust battery state on the emulated controller, it can use this information to +// make the virtual controller match the physical controller on the client. +#define LI_BATTERY_STATE_UNKNOWN 0x00 +#define LI_BATTERY_STATE_NOT_PRESENT 0x01 +#define LI_BATTERY_STATE_DISCHARGING 0x02 +#define LI_BATTERY_STATE_CHARGING 0x03 +#define LI_BATTERY_STATE_NOT_CHARGING 0x04 // Connected to power but not charging +#define LI_BATTERY_STATE_FULL 0x05 +int LiSendControllerBatteryEvent(uint8_t controllerNumber, uint8_t batteryState, uint8_t batteryPercentage); + // This function queues a vertical scroll event to the remote server. // The number of "clicks" is multiplied by WHEEL_DELTA (120) before // being sent to the PC.