Coverage Report

Created: 2026-02-26 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/channels/rdpei/client/rdpei_main.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Input Virtual Channel Extension
4
 *
5
 * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 * Copyright 2015 Thincast Technologies GmbH
7
 * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *     http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
22
#include <freerdp/config.h>
23
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <stdint.h>
28
29
#include <winpr/crt.h>
30
#include <winpr/cast.h>
31
#include <winpr/synch.h>
32
#include <winpr/thread.h>
33
#include <winpr/stream.h>
34
#include <winpr/sysinfo.h>
35
#include <winpr/cmdline.h>
36
#include <winpr/collections.h>
37
38
#include <freerdp/addin.h>
39
#include <freerdp/freerdp.h>
40
#include <freerdp/client/channels.h>
41
42
#include "rdpei_common.h"
43
44
#include "rdpei_main.h"
45
46
0
#define RDPEI_TAG CHANNELS_TAG("rdpei.client")
47
48
/**
49
 * Touch Input
50
 * http://msdn.microsoft.com/en-us/library/windows/desktop/dd562197/
51
 *
52
 * Windows Touch Input
53
 * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317321/
54
 *
55
 * Input: Touch injection sample
56
 * http://code.msdn.microsoft.com/windowsdesktop/Touch-Injection-Sample-444d9bf7
57
 *
58
 * Pointer Input Message Reference
59
 * http://msdn.microsoft.com/en-us/library/hh454916/
60
 *
61
 * POINTER_INFO Structure
62
 * http://msdn.microsoft.com/en-us/library/hh454907/
63
 *
64
 * POINTER_TOUCH_INFO Structure
65
 * http://msdn.microsoft.com/en-us/library/hh454910/
66
 */
67
68
0
#define MAX_CONTACTS 64
69
0
#define MAX_PEN_CONTACTS 4
70
71
typedef struct
72
{
73
  GENERIC_DYNVC_PLUGIN base;
74
75
  RdpeiClientContext* context;
76
77
  UINT32 version;
78
  UINT32 features; /* SC_READY_MULTIPEN_INJECTION_SUPPORTED */
79
  UINT16 maxTouchContacts;
80
  UINT64 currentFrameTime;
81
  UINT64 previousFrameTime;
82
  RDPINPUT_CONTACT_POINT contactPoints[MAX_CONTACTS];
83
84
  UINT64 currentPenFrameTime;
85
  UINT64 previousPenFrameTime;
86
  UINT16 maxPenContacts;
87
  RDPINPUT_PEN_CONTACT_POINT penContactPoints[MAX_PEN_CONTACTS];
88
89
  CRITICAL_SECTION lock;
90
  rdpContext* rdpcontext;
91
92
  HANDLE thread;
93
94
  HANDLE event;
95
  UINT64 lastPollEventTime;
96
  BOOL running;
97
  BOOL async;
98
} RDPEI_PLUGIN;
99
100
/**
101
 * Function description
102
 *
103
 * @return 0 on success, otherwise a Win32 error code
104
 */
105
static UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame);
106
107
#ifdef WITH_DEBUG_RDPEI
108
static const char* rdpei_eventid_string(UINT16 event)
109
{
110
  switch (event)
111
  {
112
    case EVENTID_SC_READY:
113
      return "EVENTID_SC_READY";
114
    case EVENTID_CS_READY:
115
      return "EVENTID_CS_READY";
116
    case EVENTID_TOUCH:
117
      return "EVENTID_TOUCH";
118
    case EVENTID_SUSPEND_TOUCH:
119
      return "EVENTID_SUSPEND_TOUCH";
120
    case EVENTID_RESUME_TOUCH:
121
      return "EVENTID_RESUME_TOUCH";
122
    case EVENTID_DISMISS_HOVERING_CONTACT:
123
      return "EVENTID_DISMISS_HOVERING_CONTACT";
124
    case EVENTID_PEN:
125
      return "EVENTID_PEN";
126
    default:
127
      return "EVENTID_UNKNOWN";
128
  }
129
}
130
#endif
131
132
static RDPINPUT_CONTACT_POINT* rdpei_contact(RDPEI_PLUGIN* rdpei, INT32 externalId, BOOL active)
133
0
{
134
0
  for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++)
135
0
  {
136
0
    RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i];
137
138
0
    if (!contactPoint->active && active)
139
0
      continue;
140
0
    else if (!contactPoint->active && !active)
141
0
    {
142
0
      contactPoint->contactId = i;
143
0
      contactPoint->externalId = externalId;
144
0
      contactPoint->active = TRUE;
145
0
      return contactPoint;
146
0
    }
147
0
    else if (contactPoint->externalId == externalId)
148
0
    {
149
0
      return contactPoint;
150
0
    }
151
0
  }
152
0
  return NULL;
153
0
}
154
155
/**
156
 * Function description
157
 *
158
 * @return 0 on success, otherwise a Win32 error code
159
 */
160
static UINT rdpei_add_frame(RdpeiClientContext* context)
161
0
{
162
0
  RDPINPUT_TOUCH_FRAME frame = WINPR_C_ARRAY_INIT;
163
0
  RDPINPUT_CONTACT_DATA contacts[MAX_CONTACTS] = WINPR_C_ARRAY_INIT;
164
165
0
  if (!context || !context->handle)
166
0
    return ERROR_INTERNAL_ERROR;
167
168
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
169
0
  frame.contacts = contacts;
170
171
0
  for (UINT16 i = 0; i < rdpei->maxTouchContacts; i++)
172
0
  {
173
0
    RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[i];
174
0
    RDPINPUT_CONTACT_DATA* contact = &contactPoint->data;
175
176
0
    if (contactPoint->dirty)
177
0
    {
178
0
      contacts[frame.contactCount] = *contact;
179
0
      rdpei->contactPoints[i].dirty = FALSE;
180
0
      frame.contactCount++;
181
0
    }
182
0
    else if (contactPoint->active)
183
0
    {
184
0
      if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_DOWN)
185
0
      {
186
0
        contact->contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE;
187
0
        contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE;
188
0
        contact->contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT;
189
0
      }
190
191
0
      contacts[frame.contactCount] = *contact;
192
0
      frame.contactCount++;
193
0
    }
194
0
    if (contact->contactFlags & RDPINPUT_CONTACT_FLAG_UP)
195
0
    {
196
0
      contactPoint->active = FALSE;
197
0
      contactPoint->externalId = 0;
198
0
      contactPoint->contactId = 0;
199
0
    }
200
0
  }
201
202
0
  if (frame.contactCount > 0)
203
0
  {
204
0
    UINT error = rdpei_send_frame(context, &frame);
205
0
    if (error != CHANNEL_RC_OK)
206
0
    {
207
0
      WLog_Print(rdpei->base.log, WLOG_ERROR,
208
0
                 "rdpei_send_frame failed with error %" PRIu32 "!", error);
209
0
      return error;
210
0
    }
211
0
  }
212
0
  return CHANNEL_RC_OK;
213
0
}
214
215
/**
216
 * Function description
217
 *
218
 * @return 0 on success, otherwise a Win32 error code
219
 */
220
static UINT rdpei_send_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s, UINT16 eventId,
221
                           size_t pduLength)
222
0
{
223
0
  if (!callback || !s || !callback->channel || !callback->channel->Write)
224
0
    return ERROR_INTERNAL_ERROR;
225
226
0
  if (pduLength > UINT32_MAX)
227
0
    return ERROR_INVALID_PARAMETER;
228
229
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
230
0
  if (!rdpei)
231
0
    return ERROR_INTERNAL_ERROR;
232
233
0
  Stream_SetPosition(s, 0);
234
0
  Stream_Write_UINT16(s, eventId);   /* eventId (2 bytes) */
235
0
  Stream_Write_UINT32(s, (UINT32)pduLength); /* pduLength (4 bytes) */
236
0
  Stream_SetPosition(s, Stream_Length(s));
237
0
  const UINT status = callback->channel->Write(callback->channel, (UINT32)Stream_Length(s),
238
0
                                               Stream_Buffer(s), NULL);
239
#ifdef WITH_DEBUG_RDPEI
240
  WLog_Print(rdpei->base.log, WLOG_DEBUG,
241
             "rdpei_send_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 " status: %" PRIu32 "",
242
             eventId, rdpei_eventid_string(eventId), pduLength, status);
243
#endif
244
0
  return status;
245
0
}
246
247
static UINT rdpei_write_pen_frame(wStream* s, const RDPINPUT_PEN_FRAME* frame)
248
0
{
249
0
  if (!s || !frame)
250
0
    return ERROR_INTERNAL_ERROR;
251
252
0
  if (!rdpei_write_2byte_unsigned(s, frame->contactCount))
253
0
    return ERROR_OUTOFMEMORY;
254
0
  if (!rdpei_write_8byte_unsigned(s, frame->frameOffset))
255
0
    return ERROR_OUTOFMEMORY;
256
0
  for (UINT16 x = 0; x < frame->contactCount; x++)
257
0
  {
258
0
    const RDPINPUT_PEN_CONTACT* contact = &frame->contacts[x];
259
260
0
    if (!Stream_EnsureRemainingCapacity(s, 1))
261
0
      return ERROR_OUTOFMEMORY;
262
0
    Stream_Write_UINT8(s, contact->deviceId);
263
0
    if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent))
