FreeRDP
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Modules Pages
server/rdpei_main.c
1
23#include <freerdp/config.h>
24
25#include <winpr/cast.h>
26#include <winpr/crt.h>
27#include <winpr/print.h>
28#include <winpr/stream.h>
29
30#include "rdpei_main.h"
31#include "../rdpei_common.h"
32#include <freerdp/channels/rdpei.h>
33#include <freerdp/server/rdpei.h>
34
35enum RdpEiState
36{
37 STATE_INITIAL,
38 STATE_WAITING_CLIENT_READY,
39 STATE_WAITING_FRAME,
40 STATE_SUSPENDED,
41};
42
43struct s_rdpei_server_private
44{
45 HANDLE channelHandle;
46 HANDLE eventHandle;
47
48 UINT32 expectedBytes;
49 BOOL waitingHeaders;
50 wStream* inputStream;
51 wStream* outputStream;
52
53 UINT16 currentMsgType;
54
55 RDPINPUT_TOUCH_EVENT touchEvent;
56 RDPINPUT_PEN_EVENT penEvent;
57
58 enum RdpEiState automataState;
59};
60
61RdpeiServerContext* rdpei_server_context_new(HANDLE vcm)
62{
63 RdpeiServerContext* ret = calloc(1, sizeof(*ret));
64 RdpeiServerPrivate* priv = NULL;
65
66 if (!ret)
67 return NULL;
68
69 ret->priv = priv = calloc(1, sizeof(*ret->priv));
70 if (!priv)
71 goto fail;
72
73 priv->inputStream = Stream_New(NULL, 256);
74 if (!priv->inputStream)
75 goto fail;
76
77 priv->outputStream = Stream_New(NULL, 200);
78 if (!priv->inputStream)
79 goto fail;
80
81 ret->vcm = vcm;
82 rdpei_server_context_reset(ret);
83 return ret;
84
85fail:
86 WINPR_PRAGMA_DIAG_PUSH
87 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
88 rdpei_server_context_free(ret);
89 WINPR_PRAGMA_DIAG_POP
90 return NULL;
91}
92
98UINT rdpei_server_init(RdpeiServerContext* context)
99{
100 void* buffer = NULL;
101 DWORD bytesReturned = 0;
102 RdpeiServerPrivate* priv = context->priv;
103 UINT32 channelId = 0;
104 BOOL status = TRUE;
105
106 priv->channelHandle = WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, RDPEI_DVC_CHANNEL_NAME,
107 WTS_CHANNEL_OPTION_DYNAMIC);
108 if (!priv->channelHandle)
109 {
110 WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!");
111 return CHANNEL_RC_INITIALIZATION_ERROR;
112 }
113
114 channelId = WTSChannelGetIdByHandle(priv->channelHandle);
115
116 IFCALLRET(context->onChannelIdAssigned, status, context, channelId);
117 if (!status)
118 {
119 WLog_ERR(TAG, "context->onChannelIdAssigned failed!");
120 goto out_close;
121 }
122
123 if (!WTSVirtualChannelQuery(priv->channelHandle, WTSVirtualEventHandle, &buffer,
124 &bytesReturned) ||
125 (bytesReturned != sizeof(HANDLE)))
126 {
127 WLog_ERR(TAG,
128 "WTSVirtualChannelQuery failed or invalid invalid returned size(%" PRIu32 ")!",
129 bytesReturned);
130 if (buffer)
131 WTSFreeMemory(buffer);
132 goto out_close;
133 }
134 priv->eventHandle = *(HANDLE*)buffer;
135 WTSFreeMemory(buffer);
136
137 return CHANNEL_RC_OK;
138
139out_close:
140 (void)WTSVirtualChannelClose(priv->channelHandle);
141 return CHANNEL_RC_INITIALIZATION_ERROR;
142}
143
144void rdpei_server_context_reset(RdpeiServerContext* context)
145{
146 RdpeiServerPrivate* priv = context->priv;
147
148 priv->channelHandle = INVALID_HANDLE_VALUE;
149 priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
150 priv->waitingHeaders = TRUE;
151 priv->automataState = STATE_INITIAL;
152 Stream_SetPosition(priv->inputStream, 0);
153}
154
155void rdpei_server_context_free(RdpeiServerContext* context)
156{
157 RdpeiServerPrivate* priv = NULL;
158
159 if (!context)
160 return;
161 priv = context->priv;
162 if (priv)
163 {
164 if (priv->channelHandle != INVALID_HANDLE_VALUE)
165 (void)WTSVirtualChannelClose(priv->channelHandle);
166 Stream_Free(priv->inputStream, TRUE);
167 }
168 free(priv);
169 free(context);
170}
171
172HANDLE rdpei_server_get_event_handle(RdpeiServerContext* context)
173{
174 return context->priv->eventHandle;
175}
176
182static UINT read_cs_ready_message(RdpeiServerContext* context, wStream* s)
183{
184 UINT error = CHANNEL_RC_OK;
185 if (!Stream_CheckAndLogRequiredLength(TAG, s, 10))
186 return ERROR_INVALID_DATA;
187
188 Stream_Read_UINT32(s, context->protocolFlags);
189 Stream_Read_UINT32(s, context->clientVersion);
190 Stream_Read_UINT16(s, context->maxTouchPoints);
191
192 switch (context->clientVersion)
193 {
194 case RDPINPUT_PROTOCOL_V10:
195 case RDPINPUT_PROTOCOL_V101:
196 case RDPINPUT_PROTOCOL_V200:
197 case RDPINPUT_PROTOCOL_V300:
198 break;
199 default:
200 WLog_ERR(TAG, "unhandled RPDEI protocol version 0x%" PRIx32 "", context->clientVersion);
201 break;
202 }
203
204 IFCALLRET(context->onClientReady, error, context);
205 if (error)
206 WLog_ERR(TAG, "context->onClientReady failed with error %" PRIu32 "", error);
207
208 return error;
209}
210
216static UINT read_touch_contact_data(RdpeiServerContext* context, wStream* s,
217 RDPINPUT_CONTACT_DATA* contactData)
218{
219 WINPR_UNUSED(context);
220 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
221 return ERROR_INVALID_DATA;
222
223 Stream_Read_UINT8(s, contactData->contactId);
224 if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
225 !rdpei_read_4byte_signed(s, &contactData->x) ||
226 !rdpei_read_4byte_signed(s, &contactData->y) ||
227 !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
228 {
229 WLog_ERR(TAG, "rdpei_read_ failed!");
230 return ERROR_INTERNAL_ERROR;
231 }
232
233 if (contactData->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT)
234 {
235 if (!rdpei_read_2byte_signed(s, &contactData->contactRectLeft) ||
236 !rdpei_read_2byte_signed(s, &contactData->contactRectTop) ||
237 !rdpei_read_2byte_signed(s, &contactData->contactRectRight) ||
238 !rdpei_read_2byte_signed(s, &contactData->contactRectBottom))
239 {
240 WLog_ERR(TAG, "rdpei_read_ failed!");
241 return ERROR_INTERNAL_ERROR;
242 }
243 }
244
245 if ((contactData->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT) &&
246 !rdpei_read_4byte_unsigned(s, &contactData->orientation))
247 {
248 WLog_ERR(TAG, "rdpei_read_ failed!");
249 return ERROR_INTERNAL_ERROR;
250 }
251
252 if ((contactData->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT) &&
253 !rdpei_read_4byte_unsigned(s, &contactData->pressure))
254 {
255 WLog_ERR(TAG, "rdpei_read_ failed!");
256 return ERROR_INTERNAL_ERROR;
257 }
258
259 return CHANNEL_RC_OK;
260}
261
262static UINT read_pen_contact(RdpeiServerContext* context, wStream* s,
263 RDPINPUT_PEN_CONTACT* contactData)
264{
265 WINPR_UNUSED(context);
266 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
267 return ERROR_INVALID_DATA;
268
269 Stream_Read_UINT8(s, contactData->deviceId);
270 if (!rdpei_read_2byte_unsigned(s, &contactData->fieldsPresent) ||
271 !rdpei_read_4byte_signed(s, &contactData->x) ||
272 !rdpei_read_4byte_signed(s, &contactData->y) ||
273 !rdpei_read_4byte_unsigned(s, &contactData->contactFlags))
274 {
275 WLog_ERR(TAG, "rdpei_read_ failed!");
276 return ERROR_INTERNAL_ERROR;
277 }
278
279 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
280 {
281 if (!rdpei_read_4byte_unsigned(s, &contactData->penFlags))
282 return ERROR_INVALID_DATA;
283 }
284 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
285 {
286 if (!rdpei_read_4byte_unsigned(s, &contactData->pressure))
287 return ERROR_INVALID_DATA;
288 }
289 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
290 {
291 if (!rdpei_read_2byte_unsigned(s, &contactData->rotation))
292 return ERROR_INVALID_DATA;
293 }
294 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
295 {
296 if (!rdpei_read_2byte_signed(s, &contactData->tiltX))
297 return ERROR_INVALID_DATA;
298 }
299 if (contactData->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
300 {
301 if (!rdpei_read_2byte_signed(s, &contactData->tiltY))
302 return ERROR_INVALID_DATA;
303 }
304
305 return CHANNEL_RC_OK;
306}
307
313static UINT read_touch_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_TOUCH_FRAME* frame)
314{
315 RDPINPUT_CONTACT_DATA* contact = NULL;
316 UINT error = 0;
317
318 if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
319 !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
320 {
321 WLog_ERR(TAG, "rdpei_read_ failed!");
322 return ERROR_INTERNAL_ERROR;
323 }
324
325 frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_CONTACT_DATA));
326 if (!frame->contacts)
327 {
328 WLog_ERR(TAG, "calloc failed!");
329 return CHANNEL_RC_NO_MEMORY;
330 }
331
332 for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
333 {
334 if ((error = read_touch_contact_data(context, s, contact)))
335 {
336 WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
337 frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
338 touch_frame_reset(frame);
339 return error;
340 }
341 }
342 return CHANNEL_RC_OK;
343}
344
345static UINT read_pen_frame(RdpeiServerContext* context, wStream* s, RDPINPUT_PEN_FRAME* frame)
346{
347 RDPINPUT_PEN_CONTACT* contact = NULL;
348 UINT error = 0;
349
350 if (!rdpei_read_2byte_unsigned(s, &frame->contactCount) ||
351 !rdpei_read_8byte_unsigned(s, &frame->frameOffset))
352 {
353 WLog_ERR(TAG, "rdpei_read_ failed!");
354 return ERROR_INTERNAL_ERROR;
355 }
356
357 frame->contacts = contact = calloc(frame->contactCount, sizeof(RDPINPUT_PEN_CONTACT));
358 if (!frame->contacts)
359 {
360 WLog_ERR(TAG, "calloc failed!");
361 return CHANNEL_RC_NO_MEMORY;
362 }
363
364 for (UINT32 i = 0; i < frame->contactCount; i++, contact++)
365 {
366 if ((error = read_pen_contact(context, s, contact)))
367 {
368 WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
369 frame->contactCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
370
371 pen_frame_reset(frame);
372 return error;
373 }
374 }
375 return CHANNEL_RC_OK;
376}
377
383static UINT read_touch_event(RdpeiServerContext* context, wStream* s)
384{
385 UINT16 frameCount = 0;
386 RDPINPUT_TOUCH_EVENT* event = &context->priv->touchEvent;
387 RDPINPUT_TOUCH_FRAME* frame = NULL;
388 UINT error = CHANNEL_RC_OK;
389
390 if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
391 !rdpei_read_2byte_unsigned(s, &frameCount))
392 {
393 WLog_ERR(TAG, "rdpei_read_ failed!");
394 return ERROR_INTERNAL_ERROR;
395 }
396
397 event->frameCount = frameCount;
398 event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_TOUCH_FRAME));
399 if (!event->frames)
400 {
401 WLog_ERR(TAG, "calloc failed!");
402 return CHANNEL_RC_NO_MEMORY;
403 }
404
405 for (UINT32 i = 0; i < frameCount; i++, frame++)
406 {
407 if ((error = read_touch_frame(context, s, frame)))
408 {
409 WLog_ERR(TAG, "read_touch_contact_data failed with error %" PRIu32 "!", error);
410 event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
411
412 goto out_cleanup;
413 }
414 }
415
416 IFCALLRET(context->onTouchEvent, error, context, event);
417 if (error)
418 WLog_ERR(TAG, "context->onTouchEvent failed with error %" PRIu32 "", error);
419
420out_cleanup:
421 touch_event_reset(event);
422 return error;
423}
424
425static UINT read_pen_event(RdpeiServerContext* context, wStream* s)
426{
427 UINT16 frameCount = 0;
428 RDPINPUT_PEN_EVENT* event = &context->priv->penEvent;
429 RDPINPUT_PEN_FRAME* frame = NULL;
430 UINT error = CHANNEL_RC_OK;
431
432 if (!rdpei_read_4byte_unsigned(s, &event->encodeTime) ||
433 !rdpei_read_2byte_unsigned(s, &frameCount))
434 {
435 WLog_ERR(TAG, "rdpei_read_ failed!");
436 return ERROR_INTERNAL_ERROR;
437 }
438
439 event->frameCount = frameCount;
440 event->frames = frame = calloc(event->frameCount, sizeof(RDPINPUT_PEN_FRAME));
441 if (!event->frames)
442 {
443 WLog_ERR(TAG, "calloc failed!");
444 return CHANNEL_RC_NO_MEMORY;
445 }
446
447 for (UINT32 i = 0; i < frameCount; i++, frame++)
448 {
449 if ((error = read_pen_frame(context, s, frame)))
450 {
451 WLog_ERR(TAG, "read_pen_frame failed with error %" PRIu32 "!", error);
452 event->frameCount = WINPR_ASSERTING_INT_CAST(UINT16, i);
453
454 goto out_cleanup;
455 }
456 }
457
458 IFCALLRET(context->onPenEvent, error, context, event);
459 if (error)
460 WLog_ERR(TAG, "context->onPenEvent failed with error %" PRIu32 "", error);
461
462out_cleanup:
463 pen_event_reset(event);
464 return error;
465}
466
472static UINT read_dismiss_hovering_contact(RdpeiServerContext* context, wStream* s)
473{
474 BYTE contactId = 0;
475 UINT error = CHANNEL_RC_OK;
476
477 if (!Stream_CheckAndLogRequiredLength(TAG, s, 1))
478 return ERROR_INVALID_DATA;
479
480 Stream_Read_UINT8(s, contactId);
481
482 IFCALLRET(context->onTouchReleased, error, context, contactId);
483 if (error)
484 WLog_ERR(TAG, "context->onTouchReleased failed with error %" PRIu32 "", error);
485
486 return error;
487}
488
494UINT rdpei_server_handle_messages(RdpeiServerContext* context)
495{
496 DWORD bytesReturned = 0;
497 RdpeiServerPrivate* priv = context->priv;
498 wStream* s = priv->inputStream;
499 UINT error = CHANNEL_RC_OK;
500
501 if (!WTSVirtualChannelRead(priv->channelHandle, 0, Stream_Pointer(s), priv->expectedBytes,
502 &bytesReturned))
503 {
504 if (GetLastError() == ERROR_NO_DATA)
505 return ERROR_READ_FAULT;
506
507 WLog_DBG(TAG, "channel connection closed");
508 return CHANNEL_RC_OK;
509 }
510 priv->expectedBytes -= bytesReturned;
511 Stream_Seek(s, bytesReturned);
512
513 if (priv->expectedBytes)
514 return CHANNEL_RC_OK;
515
516 Stream_SealLength(s);
517 Stream_SetPosition(s, 0);
518
519 if (priv->waitingHeaders)
520 {
521 UINT32 pduLen = 0;
522
523 /* header case */
524 Stream_Read_UINT16(s, priv->currentMsgType);
525 Stream_Read_UINT16(s, pduLen);
526
527 if (pduLen < RDPINPUT_HEADER_LENGTH)
528 {
529 WLog_ERR(TAG, "invalid pduLength %" PRIu32 "", pduLen);
530 return ERROR_INVALID_DATA;
531 }
532 priv->expectedBytes = pduLen - RDPINPUT_HEADER_LENGTH;
533 priv->waitingHeaders = FALSE;
534 Stream_SetPosition(s, 0);
535 if (priv->expectedBytes)
536 {
537 if (!Stream_EnsureCapacity(s, priv->expectedBytes))
538 {
539 WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
540 return CHANNEL_RC_NO_MEMORY;
541 }
542 return CHANNEL_RC_OK;
543 }
544 }
545
546 /* when here we have the header + the body */
547 switch (priv->currentMsgType)
548 {
549 case EVENTID_CS_READY:
550 if (priv->automataState != STATE_WAITING_CLIENT_READY)
551 {
552 WLog_ERR(TAG, "not expecting a CS_READY packet in this state(%d)",
553 priv->automataState);
554 return ERROR_INVALID_STATE;
555 }
556
557 if ((error = read_cs_ready_message(context, s)))
558 {
559 WLog_ERR(TAG, "read_cs_ready_message failed with error %" PRIu32 "", error);
560 return error;
561 }
562 break;
563
564 case EVENTID_TOUCH:
565 if ((error = read_touch_event(context, s)))
566 {
567 WLog_ERR(TAG, "read_touch_event failed with error %" PRIu32 "", error);
568 return error;
569 }
570 break;
571 case EVENTID_DISMISS_HOVERING_CONTACT:
572 if ((error = read_dismiss_hovering_contact(context, s)))
573 {
574 WLog_ERR(TAG, "read_dismiss_hovering_contact failed with error %" PRIu32 "", error);
575 return error;
576 }
577 break;
578 case EVENTID_PEN:
579 if ((error = read_pen_event(context, s)))
580 {
581 WLog_ERR(TAG, "read_pen_event failed with error %" PRIu32 "", error);
582 return error;
583 }
584 break;
585 default:
586 WLog_ERR(TAG, "unexpected message type 0x%" PRIx16 "", priv->currentMsgType);
587 }
588
589 Stream_SetPosition(s, 0);
590 priv->waitingHeaders = TRUE;
591 priv->expectedBytes = RDPINPUT_HEADER_LENGTH;
592 return error;
593}
594
600UINT rdpei_server_send_sc_ready(RdpeiServerContext* context, UINT32 version, UINT32 features)
601{
602 ULONG written = 0;
603 RdpeiServerPrivate* priv = context->priv;
604 UINT32 pduLen = 4;
605
606 if (priv->automataState != STATE_INITIAL)
607 {
608 WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
609 return ERROR_INVALID_STATE;
610 }
611
612 Stream_SetPosition(priv->outputStream, 0);
613
614 if (version >= RDPINPUT_PROTOCOL_V300)
615 pduLen += 4;
616
617 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen))
618 {
619 WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
620 return CHANNEL_RC_NO_MEMORY;
621 }
622
623 Stream_Write_UINT16(priv->outputStream, EVENTID_SC_READY);
624 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH + pduLen);
625 Stream_Write_UINT32(priv->outputStream, version);
626 if (version >= RDPINPUT_PROTOCOL_V300)
627 Stream_Write_UINT32(priv->outputStream, features);
628
629 const size_t pos = Stream_GetPosition(priv->outputStream);
630
631 WINPR_ASSERT(pos <= UINT32_MAX);
632 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char),
633 (ULONG)pos, &written))
634 {
635 WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
636 return ERROR_INTERNAL_ERROR;
637 }
638
639 priv->automataState = STATE_WAITING_CLIENT_READY;
640 return CHANNEL_RC_OK;
641}
642
648UINT rdpei_server_suspend(RdpeiServerContext* context)
649{
650 ULONG written = 0;
651 RdpeiServerPrivate* priv = context->priv;
652
653 switch (priv->automataState)
654 {
655 case STATE_SUSPENDED:
656 WLog_ERR(TAG, "already suspended");
657 return CHANNEL_RC_OK;
658 case STATE_WAITING_FRAME:
659 break;
660 default:
661 WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
662 return ERROR_INVALID_STATE;
663 }
664
665 Stream_SetPosition(priv->outputStream, 0);
666 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
667 {
668 WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
669 return CHANNEL_RC_NO_MEMORY;
670 }
671
672 Stream_Write_UINT16(priv->outputStream, EVENTID_SUSPEND_TOUCH);
673 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
674
675 const size_t pos = Stream_GetPosition(priv->outputStream);
676
677 WINPR_ASSERT(pos <= UINT32_MAX);
678 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char),
679 (ULONG)pos, &written))
680 {
681 WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
682 return ERROR_INTERNAL_ERROR;
683 }
684
685 priv->automataState = STATE_SUSPENDED;
686 return CHANNEL_RC_OK;
687}
688
694UINT rdpei_server_resume(RdpeiServerContext* context)
695{
696 ULONG written = 0;
697 RdpeiServerPrivate* priv = context->priv;
698
699 switch (priv->automataState)
700 {
701 case STATE_WAITING_FRAME:
702 WLog_ERR(TAG, "not suspended");
703 return CHANNEL_RC_OK;
704 case STATE_SUSPENDED:
705 break;
706 default:
707 WLog_ERR(TAG, "called from unexpected state %d", priv->automataState);
708 return ERROR_INVALID_STATE;
709 }
710
711 Stream_SetPosition(priv->outputStream, 0);
712 if (!Stream_EnsureCapacity(priv->outputStream, RDPINPUT_HEADER_LENGTH))
713 {
714 WLog_ERR(TAG, "Stream_EnsureCapacity failed!");
715 return CHANNEL_RC_NO_MEMORY;
716 }
717
718 Stream_Write_UINT16(priv->outputStream, EVENTID_RESUME_TOUCH);
719 Stream_Write_UINT32(priv->outputStream, RDPINPUT_HEADER_LENGTH);
720
721 const size_t pos = Stream_GetPosition(priv->outputStream);
722
723 WINPR_ASSERT(pos <= UINT32_MAX);
724 if (!WTSVirtualChannelWrite(priv->channelHandle, Stream_BufferAs(priv->outputStream, char),
725 (ULONG)pos, &written))
726 {
727 WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
728 return ERROR_INTERNAL_ERROR;
729 }
730
731 priv->automataState = STATE_WAITING_FRAME;
732 return CHANNEL_RC_OK;
733}
a touch event with some frames
a touch event with some frames
a frame containing contact points