Introduce new input extension functions for touch, pen, motion, and controller arrival

This commit is contained in:
Cameron Gutman 2023-06-11 19:38:22 -05:00
parent 7970925fe4
commit 4a48024dc8
5 changed files with 347 additions and 1 deletions

View File

@ -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] = {

View File

@ -1,7 +1,13 @@
#pragma once
#include <stdint.h>
#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)

View File

@ -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*)&in;
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;
}

View File

@ -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

View File

@ -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.