264
0
      return ERROR_OUTOFMEMORY;
265
0
    if (!rdpei_write_4byte_signed(s, contact->x))
266
0
      return ERROR_OUTOFMEMORY;
267
0
    if (!rdpei_write_4byte_signed(s, contact->y))
268
0
      return ERROR_OUTOFMEMORY;
269
0
    if (!rdpei_write_4byte_unsigned(s, contact->contactFlags))
270
0
      return ERROR_OUTOFMEMORY;
271
0
    if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
272
0
    {
273
0
      if (!rdpei_write_4byte_unsigned(s, contact->penFlags))
274
0
        return ERROR_OUTOFMEMORY;
275
0
    }
276
0
    if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
277
0
    {
278
0
      if (!rdpei_write_4byte_unsigned(s, contact->pressure))
279
0
        return ERROR_OUTOFMEMORY;
280
0
    }
281
0
    if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
282
0
    {
283
0
      if (!rdpei_write_2byte_unsigned(s, contact->rotation))
284
0
        return ERROR_OUTOFMEMORY;
285
0
    }
286
0
    if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
287
0
    {
288
0
      if (!rdpei_write_2byte_signed(s, contact->tiltX))
289
0
        return ERROR_OUTOFMEMORY;
290
0
    }
291
0
    if (contact->fieldsPresent & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
292
0
    {
293
0
      if (!rdpei_write_2byte_signed(s, contact->tiltY))
294
0
        return ERROR_OUTOFMEMORY;
295
0
    }
296
0
  }
297
0
  return CHANNEL_RC_OK;
298
0
}
299
300
static UINT rdpei_send_pen_event_pdu(GENERIC_CHANNEL_CALLBACK* callback, size_t frameOffset,
301
                                     const RDPINPUT_PEN_FRAME* frames, size_t count)
302
0
{
303
0
  UINT status = ERROR_OUTOFMEMORY;
304
0
  WINPR_ASSERT(callback);
305
306
0
  if (frameOffset > UINT32_MAX)
307
0
    return ERROR_INVALID_PARAMETER;
308
0
  if (count > UINT16_MAX)
309
0
    return ERROR_INVALID_PARAMETER;
310
311
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
312
0
  if (!rdpei)
313
0
    return ERROR_INTERNAL_ERROR;
314
315
0
  if (!frames || (count == 0))
316
0
    return ERROR_INTERNAL_ERROR;
317
318
0
  wStream* s = Stream_New(NULL, 64);
319
320
0
  if (!s)
321
0
  {
322
0
    WLog_Print(rdpei->base.log, WLOG_ERROR, "Stream_New failed!");
323
0
    return CHANNEL_RC_NO_MEMORY;
324
0
  }
325
326
0
  Stream_Seek(s, RDPINPUT_HEADER_LENGTH);
327
  /**
328
   * the time that has elapsed (in milliseconds) from when the oldest touch frame
329
   * was generated to when it was encoded for transmission by the client.
330
   */
331
0
  if (!rdpei_write_4byte_unsigned(
332
0
          s, (UINT32)frameOffset)) /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */
333
0
    goto fail;
334
0
  if (!rdpei_write_2byte_unsigned(s, (UINT16)count)) /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */
335
0
    goto fail;
336
337
0
  for (size_t x = 0; x < count; x++)
338
0
  {
339
0
    status = rdpei_write_pen_frame(s, &frames[x]);
340
0
    if (status)
341
0
    {
342
0
      WLog_Print(rdpei->base.log, WLOG_ERROR,
343
0
                 "rdpei_write_pen_frame failed with error %" PRIu32 "!", status);
344
0
      goto fail;
345
0
    }
346
0
  }
347
0
  Stream_SealLength(s);
348
349
0
  status = rdpei_send_pdu(callback, s, EVENTID_PEN, Stream_Length(s));
350
0
fail:
351
0
  Stream_Free(s, TRUE);
352
0
  return status;
353
0
}
354
355
static UINT rdpei_send_pen_frame(RdpeiClientContext* context, RDPINPUT_PEN_FRAME* frame)
356
0
{
357
0
  const UINT64 currentTime = GetTickCount64();
358
359
0
  if (!context)
360
0
    return ERROR_INTERNAL_ERROR;
361
362
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
363
0
  if (!rdpei || !rdpei->base.listener_callback)
364
0
    return ERROR_INTERNAL_ERROR;
365
0
  if (!rdpei || !rdpei->rdpcontext)
366
0
    return ERROR_INTERNAL_ERROR;
367
0
  if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput))
368
0
    return CHANNEL_RC_OK;
369
370
0
  GENERIC_CHANNEL_CALLBACK* callback = rdpei->base.listener_callback->channel_callback;
371
  /* Just ignore the event if the channel is not connected */
372
0
  if (!callback)
373
0
    return CHANNEL_RC_OK;
374
375
0
  if (!rdpei->previousPenFrameTime && !rdpei->currentPenFrameTime)
376
0
  {
377
0
    rdpei->currentPenFrameTime = currentTime;
378
0
    frame->frameOffset = 0;
379
0
  }
380
0
  else
381
0
  {
382
0
    rdpei->currentPenFrameTime = currentTime;
383
0
    frame->frameOffset = rdpei->currentPenFrameTime - rdpei->previousPenFrameTime;
384
0
  }
385
386
0
  const size_t off = WINPR_ASSERTING_INT_CAST(size_t, frame->frameOffset);
387
0
  const UINT error = rdpei_send_pen_event_pdu(callback, off, frame, 1);
388
0
  if (error)
389
0
    return error;
390
391
0
  rdpei->previousPenFrameTime = rdpei->currentPenFrameTime;
392
0
  return error;
393
0
}
394
395
static UINT rdpei_add_pen_frame(RdpeiClientContext* context)
396
0
{
397
0
  RDPINPUT_PEN_FRAME penFrame = WINPR_C_ARRAY_INIT;
398
0
  RDPINPUT_PEN_CONTACT penContacts[MAX_PEN_CONTACTS] = WINPR_C_ARRAY_INIT;
399
400
0
  if (!context || !context->handle)
401
0
    return ERROR_INTERNAL_ERROR;
402
403
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
404
405
0
  penFrame.contacts = penContacts;
406
407
0
  for (UINT16 i = 0; i < rdpei->maxPenContacts; i++)
408
0
  {
409
0
    RDPINPUT_PEN_CONTACT_POINT* contact = &(rdpei->penContactPoints[i]);
410
411
0
    if (contact->dirty)
412
0
    {
413
0
      penContacts[penFrame.contactCount++] = contact->data;
414
0
      contact->dirty = FALSE;
415
0
    }
416
0
    else if (contact->active)
417
0
    {
418
0
      if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_DOWN)
419
0
      {
420
0
        contact->data.contactFlags = RDPINPUT_CONTACT_FLAG_UPDATE;
421
0
        contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INRANGE;
422
0
        contact->data.contactFlags |= RDPINPUT_CONTACT_FLAG_INCONTACT;
423
0
      }
424
425
0
      penContacts[penFrame.contactCount++] = contact->data;
426
0
    }
427
0
    if (contact->data.contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED)
428
0
    {
429
0
      contact->externalId = 0;
430
0
      contact->active = FALSE;
431
0
    }
432
0
  }
433
434
0
  if (penFrame.contactCount > 0)
435
0
    return rdpei_send_pen_frame(context, &penFrame);
