diff --git a/src/Connection.c b/src/Connection.c index 3034185..2a37b8e 100644 --- a/src/Connection.c +++ b/src/Connection.c @@ -31,6 +31,7 @@ uint16_t AudioPortNumber; uint16_t VideoPortNumber; SS_PING AudioPingPayload; SS_PING VideoPingPayload; +uint16_t SunshineFeatureFlags; // Connection stages static const char* stageNames[STAGE_MAX] = { diff --git a/src/Input.h b/src/Input.h index 6e286f7..f6041b4 100644 --- a/src/Input.h +++ b/src/Input.h @@ -1,7 +1,13 @@ #pragma once +#include + #pragma pack(push, 1) +// netfloat is just a little-endian float in byte form +// for network transmission. +typedef uint8_t netfloat[4]; + typedef struct _NV_INPUT_HEADER { uint32_t size; // Size of packet (excluding this field) - Big Endian uint32_t magic; // Packet type - Little Endian @@ -116,4 +122,62 @@ typedef struct _SS_HSCROLL_PACKET { short scrollAmount; } SS_HSCROLL_PACKET, *PSS_HSCROLL_PACKET; +#define SS_TOUCH_MAGIC 0x55000002 +typedef struct _SS_TOUCH_PACKET { + NV_INPUT_HEADER header; + uint8_t eventType; + uint8_t touchIndex; + uint8_t zero[2]; // Alignment/reserved + netfloat x; + netfloat y; + netfloat pressure; +} SS_TOUCH_PACKET, *PSS_TOUCH_PACKET; + +#define SS_PEN_MAGIC 0x55000003 +typedef struct _SS_PEN_PACKET { + NV_INPUT_HEADER header; + uint8_t eventType; + uint8_t toolType; + uint8_t penButtons; + uint8_t zero[1]; // Alignment/reserved + netfloat x; + netfloat y; + netfloat pressure; + uint16_t rotation; + uint8_t tiltX; + uint8_t tiltY; +} SS_PEN_PACKET, *PSS_PEN_PACKET; + +#define SS_CONTROLLER_ARRIVAL_MAGIC 0x55000004 +typedef struct _SS_CONTROLLER_ARRIVAL_PACKET { + NV_INPUT_HEADER header; + uint8_t type; + uint8_t zero[1]; // Alignment/reserved + uint16_t capabilities; + uint32_t supportedButtonFlags; +} SS_CONTROLLER_ARRIVAL_PACKET, *PSS_CONTROLLER_ARRIVAL_PACKET; + +#define SS_CONTROLLER_TOUCH_MAGIC 0x55000005 +typedef struct _SS_CONTROLLER_TOUCH_PACKET { + NV_INPUT_HEADER header; + uint8_t controllerNumber; + uint8_t eventType; + uint8_t touchIndex; + uint8_t zero[1]; // Alignment/reserved + netfloat x; + netfloat y; + netfloat pressure; +} SS_CONTROLLER_TOUCH_PACKET, *PSS_CONTROLLER_TOUCH_PACKET; + +#define SS_CONTROLLER_MOTION_MAGIC 0x55000006 +typedef struct _SS_CONTROLLER_MOTION_PACKET { + NV_INPUT_HEADER header; + uint8_t controllerNumber; + uint8_t motionType; + uint8_t zero[2]; // Alignment/reserved + netfloat x; + netfloat y; + netfloat z; +} SS_CONTROLLER_MOTION_PACKET, *PSS_CONTROLLER_MOTION_PACKET; + #pragma pack(pop) diff --git a/src/InputStream.c b/src/InputStream.c index 1f59d5f..2c97fb8 100644 --- a/src/InputStream.c +++ b/src/InputStream.c @@ -53,6 +53,11 @@ typedef struct _PACKET_HOLDER { 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; NV_UNICODE_PACKET unicode; } packet; } PACKET_HOLDER, *PPACKET_HOLDER; @@ -586,6 +591,19 @@ int stopInputStream(void) { return 0; } +void floatToNetfloat(float in, netfloat out) { + if (IS_LITTLE_ENDIAN()) { + memcpy(out, &in, sizeof(in)); + } + else { + uint8_t* inb = (uint8_t*)∈ + out[0] = inb[3]; + out[1] = inb[2]; + out[2] = inb[1]; + out[3] = inb[0]; + } +} + // Send a mouse move event to the streaming machine int LiSendMouseMoveEvent(short deltaX, short deltaY) { PPACKET_HOLDER holder; @@ -1008,7 +1026,7 @@ int LiSendHighResHScrollEvent(short scrollAmount) { // This is a protocol extension only supported with Sunshine if (!IS_SUNSHINE()) { - return -3; + return LI_ERR_UNSUPPORTED; } if (scrollAmount == 0) { @@ -1037,3 +1055,193 @@ int LiSendHighResHScrollEvent(short scrollAmount) { int LiSendHScrollEvent(signed char scrollClicks) { return LiSendHighResHScrollEvent(scrollClicks * LI_WHEEL_DELTA); } + +int LiSendTouchEvent(uint8_t eventType, uint8_t touchIndex, 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 & SS_FF_PEN_TOUCH_EVENTS)) { + return LI_ERR_UNSUPPORTED; + } + + holder = allocatePacketHolder(0); + if (holder == NULL) { + return -1; + } + + 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.touchIndex = touchIndex; + memset(holder->packet.touch.zero, 0, sizeof(holder->packet.touch.zero)); + floatToNetfloat(x, holder->packet.touch.x); + floatToNetfloat(y, holder->packet.touch.y); + floatToNetfloat(pressure, holder->packet.touch.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 LiSendPenEvent(uint8_t eventType, uint8_t toolType, uint8_t penButtons, + float x, float y, float pressure, + uint16_t rotation, uint8_t tiltX, uint8_t tiltY) { + PPACKET_HOLDER holder; + int err; + + if (!initialized) { + return -2; + } + + // This is a protocol extension only supported with Sunshine + if (!(SunshineFeatureFlags & SS_FF_PEN_TOUCH_EVENTS)) { + return LI_ERR_UNSUPPORTED; + } + + holder = allocatePacketHolder(0); + if (holder == NULL) { + return -1; + } + + 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.touch.x); + floatToNetfloat(y, holder->packet.touch.y); + floatToNetfloat(pressure, holder->packet.touch.pressure); + holder->packet.pen.rotation = rotation; + holder->packet.pen.tiltX = tiltX; + holder->packet.pen.tiltY = tiltY; + + 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; + } + + // The arrival event is only supported by Sunshine + if (IS_SUNSHINE()) { + holder = allocatePacketHolder(0); + if (holder == NULL) { + return -1; + } + + 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.type = type; + memset(holder->packet.controllerArrival.zero, 0, sizeof(holder->packet.controllerArrival.zero)); + holder->packet.controllerArrival.capabilities = capabilities; + holder->packet.controllerArrival.supportedButtonFlags = 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, uint8_t touchIndex, 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 & SS_FF_CONTROLLER_TOUCH_EVENTS)) { + return LI_ERR_UNSUPPORTED; + } + + holder = allocatePacketHolder(0); + if (holder == NULL) { + return -1; + } + + 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; + holder->packet.controllerTouch.touchIndex = touchIndex; + memset(holder->packet.controllerTouch.zero, 0, sizeof(holder->packet.controllerTouch.zero)); + 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; + } + + // This is a protocol extension only supported with Sunshine + if (!(SunshineFeatureFlags & SS_FF_CONTROLLER_TOUCH_EVENTS)) { + return LI_ERR_UNSUPPORTED; + } + + holder = allocatePacketHolder(0); + if (holder == NULL) { + return -1; + } + + 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; + memset(holder->packet.controllerMotion.zero, 0, sizeof(holder->packet.controllerMotion.zero)); + floatToNetfloat(x, holder->packet.controllerMotion.x); + floatToNetfloat(y, holder->packet.controllerMotion.y); + floatToNetfloat(z, holder->packet.controllerMotion.z); + + 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-internal.h b/src/Limelight-internal.h index cd3f63b..823ed47 100644 --- a/src/Limelight-internal.h +++ b/src/Limelight-internal.h @@ -41,6 +41,10 @@ extern uint16_t VideoPortNumber; extern SS_PING AudioPingPayload; extern SS_PING VideoPingPayload; +#define SS_FF_PEN_TOUCH_EVENTS 0x01 +#define SS_FF_CONTROLLER_TOUCH_EVENTS 0x02 +extern uint16_t SunshineFeatureFlags; + #ifndef UINT24_MAX #define UINT24_MAX 0xFFFFFF #endif diff --git a/src/Limelight.h b/src/Limelight.h index 0daf70b..19de334 100644 --- a/src/Limelight.h +++ b/src/Limelight.h @@ -553,6 +553,38 @@ int LiSendMousePositionEvent(short x, short y, short referenceWidth, short refer // Using this function avoids double-acceleration in cases when the client motion is also accelerated. int LiSendMouseMoveAsMousePositionEvent(short deltaX, short deltaY, short referenceWidth, short referenceHeight); +// Error return value to indicate that the requested functionality is not supported by the host +#define LI_ERR_UNSUPPORTED -5501 + +// This function allows multi-touch input to be sent directly to Sunshine hosts. The x and y values +// are normalized device coordinates stretching top-left corner (0.0, 0.0) to bottom-right corner +// (1.0, 1.0) of the video area. Pressure is a 0.0 to 1.0 range value from min to max pressure. +// +// Sending a down/move event with a pressure of 0.0 indicates the actual pressure is unknown. +// +// If unsupported by the host, this will return LI_ERR_UNSUPPORTED and the caller should consider +// falling back to other functions to send this input (such as LiSendMousePositionEvent()). +#define LI_TOUCH_EVENT_HOVER 0x00 +#define LI_TOUCH_EVENT_DOWN 0x01 +#define LI_TOUCH_EVENT_UP 0x02 +#define LI_TOUCH_EVENT_MOVE 0x03 +#define LI_TOUCH_EVENT_CANCEL 0x04 +int LiSendTouchEvent(uint8_t eventType, uint8_t touchIndex, float x, float y, float pressure); + +// This function is similar to LiSendTouchEvent() but allows additional parameters relevant for pen +// input, including rotation, tilt, and buttons. Rotation is in degrees from vertical in Y dimension +// (parallel to screen) and tilt is in degrees from vertical in Z dimension (perpendicular to screen). +#define LI_TOOL_TYPE_PEN 0x01 +#define LI_TOOL_TYPE_ERASER 0x02 +#define LI_PEN_BUTTON_PRIMARY 0x01 +#define LI_PEN_BUTTON_SECONDARY 0x02 +#define LI_PEN_BUTTON_TERTIARY 0x04 +#define LI_TILT_UNKNOWN 0xFF +#define LI_ROT_UNKNOWN 0xFF +int LiSendPenEvent(uint8_t eventType, uint8_t toolType, uint8_t penButtons, + float x, float y, float pressure, + uint16_t rotation, uint8_t tiltX, uint8_t tiltY); + // This function queues a mouse button event to be sent to the remote server. #define BUTTON_ACTION_PRESS 0x07 #define BUTTON_ACTION_RELEASE 0x08 @@ -614,6 +646,43 @@ int LiSendMultiControllerEvent(short controllerNumber, short activeGamepadMask, short buttonFlags, unsigned char leftTrigger, unsigned char rightTrigger, short leftStickX, short leftStickY, short rightStickX, short rightStickY); +// This function provides a method of informing the host of the available buttons and capabilities +// on a new controller. This can be used as an alternative to calling LiSendMultiControllerEvent() +// to indicate the arrival of a new controller. +// +// This can allow the host to make better decisions about what type of controller to emulate and what +// capabilities to advertise to the OS on the virtual controller. +// +// If controller arrival events are unsupported by the host, this will fall back to indicating +// arrival via LiSendMultiControllerEvent(). +#define LI_CTYPE_UNKNOWN 0x00 +#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 +int LiSendControllerArrivalEvent(uint8_t controllerNumber, uint16_t activeGamepadMask, uint8_t type, + uint32_t supportedButtonFlags, uint16_t capabilities); + +// This function is similar to LiSendTouchEvent(), but the touch events are associated with a +// touchpad device present on a game controller instead of a touchscreen. +// +// If unsupported by the host, this will return LI_ERR_UNSUPPORTED and the caller should consider +// using this touch input to simulate trackpad input. +int LiSendControllerTouchEvent(uint8_t controllerNumber, uint8_t eventType, uint8_t touchIndex, float x, float y, float pressure); + +// This function allows clients to send controller-associated motion events to a supported host. +// +// For power and performance reasons, motion sensors should not be enabled unless the host has +// explicitly asked for motion event reports via ConnListenerSetMotionEventState(). +#define LI_MOTION_TYPE_ACCEL 0x01 +#define LI_MOTION_TYPE_GYRO 0x02 +int LiSendControllerMotionEvent(uint8_t controllerNumber, uint8_t motionType, float x, float y, float z); + // 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.