* http://msdn.microsoft.com/en-us/library/hh454910/
*/
-#define MAX_CONTACTS 512
+#define MAX_CONTACTS 64
+#define MAX_PEN_CONTACTS 4
struct _RDPEI_CHANNEL_CALLBACK
{
RdpeiClientContext* context;
- int version;
+ UINT32 version;
+ UINT32 features;
UINT16 maxTouchContacts;
UINT64 currentFrameTime;
UINT64 previousFrameTime;
- RDPINPUT_TOUCH_FRAME frame;
- RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS];
- RDPINPUT_CONTACT_POINT* contactPoints;
+ RDPINPUT_CONTACT_POINT contactPoints[MAX_CONTACTS];
+ UINT64 currentPenFrameTime;
+ UINT64 previousPenFrameTime;
+ UINT16 maxPenContacts;
+ RDPINPUT_PEN_CONTACT_POINT penContactPoints[MAX_PEN_CONTACTS];
+
+ CRITICAL_SECTION lock;
rdpContext* rdpcontext;
BOOL initialized;
+ HANDLE thread;
+ HANDLE event;
};
typedef struct _RDPEI_PLUGIN RDPEI_PLUGIN;
*
* @return 0 on success, otherwise a Win32 error code
*/
-static UINT rdpei_send_frame(RdpeiClientContext* context);
+static UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame);
#ifdef WITH_DEBUG_RDPEI
static const char* rdpei_eventid_string(UINT16 event)
return "EVENTID_RESUME_TOUCH";
case EVENTID_DISMISS_HOVERING_CONTACT:
return "EVENTID_DISMISS_HOVERING_CONTACT";
+ case EVENTID_PEN:
+ return "EVENTID_PEN";
default:
return "EVENTID_UNKNOWN";
}
}
#endif
+static RDPINPUT_CONTACT_POINT* rdpei_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, BOOL active)
+{
+ UINT16 i;
+
+ for (i = 0; i < rdpei->maxTouchContacts; i++)
+ {
+ RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i];
+
+ if (!contactPoint->active && active)
+ continue;
+ else if (!contactPoint->active && !active)
+ {
+ contactPoint->contactId = i;
+ contactPoint->externalId = externalId;
+ contactPoint->active = TRUE;
+ return contactPoint;
+ }
+ else if (contactPoint->externalId == externalId)
+ {
+ return contactPoint;
+ }
+ }
+ return NULL;
+}
+
/**
* Function description
*
*/
static UINT rdpei_add_frame(RdpeiClientContext* context)
{
- int i;
- RDPINPUT_CONTACT_DATA* contact;
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
- rdpei->frame.contactCount = 0;
+ UINT16 i;
+ RDPEI_PLUGIN* rdpei;
+ RDPINPUT_TOUCH_FRAME frame = { 0 };
+ RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS] = { 0 };
+
+ if (!context || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ frame.contacts = contacts;
for (i = 0; i < rdpei->maxTouchContacts; i++)
{
- contact = (RDPINPUT_CONTACT_DATA*)&(rdpei->contactPoints[i].data);
+ RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i];
+ RDPINPUT_CONTACT_DATA* contact = &contactPoint->data;
- if (rdpei->contactPoints[i].dirty)
+ if (contactPoint->dirty)
{
- CopyMemory(&(rdpei->contacts[rdpei->frame.contactCount]), contact,
- sizeof(RDPINPUT_CONTACT_DATA));
+ contacts[frame.contactCount] = *contact;
rdpei->contactPoints[i].dirty = FALSE;
- rdpei->frame.contactCount++;
+ frame.contactCount++;
}
- else if (rdpei->contactPoints[i].active)
+ else if (contactPoint->active)
{
if (contact->contactFlags & CONTACT_FLAG_DOWN)
{
contact->contactFlags |= CONTACT_FLAG_INCONTACT;
}
- CopyMemory(&(rdpei->contacts[rdpei->frame.contactCount]), contact,
- sizeof(RDPINPUT_CONTACT_DATA));
- rdpei->frame.contactCount++;
+ contacts[frame.contactCount] = *contact;
+ frame.contactCount++;
+ }
+ if (contact->contactFlags & CONTACT_FLAG_UP)
+ {
+ contactPoint->active = FALSE;
+ contactPoint->externalId = 0;
+ contactPoint->contactId = 0;
}
}
+ if (frame.contactCount > 0)
+ {
+ UINT error = rdpei_send_frame(context, &frame);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "rdpei_send_frame failed with error %" PRIu32 "!", error);
+ return error;
+ }
+ }
return CHANNEL_RC_OK;
}
UINT32 pduLength)
{
UINT status;
+ if (!callback || !s || !callback->channel || !callback->channel->Write)
+ return ERROR_INTERNAL_ERROR;
+
Stream_SetPosition(s, 0);
Stream_Write_UINT16(s, eventId); /* eventId (2 bytes) */
Stream_Write_UINT32(s, pduLength); /* pduLength (4 bytes) */
return status;
}
+static UINT rdpei_write_pen_frame(wStream* s, const RDPINPUT_PEN_FRAME* frame)
+{
+ UINT16 x;
+ if (!s || !frame)
+ return ERROR_INTERNAL_ERROR;
+
+ if (!rdpei_write_2byte_unsigned(s, frame->contactCount))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_8byte_unsigned(s, frame->frameOffset))
+ return ERROR_OUTOFMEMORY;
+ for (x = 0; x < frame->contactCount; x++)
+ {
+ const RDPINPUT_PEN_CONTACT* contact = &frame->contacts[x];
+
+ if (!Stream_EnsureRemainingCapacity(s, 1))
+ return ERROR_OUTOFMEMORY;
+ Stream_Write_UINT8(s, contact->deviceId);
+ if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_4byte_signed(s, contact->x))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_4byte_signed(s, contact->y))
+ return ERROR_OUTOFMEMORY;
+ if (!rdpei_write_4byte_unsigned(s, contact->contactFlags))
+ return ERROR_OUTOFMEMORY;
+ if (contact->fieldsPresent & PEN_CONTACT_PENFLAGS_PRESENT)
+ {
+ if (!rdpei_write_4byte_unsigned(s, contact->penFlags))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & PEN_CONTACT_PRESSURE_PRESENT)
+ {
+ if (!rdpei_write_4byte_unsigned(s, contact->pressure))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & PEN_CONTACT_ROTATION_PRESENT)
+ {
+ if (!rdpei_write_2byte_unsigned(s, contact->rotation))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & PEN_CONTACT_TILTX_PRESENT)
+ {
+ if (!rdpei_write_2byte_signed(s, contact->tiltX))
+ return ERROR_OUTOFMEMORY;
+ }
+ if (contact->fieldsPresent & PEN_CONTACT_TILTY_PRESENT)
+ {
+ if (!rdpei_write_2byte_signed(s, contact->tiltY))
+ return ERROR_OUTOFMEMORY;
+ }
+ }
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpei_send_pen_event_pdu(RDPEI_CHANNEL_CALLBACK* callback, UINT32 frameOffset,
+ const RDPINPUT_PEN_FRAME* frames, UINT16 count)
+{
+ UINT status;
+ wStream* s;
+ UINT16 x;
+
+ if (!frames || (count == 0))
+ return ERROR_INTERNAL_ERROR;
+
+ s = Stream_New(NULL, 64);
+
+ if (!s)
+ {
+ WLog_ERR(TAG, "Stream_New failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ Stream_Seek(s, RDPINPUT_HEADER_LENGTH);
+ /**
+ * the time that has elapsed (in milliseconds) from when the oldest touch frame
+ * was generated to when it was encoded for transmission by the client.
+ */
+ rdpei_write_4byte_unsigned(s, frameOffset); /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */
+ rdpei_write_2byte_unsigned(s, count); /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */
+
+ for (x = 0; x < count; x++)
+ {
+ if ((status = rdpei_write_pen_frame(s, &frames[x])))
+ {
+ WLog_ERR(TAG, "rdpei_write_touch_frame failed with error %" PRIu32 "!", status);
+ Stream_Free(s, TRUE);
+ return status;
+ }
+ }
+ Stream_SealLength(s);
+
+ status = rdpei_send_pdu(callback, s, EVENTID_PEN, Stream_Length(s));
+ Stream_Free(s, TRUE);
+ return status;
+}
+
+static UINT rdpei_send_pen_frame(RdpeiClientContext* context, RDPINPUT_PEN_FRAME* frame)
+{
+ const UINT64 currentTime = GetTickCount64();
+ RDPEI_PLUGIN* rdpei;
+ RDPEI_CHANNEL_CALLBACK* callback;
+ UINT error;
+
+ if (!context)
+ return ERROR_INTERNAL_ERROR;
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ if (!rdpei || !rdpei->listener_callback)
+ return ERROR_INTERNAL_ERROR;
+
+ callback = rdpei->listener_callback->channel_callback;
+
+ if (!rdpei->previousPenFrameTime && !rdpei->currentPenFrameTime)
+ {
+ rdpei->currentPenFrameTime = currentTime;
+ frame->frameOffset = 0;
+ }
+ else
+ {
+ rdpei->currentPenFrameTime = currentTime;
+ frame->frameOffset = rdpei->currentPenFrameTime - rdpei->previousPenFrameTime;
+ }
+
+ if ((error = rdpei_send_pen_event_pdu(callback, frame->frameOffset, frame, 1)))
+ return error;
+
+ rdpei->previousPenFrameTime = rdpei->currentPenFrameTime;
+ return error;
+}
+
+static UINT rdpei_add_pen_frame(RdpeiClientContext* context)
+{
+ UINT16 i;
+ RDPEI_PLUGIN* rdpei;
+ RDPINPUT_PEN_FRAME penFrame = { 0 };
+ RDPINPUT_PEN_CONTACT penContacts[MAX_PEN_CONTACTS] = { 0 };
+
+ if (!context || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+
+ penFrame.contacts = penContacts;
+
+ for (i = 0; i < rdpei->maxPenContacts; i++)
+ {
+ RDPINPUT_PEN_CONTACT_POINT* contact = &(rdpei->penContactPoints[i]);
+
+ if (contact->dirty)
+ {
+ penContacts[penFrame.contactCount++] = contact->data;
+ contact->dirty = FALSE;
+ }
+ else if (contact->active)
+ {
+ if (contact->data.contactFlags & CONTACT_FLAG_DOWN)
+ {
+ contact->data.contactFlags = CONTACT_FLAG_UPDATE;
+ contact->data.contactFlags |= CONTACT_FLAG_INRANGE;
+ contact->data.contactFlags |= CONTACT_FLAG_INCONTACT;
+ }
+
+ penContacts[penFrame.contactCount++] = contact->data;
+ }
+ if (contact->data.contactFlags & CONTACT_FLAG_UP)
+ {
+ contact->externalId = 0;
+ contact->active = FALSE;
+ }
+ }
+
+ if (penFrame.contactCount > 0)
+ return rdpei_send_pen_frame(context, &penFrame);
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpei_update(RdpeiClientContext* context)
+{
+ UINT error = rdpei_add_frame(context);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error);
+ return error;
+ }
+
+ return rdpei_add_pen_frame(context);
+}
+
+static DWORD WINAPI rdpei_periodic_update(LPVOID arg)
+{
+ DWORD status;
+ RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)arg;
+ UINT error = CHANNEL_RC_OK;
+ RdpeiClientContext* context;
+
+ if (!rdpei)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ context = (RdpeiClientContext*)rdpei->iface.pInterface;
+
+ if (!context)
+ {
+ error = ERROR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ while (rdpei->initialized)
+ {
+ status = WaitForSingleObject(rdpei->event, 20);
+
+ if (status == WAIT_FAILED)
+ {
+ error = GetLastError();
+ WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ EnterCriticalSection(&rdpei->lock);
+
+ error = rdpei_update(context);
+ if (error != CHANNEL_RC_OK)
+ {
+ WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error);
+ break;
+ }
+
+ if (status == WAIT_OBJECT_0)
+ ResetEvent(rdpei->event);
+
+ LeaveCriticalSection(&rdpei->lock);
+ }
+
+out:
+
+ if (error && rdpei && rdpei->rdpcontext)
+ setChannelError(rdpei->rdpcontext, error, "rdpei_schedule_thread reported an error");
+
+ ExitThread(error);
+ return error;
+}
+
/**
* Function description
*
wStream* s;
UINT32 flags;
UINT32 pduLength;
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
+ RDPEI_PLUGIN* rdpei;
+
+ if (!callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)callback->plugin;
flags = 0;
- flags |= READY_FLAGS_SHOW_TOUCH_VISUALS;
- // flags |= READY_FLAGS_DISABLE_TIMESTAMP_INJECTION;
+ flags |= CS_READY_FLAGS_SHOW_TOUCH_VISUALS;
+ if (rdpei->version > RDPINPUT_PROTOCOL_V10)
+ flags |= CS_READY_FLAGS_DISABLE_TIMESTAMP_INJECTION;
+ flags |= CS_READY_FLAGS_ENABLE_MULTIPEN_INJECTION;
pduLength = RDPINPUT_HEADER_LENGTH + 10;
s = Stream_New(NULL, pduLength);
Stream_Seek(s, RDPINPUT_HEADER_LENGTH);
Stream_Write_UINT32(s, flags); /* flags (4 bytes) */
- Stream_Write_UINT32(s, RDPINPUT_PROTOCOL_V10); /* protocolVersion (4 bytes) */
+ Stream_Write_UINT32(s, rdpei->version); /* protocolVersion (4 bytes) */
Stream_Write_UINT16(s, rdpei->maxTouchContacts); /* maxTouchContacts (2 bytes) */
Stream_SealLength(s);
status = rdpei_send_pdu(callback, s, EVENTID_CS_READY, pduLength);
UINT32 index;
int rectSize = 2;
RDPINPUT_CONTACT_DATA* contact;
+ if (!s || !frame)
+ return ERROR_INTERNAL_ERROR;
#ifdef WITH_DEBUG_RDPEI
WLog_DBG(TAG, "contactCount: %" PRIu32 "", frame->contactCount);
WLog_DBG(TAG, "frameOffset: 0x%016" PRIX64 "", frame->frameOffset);
UINT status;
wStream* s;
UINT32 pduLength;
+ if (!frame)
+ return ERROR_INTERNAL_ERROR;
pduLength = 64 + (frame->contactCount * 64);
s = Stream_New(NULL, pduLength);
*/
static UINT rdpei_recv_sc_ready_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s)
{
+ UINT32 features = 0;
+ UINT32 size;
UINT32 protocolVersion;
+ RDPEI_PLUGIN* rdpei;
+
+ if (!callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)callback->plugin;
+
+ size = Stream_GetRemainingLength(s);
+ if (size < 4)
+ return ERROR_INVALID_DATA;
Stream_Read_UINT32(s, protocolVersion); /* protocolVersion (4 bytes) */
+
+ if (protocolVersion >= RDPINPUT_PROTOCOL_V300)
+ {
+ if (size < 8)
+ return ERROR_INVALID_DATA;
+ }
+ if (size >= 9)
+ Stream_Read_UINT32(s, features);
+
+ if (rdpei->version > protocolVersion)
+ rdpei->version = protocolVersion;
+ rdpei->features = features;
#if 0
if (protocolVersion != RDPINPUT_PROTOCOL_V10)
*/
static UINT rdpei_recv_suspend_touch_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s)
{
- RdpeiClientContext* rdpei = (RdpeiClientContext*)callback->plugin->pInterface;
UINT error = CHANNEL_RC_OK;
+ RdpeiClientContext* rdpei;
+
+ WINPR_UNUSED(s);
+
+ if (!callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+ rdpei = (RdpeiClientContext*)callback->plugin->pInterface;
+ if (!rdpei)
+ return ERROR_INTERNAL_ERROR;
+
IFCALLRET(rdpei->SuspendTouch, error, rdpei);
if (error)
*/
static UINT rdpei_recv_resume_touch_pdu(RDPEI_CHANNEL_CALLBACK* callback, wStream* s)
{
- RdpeiClientContext* rdpei = (RdpeiClientContext*)callback->plugin->pInterface;
+ RdpeiClientContext* rdpei;
UINT error = CHANNEL_RC_OK;
+ if (!s || !callback || !callback->plugin)
+ return ERROR_INTERNAL_ERROR;
+ rdpei = (RdpeiClientContext*)callback->plugin->pInterface;
+ if (!rdpei)
+ return ERROR_INTERNAL_ERROR;
+
IFCALLRET(rdpei->ResumeTouch, error, rdpei);
if (error)
UINT16 eventId;
UINT32 pduLength;
UINT error;
+ if (!s)
+ return ERROR_INTERNAL_ERROR;
if (Stream_GetRemainingLength(s) < 6)
return ERROR_INVALID_DATA;
{
RDPEI_CHANNEL_CALLBACK* callback;
RDPEI_LISTENER_CALLBACK* listener_callback = (RDPEI_LISTENER_CALLBACK*)pListenerCallback;
+ if (!listener_callback)
+ return ERROR_INTERNAL_ERROR;
callback = (RDPEI_CHANNEL_CALLBACK*)calloc(1, sizeof(RDPEI_CHANNEL_CALLBACK));
+ WINPR_UNUSED(Data);
+ WINPR_UNUSED(pbAccept);
+
if (!callback)
{
WLog_ERR(TAG, "calloc failed!");
*
* @return 0 on success, otherwise a Win32 error code
*/
+static UINT rdpei_plugin_terminated(IWTSPlugin* pPlugin)
+{
+ RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)pPlugin;
+
+ if (!pPlugin)
+ return ERROR_INVALID_PARAMETER;
+
+ if (rdpei && rdpei->listener_callback)
+ {
+ IWTSVirtualChannelManager* mgr = rdpei->listener_callback->channel_mgr;
+ if (mgr)
+ IFCALL(mgr->DestroyListener, mgr, rdpei->listener);
+
+ rdpei->initialized = FALSE;
+ if (rdpei->event)
+ SetEvent(rdpei->event);
+
+ if (rdpei->thread)
+ {
+ WaitForSingleObject(rdpei->thread, INFINITE);
+ CloseHandle(rdpei->thread);
+ }
+ if (rdpei->event)
+ CloseHandle(rdpei->event);
+ }
+ DeleteCriticalSection(&rdpei->lock);
+ free(rdpei->listener_callback);
+ free(rdpei->context);
+ free(rdpei);
+ return CHANNEL_RC_OK;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
static UINT rdpei_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
{
UINT error;
rdpei->listener->pInterface = rdpei->iface.pInterface;
+ InitializeCriticalSection(&rdpei->lock);
+ rdpei->event = CreateEventA(NULL, TRUE, FALSE, NULL);
+ if (!rdpei->event)
+ goto error_out;
+ rdpei->thread = CreateThread(NULL, 0, rdpei_periodic_update, rdpei, 0, NULL);
+ if (!rdpei->thread)
+ goto error_out;
rdpei->initialized = TRUE;
return error;
error_out:
- free(rdpei->listener_callback);
+ rdpei_plugin_terminated(pPlugin);
return error;
}
/**
- * Function description
- *
- * @return 0 on success, otherwise a Win32 error code
- */
-static UINT rdpei_plugin_terminated(IWTSPlugin* pPlugin)
-{
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)pPlugin;
-
- if (!pPlugin)
- return ERROR_INVALID_PARAMETER;
-
- if (rdpei && rdpei->listener_callback)
- {
- IWTSVirtualChannelManager* mgr = rdpei->listener_callback->channel_mgr;
- if (mgr)
- IFCALL(mgr->DestroyListener, mgr, rdpei->listener);
- }
- free(rdpei->listener_callback);
- free(rdpei->contactPoints);
- free(rdpei->context);
- free(rdpei);
- return CHANNEL_RC_OK;
-}
-
-/**
* Channel Client Interface
*/
-static int rdpei_get_version(RdpeiClientContext* context)
+static UINT32 rdpei_get_version(RdpeiClientContext* context)
{
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
+ RDPEI_PLUGIN* rdpei;
+ if (!context || !context->handle)
+ return -1;
+ rdpei = (RDPEI_PLUGIN*)context->handle;
return rdpei->version;
}
+static UINT32 rdpei_get_features(RdpeiClientContext* context)
+{
+ RDPEI_PLUGIN* rdpei;
+ if (!context || !context->handle)
+ return -1;
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ return rdpei->features;
+}
+
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
-UINT rdpei_send_frame(RdpeiClientContext* context)
+UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame)
{
UINT64 currentTime;
RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
if (!rdpei->previousFrameTime && !rdpei->currentFrameTime)
{
rdpei->currentFrameTime = currentTime;
- rdpei->frame.frameOffset = 0;
+ frame->frameOffset = 0;
}
else
{
rdpei->currentFrameTime = currentTime;
- rdpei->frame.frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime;
+ frame->frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime;
}
- if ((error = rdpei_send_touch_event_pdu(callback, &rdpei->frame)))
+ if ((error = rdpei_send_touch_event_pdu(callback, frame)))
{
WLog_ERR(TAG, "rdpei_send_touch_event_pdu failed with error %" PRIu32 "!", error);
return error;
}
rdpei->previousFrameTime = rdpei->currentFrameTime;
- rdpei->frame.contactCount = 0;
return error;
}
*/
static UINT rdpei_add_contact(RdpeiClientContext* context, const RDPINPUT_CONTACT_DATA* contact)
{
- UINT error;
RDPINPUT_CONTACT_POINT* contactPoint;
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
+ RDPEI_PLUGIN* rdpei;
+ if (!context || !contact || !context->handle)
+ return ERROR_INTERNAL_ERROR;
- contactPoint = (RDPINPUT_CONTACT_POINT*)&rdpei->contactPoints[contact->contactId];
- CopyMemory(&(contactPoint->data), contact, sizeof(RDPINPUT_CONTACT_DATA));
- contactPoint->dirty = TRUE;
+ rdpei = (RDPEI_PLUGIN*)context->handle;
- error = rdpei_add_frame(context);
- if (error != CHANNEL_RC_OK)
- {
- WLog_ERR(TAG, "rdpei_add_frame failed with error %" PRIu32 "!", error);
- return error;
- }
+ EnterCriticalSection(&rdpei->lock);
+ contactPoint = &rdpei->contactPoints[contact->contactId];
+ contactPoint->data = *contact;
+ contactPoint->dirty = TRUE;
+ SetEvent(rdpei->event);
+ LeaveCriticalSection(&rdpei->lock);
- if (rdpei->frame.contactCount > 0)
- {
- error = rdpei_send_frame(context);
- if (error != CHANNEL_RC_OK)
- {
- WLog_ERR(TAG, "rdpei_send_frame failed with error %" PRIu32 "!", error);
- return error;
- }
- }
return CHANNEL_RC_OK;
}
-/**
- * Function description
- *
- * @return 0 on success, otherwise a Win32 error code
- */
-static UINT rdpei_touch_begin(RdpeiClientContext* context, int externalId, int x, int y,
- int* contactId)
+static UINT rdpei_touch_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
+ INT32 x, INT32 y, INT32* contactId)
{
- unsigned int i;
INT64 contactIdlocal = -1;
- RDPINPUT_CONTACT_DATA contact;
- RDPINPUT_CONTACT_POINT* contactPoint = NULL;
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
+ RDPINPUT_CONTACT_POINT* contactPoint;
+ RDPEI_PLUGIN* rdpei;
+ BOOL begin;
UINT error = CHANNEL_RC_OK;
- /* Create a new contact point in an empty slot */
-
- for (i = 0; i < rdpei->maxTouchContacts; i++)
- {
- contactPoint = (RDPINPUT_CONTACT_POINT*)&rdpei->contactPoints[i];
+ if (!context || !contactId || !context->handle)
+ return ERROR_INTERNAL_ERROR;
- if (!contactPoint->active)
- {
- contactPoint->contactId = i;
- contactIdlocal = contactPoint->contactId;
- contactPoint->externalId = externalId;
- contactPoint->active = TRUE;
- contactPoint->state = RDPINPUT_CONTACT_STATE_ENGAGED;
- break;
- }
- }
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ /* Create a new contact point in an empty slot */
+ EnterCriticalSection(&rdpei->lock);
+ begin = contactFlags & CONTACT_FLAG_DOWN;
+ contactPoint = rdpei_contact(rdpei, externalId, !begin);
+ if (contactPoint)
+ contactIdlocal = contactPoint->contactId;
+ LeaveCriticalSection(&rdpei->lock);
if (contactIdlocal >= 0)
{
- ZeroMemory(&contact, sizeof(RDPINPUT_CONTACT_DATA));
- contactPoint->lastX = x;
- contactPoint->lastY = y;
+ RDPINPUT_CONTACT_DATA contact = { 0 };
contact.x = x;
contact.y = y;
- contact.contactId = (UINT32)contactIdlocal;
- contact.contactFlags |= CONTACT_FLAG_DOWN;
- contact.contactFlags |= CONTACT_FLAG_INRANGE;
- contact.contactFlags |= CONTACT_FLAG_INCONTACT;
+ contact.contactId = contactIdlocal;
+ contact.contactFlags = contactFlags;
error = context->AddContact(context, &contact);
}
- *contactId = contactIdlocal;
+ if (contactId)
+ *contactId = contactIdlocal;
return error;
}
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_touch_begin(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId)
+{
+ return rdpei_touch_process(context, externalId,
+ CONTACT_FLAG_DOWN | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, x,
+ y, contactId);
+}
/**
* Function description
*
* @return 0 on success, otherwise a Win32 error code
*/
-static UINT rdpei_touch_update(RdpeiClientContext* context, int externalId, int x, int y,
- int* contactId)
+static UINT rdpei_touch_update(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId)
{
- unsigned int i;
- int contactIdlocal = -1;
- RDPINPUT_CONTACT_DATA contact;
- RDPINPUT_CONTACT_POINT* contactPoint = NULL;
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
- UINT error = CHANNEL_RC_OK;
+ return rdpei_touch_process(context, externalId,
+ CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT,
+ x, y, contactId);
+}
- for (i = 0; i < rdpei->maxTouchContacts; i++)
- {
- contactPoint = (RDPINPUT_CONTACT_POINT*)&rdpei->contactPoints[i];
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_touch_end(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
+ INT32* contactId)
+{
+ UINT error = rdpei_touch_process(
+ context, externalId, CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT, x,
+ y, contactId);
+ if (error != CHANNEL_RC_OK)
+ return error;
+ return rdpei_touch_process(context, externalId, CONTACT_FLAG_UP, x, y, contactId);
+}
- if (!contactPoint->active)
- continue;
+static RDPINPUT_PEN_CONTACT_POINT* rdpei_pen_contact(RDPEI_PLUGIN* rdpei, INT32 externalId,
+ BOOL active)
+{
+ UINT32 x;
+ if (!rdpei)
+ return NULL;
- if (contactPoint->externalId == externalId)
+ for (x = 0; x < rdpei->maxPenContacts; x++)
+ {
+ RDPINPUT_PEN_CONTACT_POINT* contact = &rdpei->penContactPoints[x];
+ if (active)
{
- contactIdlocal = contactPoint->contactId;
- break;
+ if (contact->active)
+ {
+ if (contact->externalId == externalId)
+ return contact;
+ }
+ }
+ else
+ {
+ if (!contact->active)
+ {
+ contact->externalId = externalId;
+ contact->active = TRUE;
+ return contact;
+ }
}
}
+ return NULL;
+}
- if (contactIdlocal >= 0)
+static UINT rdpei_add_pen(RdpeiClientContext* context, INT32 externalId,
+ const RDPINPUT_PEN_CONTACT* contact)
+{
+ RDPEI_PLUGIN* rdpei;
+ RDPINPUT_PEN_CONTACT_POINT* contactPoint;
+
+ if (!context || !contact || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+
+ EnterCriticalSection(&rdpei->lock);
+ contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE);
+ if (contactPoint)
{
- ZeroMemory(&contact, sizeof(RDPINPUT_CONTACT_DATA));
- contactPoint->lastX = x;
- contactPoint->lastY = y;
+ contactPoint->data = *contact;
+ contactPoint->dirty = TRUE;
+ SetEvent(rdpei->event);
+ }
+ LeaveCriticalSection(&rdpei->lock);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT rdpei_pen_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
+ UINT32 fieldFlags, INT32 x, INT32 y, va_list ap)
+{
+ RDPINPUT_PEN_CONTACT_POINT* contactPoint;
+ RDPEI_PLUGIN* rdpei;
+ BOOL begin;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!context || !context->handle)
+ return ERROR_INTERNAL_ERROR;
+
+ rdpei = (RDPEI_PLUGIN*)context->handle;
+ begin = contactFlags & CONTACT_FLAG_DOWN;
+
+ EnterCriticalSection(&rdpei->lock);
+ contactPoint = rdpei_pen_contact(rdpei, externalId, !begin);
+ LeaveCriticalSection(&rdpei->lock);
+ if (contactPoint != NULL)
+ {
+ RDPINPUT_PEN_CONTACT contact = { 0 };
+
contact.x = x;
contact.y = y;
- contact.contactId = (UINT32)contactIdlocal;
- contact.contactFlags |= CONTACT_FLAG_UPDATE;
- contact.contactFlags |= CONTACT_FLAG_INRANGE;
- contact.contactFlags |= CONTACT_FLAG_INCONTACT;
- error = context->AddContact(context, &contact);
+ contact.fieldsPresent = fieldFlags;
+
+ contact.contactFlags = contactFlags;
+ if (fieldFlags & PEN_CONTACT_PENFLAGS_PRESENT)
+ contact.penFlags = va_arg(ap, UINT32);
+ if (fieldFlags & PEN_CONTACT_PRESSURE_PRESENT)
+ contact.pressure = va_arg(ap, UINT32);
+ if (fieldFlags & PEN_CONTACT_ROTATION_PRESENT)
+ contact.rotation = va_arg(ap, UINT32);
+ if (fieldFlags & PEN_CONTACT_TILTX_PRESENT)
+ contact.tiltX = va_arg(ap, INT32);
+ if (fieldFlags & PEN_CONTACT_TILTY_PRESENT)
+ contact.tiltY = va_arg(ap, INT32);
+
+ error = context->AddPen(context, externalId, &contact);
}
- *contactId = contactIdlocal;
return error;
}
*
* @return 0 on success, otherwise a Win32 error code
*/
-static UINT rdpei_touch_end(RdpeiClientContext* context, int externalId, int x, int y,
- int* contactId)
-{
- unsigned int i;
- int contactIdlocal = -1;
- int tempvalue;
- RDPINPUT_CONTACT_DATA contact;
- RDPINPUT_CONTACT_POINT* contactPoint = NULL;
- RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
+static UINT rdpei_pen_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...)
+{
UINT error;
+ va_list ap;
- for (i = 0; i < rdpei->maxTouchContacts; i++)
- {
- contactPoint = (RDPINPUT_CONTACT_POINT*)&rdpei->contactPoints[i];
-
- if (!contactPoint->active)
- continue;
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ CONTACT_FLAG_DOWN | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT,
+ fieldFlags, x, y, ap);
+ va_end(ap);
- if (contactPoint->externalId == externalId)
- {
- contactIdlocal = contactPoint->contactId;
- break;
- }
- }
-
- if (contactIdlocal >= 0)
- {
- ZeroMemory(&contact, sizeof(RDPINPUT_CONTACT_DATA));
-
- if ((contactPoint->lastX != x) && (contactPoint->lastY != y))
- {
- if ((error = context->TouchUpdate(context, externalId, x, y, &tempvalue)))
- {
- WLog_ERR(TAG, "context->TouchUpdate failed with error %" PRIu32 "!", error);
- return error;
- }
- }
-
- contact.x = x;
- contact.y = y;
- contact.contactId = (UINT32)contactIdlocal;
- contact.contactFlags |= CONTACT_FLAG_UP;
+ return error;
+}
- if ((error = context->AddContact(context, &contact)))
- {
- WLog_ERR(TAG, "context->AddContact failed with error %" PRIu32 "!", error);
- return error;
- }
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
+ INT32 x, INT32 y, ...)
+{
+ UINT error;
+ va_list ap;
- contactPoint->externalId = 0;
- contactPoint->active = FALSE;
- contactPoint->flags = 0;
- contactPoint->contactId = 0;
- contactPoint->state = RDPINPUT_CONTACT_STATE_OUT_OF_RANGE;
- }
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT,
+ fieldFlags, x, y, ap);
+ va_end(ap);
+ return error;
+}
- *contactId = contactIdlocal;
- return CHANNEL_RC_OK;
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT rdpei_pen_end(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, INT32 x,
+ INT32 y, ...)
+{
+ UINT error;
+ va_list ap;
+
+ va_start(ap, y);
+ error = rdpei_pen_process(context, externalId,
+ CONTACT_FLAG_UPDATE | CONTACT_FLAG_INRANGE | CONTACT_FLAG_INCONTACT,
+ fieldFlags, x, y, ap);
+ if (error == CHANNEL_RC_OK)
+ error = rdpei_pen_process(context, externalId, CONTACT_FLAG_UP, fieldFlags, x, y, ap);
+ va_end(ap);
+ return error;
}
#ifdef BUILTIN_CHANNELS
if (!rdpei)
{
- size_t size;
rdpei = (RDPEI_PLUGIN*)calloc(1, sizeof(RDPEI_PLUGIN));
if (!rdpei)
rdpei->iface.Connected = NULL;
rdpei->iface.Disconnected = NULL;
rdpei->iface.Terminated = rdpei_plugin_terminated;
- rdpei->version = 1;
+ rdpei->version = RDPINPUT_PROTOCOL_V300;
rdpei->currentFrameTime = 0;
rdpei->previousFrameTime = 0;
- rdpei->frame.contacts = (RDPINPUT_CONTACT_DATA*)rdpei->contacts;
- rdpei->maxTouchContacts = 10;
- size = rdpei->maxTouchContacts * sizeof(RDPINPUT_CONTACT_POINT);
- rdpei->contactPoints = (RDPINPUT_CONTACT_POINT*)calloc(1, size);
+ rdpei->maxTouchContacts = MAX_CONTACTS;
+ rdpei->maxPenContacts = MAX_PEN_CONTACTS;
rdpei->rdpcontext =
((freerdp*)((rdpSettings*)pEntryPoints->GetRdpSettings(pEntryPoints))->instance)
->context;
- if (!rdpei->contactPoints)
- {
- WLog_ERR(TAG, "calloc failed!");
- error = CHANNEL_RC_NO_MEMORY;
- goto error_out;
- }
-
context = (RdpeiClientContext*)calloc(1, sizeof(RdpeiClientContext));
if (!context)
context->handle = (void*)rdpei;
context->GetVersion = rdpei_get_version;
+ context->GetFeatures = rdpei_get_features;
context->AddContact = rdpei_add_contact;
context->TouchBegin = rdpei_touch_begin;
context->TouchUpdate = rdpei_touch_update;
context->TouchEnd = rdpei_touch_end;
+ context->AddPen = rdpei_add_pen;
+ context->PenBegin = rdpei_pen_begin;
+ context->PenUpdate = rdpei_pen_update;
+ context->PenEnd = rdpei_pen_end;
rdpei->iface.pInterface = (void*)context;
if ((error = pEntryPoints->RegisterPlugin(pEntryPoints, "rdpei", (IWTSPlugin*)rdpei)))
UINT16 currentMsgType;
RDPINPUT_TOUCH_EVENT touchEvent;
+ RDPINPUT_PEN_EVENT penEvent;
enum RdpEiState automataState;
};
ret->priv = priv = calloc(1, sizeof(*ret->priv));
if (!priv)
- goto out_free;
+ goto fail;
priv->inputStream = Stream_New(NULL, 256);
if (!priv->inputStream)
- goto out_free_priv;
+ goto fail;
priv->outputStream = Stream_New(NULL, 200);
if (!priv->inputStream)
- goto out_free_input_stream;
+ goto fail;
ret->vcm = vcm;
rdpei_server_context_reset(ret);
return ret;
-out_free_input_stream:
- Stream_Free(priv->inputStream, TRUE);
-out_free_priv:
- free(ret->priv);
-out_free:
- free(ret);
+fail:
+ rdpei_server_context_free(ret);
return NULL;
}
void rdpei_server_context_free(RdpeiServerContext* context)
{
- RdpeiServerPrivate* priv = context->priv;
- if (priv->channelHandle != INVALID_HANDLE_VALUE)
- WTSVirtualChannelClose(priv->channelHandle);
- Stream_Free(priv->inputStream, TRUE);
+ RdpeiServerPrivate* priv;
+
+ if (!context)
+ return;
+ priv = context->priv;
+ if (priv)
+ {
+ if (priv->channelHandle != INVALID_HANDLE_VALUE)
+ WTSVirtualChannelClose(priv->channelHandle);
+ Stream_Free(priv->inputStream, TRUE);
+ }
free(priv);
free(context);
}
{
case RDPINPUT_PROTOCOL_V10:
case RDPINPUT_PROTOCOL_V101:
+ case RDPINPUT_PROTOCOL_V200:
+ case RDPINPUT_PROTOCOL_V300:
break;
default:
WLog_ERR(TAG, "unhandled RPDEI protocol version 0x%" PRIx32 "", context->clientVersion);
static UINT read_touch_contact_data(RdpeiServerContext* context, wStream* s,
RDPINPUT_CONTACT_DATA* contactData)
{
+ WINPR_UNUSED(context);
if (Stream_GetRemainingLength(s) < 1)
{
WLog_ERR(TAG, "Not enough data!");
return CHANNEL_RC_OK;
}
+static UINT read_pen_contact(RdpeiServerContext* context, wStream* s,
+ RDPINPUT_PEN_CONTACT* contactData)
+{
+ WINPR_UNUSED(context);
+ if (Stream_GetRemainingLength(s) < 1)
+ {
+ WLog_ERR(TAG, "Not enough data!");
+ return ERROR_INVALID_DATA;
+ }
+
+ Stream_Read_UINT8(s, contactData->deviceId);
+ if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
+ !rdpei_read_4byte_signed(s, &contactData->x) ||
+ !rdpei_read_4byte_signed(s, &contactData->y) ||
+ !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ if (contactData->fieldsPresent & PEN_CONTACT_PENFLAGS_PRESENT)
+ {
+ if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & PEN_CONTACT_PRESSURE_PRESENT)
+ {
+ if (!rdpei_read_4byte_unsigned(s, &contactData->pressure))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & PEN_CONTACT_ROTATION_PRESENT)
+ {
+ if (!rdpei_read_2byte_unsigned(s, &contactData->rotation))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & PEN_CONTACT_TILTX_PRESENT)
+ {
+ if (!rdpei_read_2byte_signed(s, &contactData->tiltX))
+ return ERROR_INVALID_DATA;
+ }
+ if (contactData->fieldsPresent & PEN_CONTACT_TILTY_PRESENT)
+ {
+ if (!rdpei_read_2byte_signed(s, &contactData->tiltY))
+ return ERROR_INVALID_DATA;
+ }
+
+ return CHANNEL_RC_OK;
+}
+
/**
* Function description
*
return CHANNEL_RC_OK;
}
+static UINT read_pen_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_PEN_FRAME* frame)
+{
+ UINT32 i;
+ RDPINPUT_PEN_CONTACT* contact;
+ UINT error;
+
+ if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
+ !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_CONTACT_DATA));
+ if (!frame->contacts)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (i = 0; i < frame->contactCount; i++, contact++)
+ {
+ if ((error = read_pen_contact(context, s, contact)))
+ {
+ WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
+ frame->contactCount = i;
+ pen_frame_reset(frame);
+ return error;
+ }
+ }
+ return CHANNEL_RC_OK;
+}
+
/**
* Function description
*
*/
static UINT read_touch_event(RdpeiServerContext* context, wStream* s)
{
- UINT32 frameCount;
+ UINT16 frameCount;
UINT32 i;
RDPINPUT_TOUCH_EVENT* event = &context->priv->touchEvent;
RDPINPUT_TOUCH_FRAME* frame;
return error;
}
+static UINT read_pen_event(RdpeiServerContext* context, wStream* s)
+{
+ UINT16 frameCount;
+ UINT32 i;
+ RDPINPUT_PEN_EVENT* event = &context->priv->penEvent;
+ RDPINPUT_PEN_FRAME* frame;
+ UINT error = CHANNEL_RC_OK;
+
+ if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
+ !rdpei_read_2byte_unsigned(s, &frameCount))
+ {
+ WLog_ERR(TAG, "rdpei_read_ failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ event->frameCount = frameCount;
+ event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_PEN_FRAME));
+ if (!event->frames)
+ {
+ WLog_ERR(TAG, "calloc failed!");
+ return CHANNEL_RC_NO_MEMORY;
+ }
+
+ for (i = 0; i < frameCount; i++, frame++)
+ {
+ if ((error = read_pen_frame(context, s, frame)))
+ {
+ WLog_ERR(TAG, "read_pen_frame failed with error %" PRIu32 "!", error);
+ event->frameCount = i;
+ goto out_cleanup;
+ }
+ }
+
+ IFCALLRET(context->onPenEvent, error, context, event);
+ if (error)
+ WLog_ERR(TAG, "context->onPenEvent failed with error %" PRIu32 "", error);
+
+out_cleanup:
+ pen_event_reset(event);
+ return error;
+}
+
/**
* Function description
*
return error;
}
break;
+ case EVENTID_PEN:
+ if ((error = read_pen_event(context, s)))
+ {
+ WLog_ERR(TAG, "read_pen_event failed with error %" PRIu32 "", error);
+ return error;
+ }
+ break;
default:
WLog_ERR(TAG, "unexpected message type 0x%" PRIx16 "", priv->currentMsgType);
}
*
* @return 0 on success, otherwise a Win32 error code
*/
-UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version)
+UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features)
{
ULONG written;
RdpeiServerPrivate* priv = context->priv;
Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY);
Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + 4);
Stream_Write_UINT32(priv->outputStream, version);
+ if (version >= RDPINPUT_PROTOCOL_V300)
+ Stream_Write_UINT32(priv->outputStream, features);
if (!WTSVirtualChannelWrite(priv->channelHandle, (PCHAR)Stream_Buffer(priv->outputStream),
Stream_GetPosition(priv->outputStream), &written))