436
0
  return CHANNEL_RC_OK;
437
0
}
438
439
static UINT rdpei_update(wLog* log, RdpeiClientContext* context)
440
0
{
441
0
  UINT error = rdpei_add_frame(context);
442
0
  if (error != CHANNEL_RC_OK)
443
0
  {
444
0
    WLog_Print(log, WLOG_ERROR, "rdpei_add_frame failed with error %" PRIu32 "!", error);
445
0
    return error;
446
0
  }
447
448
0
  return rdpei_add_pen_frame(context);
449
0
}
450
451
static BOOL rdpei_poll_run_unlocked(rdpContext* context, void* userdata)
452
0
{
453
0
  RDPEI_PLUGIN* rdpei = userdata;
454
0
  WINPR_ASSERT(rdpei);
455
0
  WINPR_ASSERT(context);
456
457
0
  const UINT64 now = GetTickCount64();
458
459
  /* Send an event every ~20ms */
460
0
  if ((now < rdpei->lastPollEventTime) || (now - rdpei->lastPollEventTime < 20ULL))
461
0
    return TRUE;
462
463
0
  rdpei->lastPollEventTime = now;
464
465
0
  const UINT error = rdpei_update(rdpei->base.log, rdpei->context);
466
467
0
  (void)ResetEvent(rdpei->event);
468
469
0
  if (error != CHANNEL_RC_OK)
470
0
  {
471
0
    WLog_Print(rdpei->base.log, WLOG_ERROR, "rdpei_add_frame failed with error %" PRIu32 "!",
472
0
               error);
473
0
    setChannelError(context, error, "rdpei_add_frame reported an error");
474
0
    return FALSE;
475
0
  }
476
477
0
  return TRUE;
478
0
}
479
480
static BOOL rdpei_poll_run(rdpContext* context, void* userdata)
481
0
{
482
0
  RDPEI_PLUGIN* rdpei = userdata;
483
0
  WINPR_ASSERT(rdpei);
484
485
0
  EnterCriticalSection(&rdpei->lock);
486
0
  BOOL rc = rdpei_poll_run_unlocked(context, userdata);
487
0
  LeaveCriticalSection(&rdpei->lock);
488
0
  return rc;
489
0
}
490
491
static DWORD WINAPI rdpei_periodic_update(LPVOID arg)
492
0
{
493
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)arg;
494
0
  UINT error = CHANNEL_RC_OK;
495
496
0
  if (!rdpei)
497
0
  {
498
0
    error = ERROR_INVALID_PARAMETER;
499
0
    goto out;
500
0
  }
501
502
0
  if (!rdpei->context)
503
0
  {
504
0
    error = ERROR_INVALID_PARAMETER;
505
0
    goto out;
506
0
  }
507
508
0
  while (rdpei->running)
509
0
  {
510
0
    const DWORD status = WaitForSingleObject(rdpei->event, 20);
511
512
0
    if (status == WAIT_FAILED)
513
0
    {
514
0
      error = GetLastError();
515
0
      WLog_Print(rdpei->base.log, WLOG_ERROR,
516
0
                 "WaitForMultipleObjects failed with error %" PRIu32 "!", error);
517
0
      break;
518
0
    }
519
520
0
    if (!rdpei_poll_run(rdpei->rdpcontext, rdpei))
521
0
      error = ERROR_INTERNAL_ERROR;
522
0
  }
523
524
0
out:
525
526
0
  if (error && rdpei && rdpei->rdpcontext)
527
0
    setChannelError(rdpei->rdpcontext, error, "rdpei_schedule_thread reported an error");
528
529
0
  if (rdpei)
530
0
    rdpei->running = FALSE;
531
532
0
  ExitThread(error);
533
0
  return error;
534
0
}
535
536
/**
537
 * Function description
538
 *
539
 * @return 0 on success, otherwise a Win32 error code
540
 */
541
static UINT rdpei_send_cs_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback)
542
0
{
543
0
  if (!callback || !callback->plugin)
544
0
    return ERROR_INTERNAL_ERROR;
545
546
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
547
548
0
  UINT32 flags = CS_READY_FLAGS_SHOW_TOUCH_VISUALS & rdpei->context->clientFeaturesMask;
549
0
  if (rdpei->version > RDPINPUT_PROTOCOL_V10)
550
0
    flags |= CS_READY_FLAGS_DISABLE_TIMESTAMP_INJECTION & rdpei->context->clientFeaturesMask;
551
0
  if (rdpei->features & SC_READY_MULTIPEN_INJECTION_SUPPORTED)
552
0
    flags |= CS_READY_FLAGS_ENABLE_MULTIPEN_INJECTION & rdpei->context->clientFeaturesMask;
553
554
0
  UINT32 pduLength = RDPINPUT_HEADER_LENGTH + 10;
555
0
  wStream* s = Stream_New(NULL, pduLength);
556
557
0
  if (!s)
558
0
  {
559
0
    WLog_Print(rdpei->base.log, WLOG_ERROR, "Stream_New failed!");
560
0
    return CHANNEL_RC_NO_MEMORY;
561
0
  }
562
563
0
  Stream_Seek(s, RDPINPUT_HEADER_LENGTH);
564
0
  Stream_Write_UINT32(s, flags);                   /* flags (4 bytes) */
565
0
  Stream_Write_UINT32(s, rdpei->version);          /* protocolVersion (4 bytes) */
566
0
  Stream_Write_UINT16(s, rdpei->maxTouchContacts); /* maxTouchContacts (2 bytes) */
567
0
  Stream_SealLength(s);
568
569
0
  const UINT status = rdpei_send_pdu(callback, s, EVENTID_CS_READY, pduLength);
570
0
  Stream_Free(s, TRUE);
571
0
  return status;
572
0
}
573
574
#if defined(WITH_DEBUG_RDPEI)
575
static void rdpei_print_contact_flags(wLog* log, UINT32 contactFlags)
576
{
577
  if (contactFlags & RDPINPUT_CONTACT_FLAG_DOWN)
578
    WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_DOWN");
579
580
  if (contactFlags & RDPINPUT_CONTACT_FLAG_UPDATE)
581
    WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_UPDATE");
582
583
  if (contactFlags & RDPINPUT_CONTACT_FLAG_UP)
584
    WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_UP");
585
586
  if (contactFlags & RDPINPUT_CONTACT_FLAG_INRANGE)
587
    WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_INRANGE");
588
589
  if (contactFlags & RDPINPUT_CONTACT_FLAG_INCONTACT)
590
    WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_INCONTACT");
591
592
  if (contactFlags & RDPINPUT_CONTACT_FLAG_CANCELED)
593
    WLog_Print(log, WLOG_DEBUG, " RDPINPUT_CONTACT_FLAG_CANCELED");
594
}
595
#endif
596
597
static INT16 bounded(INT32 val)
598
0
{
599
0
  if (val < INT16_MIN)
600
0
    return INT16_MIN;
601
0
  if (val > INT16_MAX)
602
0
    return INT16_MAX;
603
0
  return (INT16)val;
604
0
}
605
606
/**
607
 * Function description
608
 *
609
 * @return 0 on success, otherwise a Win32 error code
610
 */
611
static UINT rdpei_write_touch_frame(wLog* log, wStream* s, RDPINPUT_TOUCH_FRAME* frame)
612
0
{
613
0
  int rectSize = 2;
614
0
  if (!s || !frame)
615
0
    return ERROR_INTERNAL_ERROR;
616
#ifdef WITH_DEBUG_RDPEI
617
  WLog_Print(log, WLOG_DEBUG, "contactCount: %" PRIu32 "", frame->contactCount);
618
  WLog_Print(log, WLOG_DEBUG, "frameOffset: 0x%016" PRIX64 "", frame->frameOffset);
619
#endif
620
0
  if (!rdpei_write_2byte_unsigned(
621
0
          s, frame->contactCount)) /* contactCount (TWO_BYTE_UNSIGNED_INTEGER) */
622
0
    return ERROR_OUTOFMEMORY;
623
  /**
624
   * the time offset from the previous frame (in microseconds).
625
   * If this is the first frame being transmitted then this field MUST be set to zero.
626
   */
627
0
  if (!rdpei_write_8byte_unsigned(s, frame->frameOffset *
628
0
                                         1000)) /* frameOffset (EIGHT_BYTE_UNSIGNED_INTEGER) */
629
0
    return ERROR_OUTOFMEMORY;
630
631
0
  if (!Stream_EnsureRemainingCapacity(s, (size_t)frame->contactCount * 64))
632
0
  {
633
0
    WLog_Print(log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
634
0
    return CHANNEL_RC_NO_MEMORY;
635
0
  }
636
637
0
  for (UINT32 index = 0; index < frame->contactCount; index++)
638
0
  {
639
0
    RDPINPUT_CONTACT_DATA* contact = &frame->contacts[index];
640
641
0
    contact->fieldsPresent |= CONTACT_DATA_CONTACTRECT_PRESENT;
642
0
    contact->contactRectLeft = bounded(contact->x - rectSize);
643
0
    contact->contactRectTop = bounded(contact->y - rectSize);
644
0
    contact->contactRectRight = bounded(contact->x + rectSize);
645
0
    contact->contactRectBottom = bounded(contact->y + rectSize);
646
#ifdef WITH_DEBUG_RDPEI
647
    WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].contactId: %" PRIu32 "", index,
648
               contact->contactId);
649
    WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].fieldsPresent: %" PRIu32 "", index,
650
               contact->fieldsPresent);
651
    WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].x: %" PRId32 "", index, contact->x);
652
    WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].y: %" PRId32 "", index, contact->y);
653
    WLog_Print(log, WLOG_DEBUG, "contact[%" PRIu32 "].contactFlags: 0x%08" PRIX32 "", index,
654
               contact->contactFlags);
655
    rdpei_print_contact_flags(log, contact->contactFlags);
656
#endif
657
0
    Stream_Write_UINT8(
658
0
        s, WINPR_ASSERTING_INT_CAST(uint8_t, contact->contactId)); /* contactId (1 byte) */
659
    /* fieldsPresent (TWO_BYTE_UNSIGNED_INTEGER) */
660
0
    if (!rdpei_write_2byte_unsigned(s, contact->fieldsPresent))
661
0
      return ERROR_OUTOFMEMORY;
662
0
    if (!rdpei_write_4byte_signed(s, contact->x)) /* x (FOUR_BYTE_SIGNED_INTEGER) */
663
0
      return ERROR_OUTOFMEMORY;
664
0
    if (!rdpei_write_4byte_signed(s, contact->y)) /* y (FOUR_BYTE_SIGNED_INTEGER) */
665
0
      return ERROR_OUTOFMEMORY;
666
    /* contactFlags (FOUR_BYTE_UNSIGNED_INTEGER) */
667
0
    if (!rdpei_write_4byte_unsigned(s, contact->contactFlags))
668
0
      return ERROR_OUTOFMEMORY;
669
670
0
    if (contact->fieldsPresent & CONTACT_DATA_CONTACTRECT_PRESENT)
671
0
    {
672
      /* contactRectLeft (TWO_BYTE_SIGNED_INTEGER) */
673
0
      if (!rdpei_write_2byte_signed(s, contact->contactRectLeft))
674
0
        return ERROR_OUTOFMEMORY;
675
      /* contactRectTop (TWO_BYTE_SIGNED_INTEGER) */
676
0
      if (!rdpei_write_2byte_signed(s, contact->contactRectTop))
677
0
        return ERROR_OUTOFMEMORY;
678
      /* contactRectRight (TWO_BYTE_SIGNED_INTEGER) */
679
0
      if (!rdpei_write_2byte_signed(s, contact->contactRectRight))
680
0
        return ERROR_OUTOFMEMORY;
681
      /* contactRectBottom (TWO_BYTE_SIGNED_INTEGER) */
682
0
      if (!rdpei_write_2byte_signed(s, contact->contactRectBottom))
683
0
        return ERROR_OUTOFMEMORY;
684
0
    }
685
686
0
    if (contact->fieldsPresent & CONTACT_DATA_ORIENTATION_PRESENT)
687
0
    {
688
      /* orientation (FOUR_BYTE_UNSIGNED_INTEGER) */
689
0
      if (!rdpei_write_4byte_unsigned(s, contact->orientation))
690
0
        return ERROR_OUTOFMEMORY;
691
0
    }
692
693
0
    if (contact->fieldsPresent & CONTACT_DATA_PRESSURE_PRESENT)
694
0
    {
695
      /* pressure (FOUR_BYTE_UNSIGNED_INTEGER) */
696
0
      if (!rdpei_write_4byte_unsigned(s, contact->pressure))
697
0
        return ERROR_OUTOFMEMORY;
698
0
    }
699
0
  }
700
701
0
  return CHANNEL_RC_OK;
702
0
}
703
704
/**
705
 * Function description
706
 *
707
 * @return 0 on success, otherwise a Win32 error code
708
 */
709
static UINT rdpei_send_touch_event_pdu(GENERIC_CHANNEL_CALLBACK* callback,
710
                                       RDPINPUT_TOUCH_FRAME* frame)
711
0
{
712
0
  UINT status = ERROR_OUTOFMEMORY;
713
0
  WINPR_ASSERT(callback);
714
715
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
716
0
  if (!rdpei || !rdpei->rdpcontext)
717
0
    return ERROR_INTERNAL_ERROR;
718
0
  if (freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SuspendInput))
719
0
    return CHANNEL_RC_OK;
720
721
0
  if (!frame)
722
0
    return ERROR_INTERNAL_ERROR;
723
724
0
  size_t pduLength = 64ULL + (64ULL * frame->contactCount);
725
0
  wStream* s = Stream_New(NULL, pduLength);
726
727
0
  if (!s)
728
0
  {
729
0
    WLog_Print(rdpei->base.log, WLOG_ERROR, "Stream_New failed!");
730
0
    return CHANNEL_RC_NO_MEMORY;
731
0
  }
732
733
0
  if (!Stream_SafeSeek(s, RDPINPUT_HEADER_LENGTH))
734
0
    goto fail;
735
  /**
736
   * the time that has elapsed (in milliseconds) from when the oldest touch frame
737
   * was generated to when it was encoded for transmission by the client.
738
   */
739
0
  if (!rdpei_write_4byte_unsigned(
740
0
          s, (UINT32)frame->frameOffset)) /* encodeTime (FOUR_BYTE_UNSIGNED_INTEGER) */
741
0
    goto fail;
742
0
  if (!rdpei_write_2byte_unsigned(s, 1)) /* (frameCount) TWO_BYTE_UNSIGNED_INTEGER */
743
0
    goto fail;
744
745
0
  const UINT rc = rdpei_write_touch_frame(rdpei->base.log, s, frame);
746
0
  if (rc)
747
0
  {
748
0
    WLog_Print(rdpei->base.log, WLOG_ERROR,
749
0
               "rdpei_write_touch_frame failed with error %" PRIu32 "!", rc);
750
0
    status = rc;
751
0
    goto fail;
752
0
  }
753
754
0
  Stream_SealLength(s);
755
756
0
  status = rdpei_send_pdu(callback, s, EVENTID_TOUCH, Stream_Length(s));
757
0
fail:
758
0
  Stream_Free(s, TRUE);
759
0
  return status;
760
0
}
761
762
/**
763
 * Function description
764
 *
765
 * @return 0 on success, otherwise a Win32 error code
766
 */
767
static UINT rdpei_recv_sc_ready_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
768
0
{
769
0
  UINT32 features = 0;
770
0
  UINT32 protocolVersion = 0;
771
772
0
  if (!callback || !callback->plugin)
773
0
    return ERROR_INTERNAL_ERROR;
774
775
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
776
777
0
  if (!Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, 4))
778
0
    return ERROR_INVALID_DATA;
779
0
  Stream_Read_UINT32(s, protocolVersion); /* protocolVersion (4 bytes) */
780
781
0
  if (protocolVersion >= RDPINPUT_PROTOCOL_V300)
782
0
  {
783
0
    if (!Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, 4))
784
0
      return ERROR_INVALID_DATA;
785
0
  }
786
787
0
  if (Stream_GetRemainingLength(s) >= 4)
788
0
    Stream_Read_UINT32(s, features);
789
790
0
  if (rdpei->version > protocolVersion)
791
0
    rdpei->version = protocolVersion;
792
0
  rdpei->features = features;
793
794
0
  if (protocolVersion > RDPINPUT_PROTOCOL_V300)
795
0
  {
796
0
    WLog_Print(rdpei->base.log, WLOG_WARN,
797
0
               "Unknown [MS-RDPEI] protocolVersion: 0x%08" PRIX32 "", protocolVersion);
798
0
  }
799
800
0
  return CHANNEL_RC_OK;
801
0
}
802
803
/**
804
 * Function description
805
 *
806
 * @return 0 on success, otherwise a Win32 error code
807
 */
808
static UINT rdpei_recv_suspend_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
809
0
{
810
0
  UINT error = CHANNEL_RC_OK;
811
812
0
  WINPR_UNUSED(s);
813
814
0
  if (!callback || !callback->plugin)
815
0
    return ERROR_INTERNAL_ERROR;
816
817
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
818
0
  RdpeiClientContext* context = rdpei->context;
819
0
  if (!rdpei)
820
0
    return ERROR_INTERNAL_ERROR;
821
822
0
  IFCALLRET(context->SuspendTouch, error, context);
823
824
0
  if (error)
825
0
    WLog_Print(rdpei->base.log, WLOG_ERROR,
826
0
               "rdpei->SuspendTouch failed with error %" PRIu32 "!", error);
827
828
0
  return error;
829
0
}
830
831
/**
832
 * Function description
833
 *
834
 * @return 0 on success, otherwise a Win32 error code
835
 */
836
static UINT rdpei_recv_resume_touch_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
837
0
{
838
0
  UINT error = CHANNEL_RC_OK;
839
0
  if (!s || !callback)
840
0
    return ERROR_INTERNAL_ERROR;
841
842
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
843
0
  if (!rdpei)
844
0
    return ERROR_INTERNAL_ERROR;
845
846
0
  RdpeiClientContext* context = (RdpeiClientContext*)callback->plugin->pInterface;
847
0
  if (!context)
848
0
    return ERROR_INTERNAL_ERROR;
849
850
0
  IFCALLRET(context->ResumeTouch, error, context);
851
852
0
  if (error)
853
0
    WLog_Print(rdpei->base.log, WLOG_ERROR, "rdpei->ResumeTouch failed with error %" PRIu32 "!",
854
0
               error);
855
856
0
  return error;
857
0
}
858
859
/**
860
 * Function description
861
 *
862
 * @return 0 on success, otherwise a Win32 error code
863
 */
864
static UINT rdpei_recv_pdu(GENERIC_CHANNEL_CALLBACK* callback, wStream* s)
865
0
{
866
0
  UINT16 eventId = 0;
867
0
  UINT32 pduLength = 0;
868
0
  UINT error = 0;
869
870
0
  if (!callback || !s)
871
0
    return ERROR_INTERNAL_ERROR;
872
873
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
874
0
  if (!rdpei)
875
0
    return ERROR_INTERNAL_ERROR;
876
877
0
  if (!Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, 6))
878
0
    return ERROR_INVALID_DATA;
879
880
0
  Stream_Read_UINT16(s, eventId);   /* eventId (2 bytes) */
881
0
  Stream_Read_UINT32(s, pduLength); /* pduLength (4 bytes) */
882
#ifdef WITH_DEBUG_RDPEI
883
  WLog_Print(rdpei->base.log, WLOG_DEBUG,
884
             "rdpei_recv_pdu: eventId: %" PRIu16 " (%s) length: %" PRIu32 "", eventId,
885
             rdpei_eventid_string(eventId), pduLength);
886
#endif
887
888
0
  if ((pduLength < 6) || !Stream_CheckAndLogRequiredLengthWLog(rdpei->base.log, s, pduLength - 6))
889
0
    return ERROR_INVALID_DATA;
890
891
0
  switch (eventId)
892
0
  {
893
0
    case EVENTID_SC_READY:
894
0
      if ((error = rdpei_recv_sc_ready_pdu(callback, s)))
895
0
      {
896
0
        WLog_Print(rdpei->base.log, WLOG_ERROR,
897
0
                   "rdpei_recv_sc_ready_pdu failed with error %" PRIu32 "!", error);
898
0
        return error;
899
0
      }
900
901
0
      if ((error = rdpei_send_cs_ready_pdu(callback)))
902
0
      {
903
0
        WLog_Print(rdpei->base.log, WLOG_ERROR,
904
0
                   "rdpei_send_cs_ready_pdu failed with error %" PRIu32 "!", error);
905
0
        return error;
906
0
      }
907
908
0
      break;
909
910
0
    case EVENTID_SUSPEND_TOUCH:
911
0
      if ((error = rdpei_recv_suspend_touch_pdu(callback, s)))
912
0
      {
913
0
        WLog_Print(rdpei->base.log, WLOG_ERROR,
914
0
                   "rdpei_recv_suspend_touch_pdu failed with error %" PRIu32 "!", error);
915
0
        return error;
916
0
      }
917
918
0
      break;
919
920
0
    case EVENTID_RESUME_TOUCH:
921
0
      if ((error = rdpei_recv_resume_touch_pdu(callback, s)))
922
0
      {
923
0
        WLog_Print(rdpei->base.log, WLOG_ERROR,
924
0
                   "rdpei_recv_resume_touch_pdu failed with error %" PRIu32 "!", error);
925
0
        return error;
926
0
      }
927
928
0
      break;
929
930
0
    default:
931
0
      break;
932
0
  }
933
934
0
  return CHANNEL_RC_OK;
935
0
}
936
937
/**
938
 * Function description
939
 *
940
 * @return 0 on success, otherwise a Win32 error code
941
 */
942
static UINT rdpei_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
943
0
{
944
0
  GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
945
0
  return rdpei_recv_pdu(callback, data);
946
0
}
947
948
/**
949
 * Function description
950
 *
951
 * @return 0 on success, otherwise a Win32 error code
952
 */
953
static UINT rdpei_on_close(IWTSVirtualChannelCallback* pChannelCallback)
954
0
{
955
0
  GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
956
0
  if (callback)
957
0
  {
958
0
    RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)callback->plugin;
959
0
    if (rdpei && rdpei->base.listener_callback)
960
0
    {
961
0
      if (rdpei->base.listener_callback->channel_callback == callback)
962
0
        rdpei->base.listener_callback->channel_callback = NULL;
963
0
    }
964
0
  }
965
0
  free(callback);
966
0
  return CHANNEL_RC_OK;
967
0
}
968
969
/**
970
 * Channel Client Interface
971
 */
972
973
static UINT32 rdpei_get_version(RdpeiClientContext* context)
974
0
{
975
0
  if (!context || !context->handle)
976
0
    return 0;
977
978
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
979
0
  return rdpei->version;
980
0
}
981
982
static UINT32 rdpei_get_features(RdpeiClientContext* context)
983
0
{
984
0
  if (!context || !context->handle)
985
0
    return 0;
986
987
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
988
0
  return rdpei->features;
989
0
}
990
991
/**
992
 * Function description
993
 *
994
 * @return 0 on success, otherwise a Win32 error code
995
 */
996
UINT rdpei_send_frame(RdpeiClientContext* context, RDPINPUT_TOUCH_FRAME* frame)
997
0
{
998
0
  UINT64 currentTime = GetTickCount64();
999
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
1000
1001
0
  GENERIC_CHANNEL_CALLBACK* callback = rdpei->base.listener_callback->channel_callback;
1002
1003
  /* Just ignore the event if the channel is not connected */
1004
0
  if (!callback)
1005
0
    return CHANNEL_RC_OK;
1006
1007
0
  if (!rdpei->previousFrameTime && !rdpei->currentFrameTime)
1008
0
  {
1009
0
    rdpei->currentFrameTime = currentTime;
1010
0
    frame->frameOffset = 0;
1011
0
  }
1012
0
  else
1013
0
  {
1014
0
    rdpei->currentFrameTime = currentTime;
1015
0
    frame->frameOffset = rdpei->currentFrameTime - rdpei->previousFrameTime;
1016
0
  }
1017
1018
0
  const UINT error = rdpei_send_touch_event_pdu(callback, frame);
1019
0
  if (error)
1020
0
  {
1021
0
    WLog_Print(rdpei->base.log, WLOG_ERROR,
1022
0
               "rdpei_send_touch_event_pdu failed with error %" PRIu32 "!", error);
1023
0
    return error;
1024
0
  }
1025
1026
0
  rdpei->previousFrameTime = rdpei->currentFrameTime;
1027
0
  return error;
1028
0
}
1029
1030
/**
1031
 * Function description
1032
 *
1033
 * @return 0 on success, otherwise a Win32 error code
1034
 */
1035
static UINT rdpei_add_contact(RdpeiClientContext* context, const RDPINPUT_CONTACT_DATA* contact)
1036
0
{
1037
0
  UINT error = CHANNEL_RC_OK;
1038
0
  if (!context || !contact || !context->handle)
1039
0
    return ERROR_INTERNAL_ERROR;
1040
1041
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
1042
1043
0
  EnterCriticalSection(&rdpei->lock);
1044
0
  RDPINPUT_CONTACT_POINT* contactPoint = &rdpei->contactPoints[contact->contactId];
1045
1046
0
  if (contactPoint->dirty && contactPoint->data.contactFlags != contact->contactFlags)
1047
0
  {
1048
0
    const INT32 externalId = contactPoint->externalId;
1049
0
    error = rdpei_add_frame(context);
1050
0
    if (!contactPoint->active)
1051
0
    {
1052
0
      contactPoint->active = TRUE;
1053
0
      contactPoint->externalId = externalId;
1054
0
      contactPoint->contactId = contact->contactId;
1055
0
    }
1056
0
  }
1057
1058
0
  contactPoint->data = *contact;
1059
0
  contactPoint->dirty = TRUE;
1060
0
  (void)SetEvent(rdpei->event);
1061
0
  LeaveCriticalSection(&rdpei->lock);
1062
1063
0
  return error;
1064
0
}
1065
1066
static UINT rdpei_touch_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
1067
                                INT32 x, INT32 y, INT32* contactId, UINT32 fieldFlags, va_list ap)
1068
0
{
1069
0
  INT64 contactIdlocal = -1;
1070
0
  UINT error = CHANNEL_RC_OK;
1071
1072
0
  if (!context || !contactId || !context->handle)
1073
0
    return ERROR_INTERNAL_ERROR;
1074
1075
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
1076
  /* Create a new contact point in an empty slot */
1077
0
  EnterCriticalSection(&rdpei->lock);
1078
0
  const BOOL begin = (contactFlags & RDPINPUT_CONTACT_FLAG_DOWN) != 0;
1079
0
  RDPINPUT_CONTACT_POINT* contactPoint = rdpei_contact(rdpei, externalId, !begin);
1080
0
  if (contactPoint)
1081
0
    contactIdlocal = contactPoint->contactId;
1082
1083
0
  if (contactIdlocal > UINT32_MAX)
1084
0
  {
1085
0
    error = ERROR_INVALID_PARAMETER;
1086
0
    goto fail;
1087
0
  }
1088
1089
0
  if (contactIdlocal >= 0)
1090
0
  {
1091
0
    RDPINPUT_CONTACT_DATA contact = WINPR_C_ARRAY_INIT;
1092
0
    contact.x = x;
1093
0
    contact.y = y;
1094
0
    contact.contactId = (UINT32)contactIdlocal;
1095
0
    contact.contactFlags = contactFlags;
1096
0
    contact.fieldsPresent = WINPR_ASSERTING_INT_CAST(UINT16, fieldFlags);
1097
1098
0
    if (fieldFlags & CONTACT_DATA_CONTACTRECT_PRESENT)
1099
0
    {
1100
0
      INT32 val = va_arg(ap, INT32);
1101
0
      contact.contactRectLeft = WINPR_ASSERTING_INT_CAST(INT16, val);
1102
1103
0
      val = va_arg(ap, INT32);
1104
0
      contact.contactRectTop = WINPR_ASSERTING_INT_CAST(INT16, val);
1105
1106
0
      val = va_arg(ap, INT32);
1107
0
      contact.contactRectRight = WINPR_ASSERTING_INT_CAST(INT16, val);
1108
1109
0
      val = va_arg(ap, INT32);
1110
0
      contact.contactRectBottom = WINPR_ASSERTING_INT_CAST(INT16, val);
1111
0
    }
1112
0
    if (fieldFlags & CONTACT_DATA_ORIENTATION_PRESENT)
1113
0
    {
1114
0
      UINT32 p = va_arg(ap, UINT32);
1115
0
      if (p >= 360)
1116
0
      {
1117
0
        WLog_Print(rdpei->base.log, WLOG_WARN,
1118
0
                   "TouchContact %" PRId64 ": Invalid orientation value %" PRIu32
1119
0
                   "degree, clamping to 359 degree",
1120
0
                   contactIdlocal, p);
1121
0
        p = 359;
1122
0
      }
1123
0
      contact.orientation = p;
1124
0
    }
1125
0
    if (fieldFlags & CONTACT_DATA_PRESSURE_PRESENT)
1126
0
    {
1127
0
      UINT32 p = va_arg(ap, UINT32);
1128
0
      if (p > 1024)
1129
0
      {
1130
0
        WLog_Print(rdpei->base.log, WLOG_WARN,
1131
0
                   "TouchContact %" PRId64 ": Invalid pressure value %" PRIu32
1132
0
                   ", clamping to 1024",
1133
0
                   contactIdlocal, p);
1134
0
        p = 1024;
1135
0
      }
1136
0
      contact.pressure = p;
1137
0
    }
1138
1139
0
    error = context->AddContact(context, &contact);
1140
0
  }
1141
1142
0
fail:
1143
0
  if (contactId)
1144
0
    *contactId = (INT32)contactIdlocal;
1145
1146
0
  LeaveCriticalSection(&rdpei->lock);
1147
0
  return error;
1148
0
}
1149
1150
/**
1151
 * Function description
1152
 *
1153
 * @return 0 on success, otherwise a Win32 error code
1154
 */
1155
static UINT rdpei_touch_begin(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
1156
                              INT32* contactId)
1157
0
{
1158
0
  UINT rc = 0;
1159
0
  va_list ap = WINPR_C_ARRAY_INIT;
1160
0
  rc = rdpei_touch_process(context, externalId,
1161
0
                           RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
1162
0
                               RDPINPUT_CONTACT_FLAG_INCONTACT,
1163
0
                           x, y, contactId, 0, ap);
1164
0
  return rc;
1165
0
}
1166
1167
/**
1168
 * Function description
1169
 *
1170
 * @return 0 on success, otherwise a Win32 error code
1171
 */
1172
static UINT rdpei_touch_update(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
1173
                               INT32* contactId)
1174
0
{
1175
0
  UINT rc = 0;
1176
0
  va_list ap = WINPR_C_ARRAY_INIT;
1177
0
  rc = rdpei_touch_process(context, externalId,
1178
0
                           RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
1179
0
                               RDPINPUT_CONTACT_FLAG_INCONTACT,
1180
0
                           x, y, contactId, 0, ap);
1181
0
  return rc;
1182
0
}
1183
1184
/**
1185
 * Function description
1186
 *
1187
 * @return 0 on success, otherwise a Win32 error code
1188
 */
1189
static UINT rdpei_touch_end(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
1190
                            INT32* contactId)
1191
0
{
1192
0
  UINT error = 0;
1193
0
  va_list ap = WINPR_C_ARRAY_INIT;
1194
0
  error = rdpei_touch_process(context, externalId,
1195
0
                              RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
1196
0
                                  RDPINPUT_CONTACT_FLAG_INCONTACT,
1197
0
                              x, y, contactId, 0, ap);
1198
0
  if (error != CHANNEL_RC_OK)
1199
0
    return error;
1200
0
  error =
1201
0
      rdpei_touch_process(context, externalId, RDPINPUT_CONTACT_FLAG_UP, x, y, contactId, 0, ap);
1202
0
  return error;
1203
0
}
1204
1205
/**
1206
 * Function description
1207
 *
1208
 * @return 0 on success, otherwise a Win32 error code
1209
 */
1210
static UINT rdpei_touch_cancel(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
1211
                               INT32* contactId)
1212
0
{
1213
0
  UINT rc = 0;
1214
0
  va_list ap = WINPR_C_ARRAY_INIT;
1215
0
  rc = rdpei_touch_process(context, externalId,
1216
0
                           RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_CANCELED, x, y,
1217
0
                           contactId, 0, ap);
1218
0
  return rc;
1219
0
}
1220
1221
static UINT rdpei_touch_raw_event(RdpeiClientContext* context, INT32 externalId, INT32 x, INT32 y,
1222
                                  INT32* contactId, UINT32 flags, UINT32 fieldFlags, ...)
1223
0
{
1224
0
  UINT rc = 0;
1225
0
  va_list ap = WINPR_C_ARRAY_INIT;
1226
0
  va_start(ap, fieldFlags);
1227
0
  rc = rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, ap);
1228
0
  va_end(ap);
1229
0
  return rc;
1230
0
}
1231
1232
static UINT rdpei_touch_raw_event_va(RdpeiClientContext* context, INT32 externalId, INT32 x,
1233
                                     INT32 y, INT32* contactId, UINT32 flags, UINT32 fieldFlags,
1234
                                     va_list args)
1235
0
{
1236
0
  return rdpei_touch_process(context, externalId, flags, x, y, contactId, fieldFlags, args);
1237
0
}
1238
1239
static RDPINPUT_PEN_CONTACT_POINT* rdpei_pen_contact(RDPEI_PLUGIN* rdpei, INT32 externalId,
1240
                                                     BOOL active)
1241
0
{
1242
0
  if (!rdpei)
1243
0
    return NULL;
1244
1245
0
  for (UINT32 x = 0; x < rdpei->maxPenContacts; x++)
1246
0
  {
1247
0
    RDPINPUT_PEN_CONTACT_POINT* contact = &rdpei->penContactPoints[x];
1248
0
    if (active)
1249
0
    {
1250
0
      if (contact->active)
1251
0
      {
1252
0
        if (contact->externalId == externalId)
1253
0
          return contact;
1254
0
      }
1255
0
    }
1256
0
    else
1257
0
    {
1258
0
      if (!contact->active)
1259
0
      {
1260
0
        contact->externalId = externalId;
1261
0
        contact->active = TRUE;
1262
0
        return contact;
1263
0
      }
1264
0
    }
1265
0
  }
1266
0
  return NULL;
1267
0
}
1268
1269
static UINT rdpei_add_pen(RdpeiClientContext* context, INT32 externalId,
1270
                          const RDPINPUT_PEN_CONTACT* contact)
1271
0
{
1272
0
  if (!context || !contact || !context->handle)
1273
0
    return ERROR_INTERNAL_ERROR;
1274
1275
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
1276
1277
0
  EnterCriticalSection(&rdpei->lock);
1278
1279
0
  RDPINPUT_PEN_CONTACT_POINT* contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE);
1280
0
  if (contactPoint)
1281
0
  {
1282
0
    contactPoint->data = *contact;
1283
0
    contactPoint->dirty = TRUE;
1284
0
    (void)SetEvent(rdpei->event);
1285
0
  }
1286
1287
0
  LeaveCriticalSection(&rdpei->lock);
1288
1289
0
  return CHANNEL_RC_OK;
1290
0
}
1291
1292
static UINT rdpei_pen_process(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
1293
                              UINT32 fieldFlags, INT32 x, INT32 y, va_list ap)
1294
0
{
1295
0
  RDPINPUT_PEN_CONTACT_POINT* contactPoint = NULL;
1296
0
  UINT error = CHANNEL_RC_OK;
1297
1298
0
  if (!context || !context->handle)
1299
0
    return ERROR_INTERNAL_ERROR;
1300
1301
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)context->handle;
1302
1303
0
  EnterCriticalSection(&rdpei->lock);
1304
  // Start a new contact only when it is not active.
1305
0
  contactPoint = rdpei_pen_contact(rdpei, externalId, TRUE);
1306
0
  if (!contactPoint)
1307
0
  {
1308
0
    const UINT32 mask = RDPINPUT_CONTACT_FLAG_INRANGE;
1309
0
    if ((contactFlags & mask) == mask)
1310
0
    {
1311
0
      contactPoint = rdpei_pen_contact(rdpei, externalId, FALSE);
1312
0
    }
1313
0
  }
1314
1315
0
  if (contactPoint != NULL)
1316
0
  {
1317
0
    RDPINPUT_PEN_CONTACT contact = WINPR_C_ARRAY_INIT;
1318
1319
0
    contact.x = x;
1320
0
    contact.y = y;
1321
0
    contact.fieldsPresent = WINPR_ASSERTING_INT_CAST(UINT16, fieldFlags);
1322
1323
0
    contact.contactFlags = contactFlags;
1324
0
    if (fieldFlags & RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT)
1325
0
    {
1326
0
      const UINT32 val = va_arg(ap, UINT32);
1327
0
      contact.penFlags = WINPR_ASSERTING_INT_CAST(UINT16, val);
1328
0
    }
1329
0
    if (fieldFlags & RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT)
1330
0
    {
1331
0
      const UINT32 val = va_arg(ap, UINT32);
1332
0
      contact.pressure = WINPR_ASSERTING_INT_CAST(UINT16, val);
1333
0
    }
1334
0
    if (fieldFlags & RDPINPUT_PEN_CONTACT_ROTATION_PRESENT)
1335
0
    {
1336
0
      const UINT32 val = va_arg(ap, UINT32);
1337
0
      contact.rotation = WINPR_ASSERTING_INT_CAST(UINT16, val);
1338
0
    }
1339
0
    if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTX_PRESENT)
1340
0
    {
1341
0
      const INT32 val = va_arg(ap, INT32);
1342
0
      contact.tiltX = WINPR_ASSERTING_INT_CAST(INT16, val);
1343
0
    }
1344
0
    if (fieldFlags & RDPINPUT_PEN_CONTACT_TILTY_PRESENT)
1345
0
    {
1346
0
      const INT32 val = va_arg(ap, INT32);
1347
0
      WINPR_ASSERT((val >= INT16_MIN) && (val <= INT16_MAX));
1348
0
      contact.tiltY = WINPR_ASSERTING_INT_CAST(INT16, val);
1349
0
    }
1350
1351
0
    error = context->AddPen(context, externalId, &contact);
1352
0
  }
1353
1354
0
  LeaveCriticalSection(&rdpei->lock);
1355
1356
0
  return error;
1357
0
}
1358
1359
/**
1360
 * Function description
1361
 *
1362
 * @return 0 on success, otherwise a Win32 error code
1363
 */
1364
static UINT rdpei_pen_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
1365
                            INT32 x, INT32 y, ...)
1366
0
{
1367
0
  UINT error = 0;
1368
0
  va_list ap = WINPR_C_ARRAY_INIT;
1369
1370
0
  va_start(ap, y);
1371
0
  error = rdpei_pen_process(context, externalId,
1372
0
                            RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
1373
0
                                RDPINPUT_CONTACT_FLAG_INCONTACT,
1374
0
                            fieldFlags, x, y, ap);
1375
0
  va_end(ap);
1376
1377
0
  return error;
1378
0
}
1379
1380
/**
1381
 * Function description
1382
 *
1383
 * @return 0 on success, otherwise a Win32 error code
1384
 */
1385
static UINT rdpei_pen_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
1386
                             INT32 x, INT32 y, ...)
1387
0
{
1388
0
  UINT error = 0;
1389
0
  va_list ap = WINPR_C_ARRAY_INIT;
1390
1391
0
  va_start(ap, y);
1392
0
  error = rdpei_pen_process(context, externalId,
1393
0
                            RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
1394
0
                                RDPINPUT_CONTACT_FLAG_INCONTACT,
1395
0
                            fieldFlags, x, y, ap);
1396
0
  va_end(ap);
1397
0
  return error;
1398
0
}
1399
1400
/**
1401
 * Function description
1402
 *
1403
 * @return 0 on success, otherwise a Win32 error code
1404
 */
1405
static UINT rdpei_pen_end(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags, INT32 x,
1406
                          INT32 y, ...)
1407
0
{
1408
0
  UINT error = 0;
1409
0
  va_list ap = WINPR_C_ARRAY_INIT;
1410
0
  va_start(ap, y);
1411
0
  error = rdpei_pen_process(context, externalId,
1412
0
                            RDPINPUT_CONTACT_FLAG_UP | RDPINPUT_CONTACT_FLAG_INRANGE, fieldFlags,
1413
0
                            x, y, ap);
1414
0
  va_end(ap);
1415
0
  return error;
1416
0
}
1417
1418
/**
1419
 * Function description
1420
 *
1421
 * @return 0 on success, otherwise a Win32 error code
1422
 */
1423
static UINT rdpei_pen_hover_begin(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
1424
                                  INT32 x, INT32 y, ...)
1425
0
{
1426
0
  UINT error = 0;
1427
0
  va_list ap = WINPR_C_ARRAY_INIT;
1428
1429
0
  va_start(ap, y);
1430
0
  error = rdpei_pen_process(context, externalId,
1431
0
                            RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE,
1432
0
                            fieldFlags, x, y, ap);
1433
0
  va_end(ap);
1434
1435
0
  return error;
1436
0
}
1437
1438
/**
1439
 * Function description
1440
 *
1441
 * @return 0 on success, otherwise a Win32 error code
1442
 */
1443
static UINT rdpei_pen_hover_update(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
1444
                                   INT32 x, INT32 y, ...)
1445
0
{
1446
0
  UINT error = 0;
1447
0
  va_list ap = WINPR_C_ARRAY_INIT;
1448
1449
0
  va_start(ap, y);
1450
0
  error = rdpei_pen_process(context, externalId,
1451
0
                            RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE,
1452
0
                            fieldFlags, x, y, ap);
1453
0
  va_end(ap);
1454
1455
0
  return error;
1456
0
}
1457
1458
/**
1459
 * Function description
1460
 *
1461
 * @return 0 on success, otherwise a Win32 error code
1462
 */
1463
static UINT rdpei_pen_hover_cancel(RdpeiClientContext* context, INT32 externalId, UINT32 fieldFlags,
1464
                                   INT32 x, INT32 y, ...)
1465
0
{
1466
0
  UINT error = 0;
1467
0
  va_list ap = WINPR_C_ARRAY_INIT;
1468
1469
0
  va_start(ap, y);
1470
0
  error = rdpei_pen_process(context, externalId,
1471
0
                            RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED,
1472
0
                            fieldFlags, x, y, ap);
1473
0
  va_end(ap);
1474
1475
0
  return error;
1476
0
}
1477
1478
static UINT rdpei_pen_raw_event(RdpeiClientContext* context, INT32 externalId, UINT32 contactFlags,
1479
                                UINT32 fieldFlags, INT32 x, INT32 y, ...)
1480
0
{
1481
0
  UINT error = 0;
1482
0
  va_list ap = WINPR_C_ARRAY_INIT;
1483
1484
0
  va_start(ap, y);
1485
0
  error = rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, ap);
1486
0
  va_end(ap);
1487
0
  return error;
1488
0
}
1489
1490
static UINT rdpei_pen_raw_event_va(RdpeiClientContext* context, INT32 externalId,
1491
                                   UINT32 contactFlags, UINT32 fieldFlags, INT32 x, INT32 y,
1492
                                   va_list args)
1493
0
{
1494
0
  return rdpei_pen_process(context, externalId, contactFlags, fieldFlags, x, y, args);
1495
0
}
1496
1497
static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
1498
0
{
1499
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base;
1500
1501
0
  WINPR_ASSERT(base);
1502
0
  WINPR_UNUSED(settings);
1503
1504
0
  rdpei->version = RDPINPUT_PROTOCOL_V300;
1505
0
  rdpei->currentFrameTime = 0;
1506
0
  rdpei->previousFrameTime = 0;
1507
0
  rdpei->maxTouchContacts = MAX_CONTACTS;
1508
0
  rdpei->maxPenContacts = MAX_PEN_CONTACTS;
1509
0
  rdpei->rdpcontext = rcontext;
1510
1511
0
  WINPR_ASSERT(rdpei->base.log);
1512
1513
0
  InitializeCriticalSection(&rdpei->lock);
1514
0
  rdpei->event = CreateEventA(NULL, TRUE, FALSE, NULL);
1515
0
  if (!rdpei->event)
1516
0
  {
1517
0
    WLog_Print(rdpei->base.log, WLOG_ERROR, "calloc failed!");
1518
0
    return CHANNEL_RC_NO_MEMORY;
1519
0
  }
1520
1521
0
  RdpeiClientContext* context = (RdpeiClientContext*)calloc(1, sizeof(RdpeiClientContext));
1522
0
  if (!context)
1523
0
  {
1524
0
    WLog_Print(rdpei->base.log, WLOG_ERROR, "calloc failed!");
1525
0
    return CHANNEL_RC_NO_MEMORY;
1526
0
  }
1527
1528
0
  context->clientFeaturesMask = UINT32_MAX;
1529
0
  context->handle = (void*)rdpei;
1530
0
  context->GetVersion = rdpei_get_version;
1531
0
  context->GetFeatures = rdpei_get_features;
1532
0
  context->AddContact = rdpei_add_contact;
1533
0
  context->TouchBegin = rdpei_touch_begin;
1534
0
  context->TouchUpdate = rdpei_touch_update;
1535
0
  context->TouchEnd = rdpei_touch_end;
1536
0
  context->TouchCancel = rdpei_touch_cancel;
1537
0
  context->TouchRawEvent = rdpei_touch_raw_event;
1538
0
  context->TouchRawEventVA = rdpei_touch_raw_event_va;
1539
0
  context->AddPen = rdpei_add_pen;
1540
0
  context->PenBegin = rdpei_pen_begin;
1541
0
  context->PenUpdate = rdpei_pen_update;
1542
0
  context->PenEnd = rdpei_pen_end;
1543
0
  context->PenHoverBegin = rdpei_pen_hover_begin;
1544
0
  context->PenHoverUpdate = rdpei_pen_hover_update;
1545
0
  context->PenHoverCancel = rdpei_pen_hover_cancel;
1546
0
  context->PenRawEvent = rdpei_pen_raw_event;
1547
0
  context->PenRawEventVA = rdpei_pen_raw_event_va;
1548
1549
0
  rdpei->context = context;
1550
0
  rdpei->base.iface.pInterface = (void*)context;
1551
1552
0
  rdpei->async =
1553
0
      !freerdp_settings_get_bool(rdpei->rdpcontext->settings, FreeRDP_SynchronousDynamicChannels);
1554
0
  if (rdpei->async)
1555
0
  {
1556
0
    rdpei->running = TRUE;
1557
1558
0
    rdpei->thread = CreateThread(NULL, 0, rdpei_periodic_update, rdpei, 0, NULL);
1559
0
    if (!rdpei->thread)
1560
0
    {
1561
0
      WLog_Print(rdpei->base.log, WLOG_ERROR, "calloc failed!");
1562
0
      return CHANNEL_RC_NO_MEMORY;
1563
0
    }
1564
0
  }
1565
0
  else
1566
0
  {
1567
0
    if (!freerdp_client_channel_register(rdpei->rdpcontext->channels, rdpei->event,
1568
0
                                         rdpei_poll_run, rdpei))
1569
0
      return ERROR_INTERNAL_ERROR;
1570
0
  }
1571
1572
0
  return CHANNEL_RC_OK;
1573
0
}
1574
1575
static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
1576
0
{
1577
0
  RDPEI_PLUGIN* rdpei = (RDPEI_PLUGIN*)base;
1578
0
  WINPR_ASSERT(rdpei);
1579
1580
0
  rdpei->running = FALSE;
1581
0
  if (rdpei->event)
1582
0
    (void)SetEvent(rdpei->event);
1583
1584
0
  if (rdpei->thread)
1585
0
  {
1586
0
    (void)WaitForSingleObject(rdpei->thread, INFINITE);
1587
0
    (void)CloseHandle(rdpei->thread);
1588
0
  }
1589
1590
0
  if (rdpei->event && !rdpei->async)
1591
0
    freerdp_client_channel_unregister(rdpei->rdpcontext->channels, rdpei->event);
1592
1593
0
  if (rdpei->event)
1594
0
    (void)CloseHandle(rdpei->event);
1595
1596
0
  DeleteCriticalSection(&rdpei->lock);
1597
0
  free(rdpei->context);
1598
0
}
1599
1600
static const IWTSVirtualChannelCallback rdpei_callbacks = { rdpei_on_data_received, NULL, /* Open */
1601
                                                          rdpei_on_close, NULL };
1602
1603
/**
1604
 * Function description
1605
 *
1606
 * @return 0 on success, otherwise a Win32 error code
1607
 */
1608
FREERDP_ENTRY_POINT(UINT VCAPITYPE rdpei_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
1609
0
{
1610
0
  return freerdp_generic_DVCPluginEntry(pEntryPoints, RDPEI_TAG, RDPEI_DVC_CHANNEL_NAME,
1611
0
                                        sizeof(RDPEI_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
1612
0
                                        &rdpei_callbacks, init_plugin_cb, terminate_plugin_cb);
1613
0
}