FreeRDP
Loading...
Searching...
No Matches
camera_device_enum_main.c
1
20#include <winpr/assert.h>
21#include <winpr/cast.h>
22#include <winpr/cmdline.h>
23#include <winpr/string.h>
24
25#include "camera.h"
26
27#define TAG CHANNELS_TAG("rdpecam-enum.client")
28
34UINT ecam_channel_send_error_response(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
35 CAM_ERROR_CODE code)
36{
37 CAM_MSG_ID msg = CAM_MSG_ID_ErrorResponse;
38
39 WINPR_ASSERT(ecam);
40
41 wStream* s = Stream_New(nullptr, CAM_HEADER_SIZE + 4);
42 if (!s)
43 {
44 WLog_ERR(TAG, "Stream_New failed!");
45 return ERROR_NOT_ENOUGH_MEMORY;
46 }
47
48 Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, ecam->version));
49 Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg));
50 Stream_Write_UINT32(s, code);
51
52 return ecam_channel_write(ecam, hchannel, msg, s, TRUE);
53}
54
60UINT ecam_channel_send_generic_msg(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
61 CAM_MSG_ID msg)
62{
63 WINPR_ASSERT(ecam);
64
65 wStream* s = Stream_New(nullptr, CAM_HEADER_SIZE);
66 if (!s)
67 {
68 WLog_ERR(TAG, "Stream_New failed!");
69 return ERROR_NOT_ENOUGH_MEMORY;
70 }
71
72 Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, ecam->version));
73 Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg));
74
75 return ecam_channel_write(ecam, hchannel, msg, s, TRUE);
76}
77
83UINT ecam_channel_write(WINPR_ATTR_UNUSED CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
84 CAM_MSG_ID msg, wStream* out, BOOL freeStream)
85{
86 if (!hchannel || !out)
87 return ERROR_INVALID_PARAMETER;
88
89 Stream_SealLength(out);
90
91 const ULONG len = WINPR_ASSERTING_INT_CAST(ULONG, Stream_Length(out));
92
93 WLog_DBG(TAG, "ChannelId=%" PRIu32 ", MessageId=0x%02" PRIx8 ", Length=%" PRIu32,
94 hchannel->channel_mgr->GetChannelId(hchannel->channel), msg, len);
95
96 const UINT error =
97 hchannel->channel->Write(hchannel->channel, len, Stream_Buffer(out), nullptr);
98
99 if (freeStream)
100 Stream_Free(out, TRUE);
101
102 return error;
103}
104
110static UINT ecam_send_device_added_notification(CameraPlugin* ecam,
111 GENERIC_CHANNEL_CALLBACK* hchannel,
112 const char* deviceName, const char* channelName)
113{
114 CAM_MSG_ID msg = CAM_MSG_ID_DeviceAddedNotification;
115
116 WINPR_ASSERT(ecam);
117
118 wStream* s = Stream_New(nullptr, 256);
119 if (!s)
120 {
121 WLog_ERR(TAG, "Stream_New failed!");
122 return ERROR_NOT_ENOUGH_MEMORY;
123 }
124
125 Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, ecam->version));
126 Stream_Write_UINT8(s, WINPR_ASSERTING_INT_CAST(uint8_t, msg));
127
128 const size_t devNameLen = strlen(deviceName);
129 const SSIZE_T devNameWLen = ConvertUtf8ToWChar(deviceName, nullptr, 0);
130 if ((devNameWLen < 0) ||
131 Stream_Write_UTF16_String_From_UTF8(s, WINPR_ASSERTING_INT_CAST(size_t, devNameWLen) + 1ull,
132 deviceName, devNameLen, TRUE) < 0)
133 {
134 Stream_Free(s, TRUE);
135 return ERROR_INTERNAL_ERROR;
136 }
137 Stream_Write(s, channelName, strlen(channelName) + 1);
138
139 return ecam_channel_write(ecam, hchannel, msg, s, TRUE);
140}
141
147static UINT ecam_ihal_device_added_callback(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel,
148 const char* deviceId, const char* deviceName)
149{
150 WLog_DBG(TAG, "deviceId=%s, deviceName=%s", deviceId, deviceName);
151
152 if (!HashTable_ContainsKey(ecam->devices, deviceId))
153 {
154 CameraDevice* dev = ecam_dev_create(ecam, deviceId, deviceName);
155 if (!HashTable_Insert(ecam->devices, deviceId, dev))
156 {
157 ecam_dev_destroy(dev);
158 return ERROR_INTERNAL_ERROR;
159 }
160 }
161 else
162 {
163 WLog_DBG(TAG, "Device %s already exists", deviceId);
164 }
165
166 ecam_send_device_added_notification(ecam, hchannel, deviceName, deviceId /*channelName*/);
167
168 return CHANNEL_RC_OK;
169}
170
176static UINT ecam_enumerate_devices(CameraPlugin* ecam, GENERIC_CHANNEL_CALLBACK* hchannel)
177{
178 return ecam->ihal->Enumerate(ecam->ihal, ecam_ihal_device_added_callback, ecam, hchannel);
179}
180
186static UINT ecam_process_select_version_response(CameraPlugin* ecam,
187 GENERIC_CHANNEL_CALLBACK* hchannel,
188 WINPR_ATTR_UNUSED wStream* s, BYTE serverVersion)
189{
190 const BYTE clientVersion = ECAM_PROTO_VERSION;
191
192 /* check remaining s capacity */
193
194 WLog_DBG(TAG, "ServerVersion=%" PRIu8 ", ClientVersion=%" PRIu8, serverVersion, clientVersion);
195
196 if (serverVersion > clientVersion)
197 {
198 WLog_ERR(TAG,
199 "Incompatible protocol version server=%" PRIu8 ", client supports version=%" PRIu8,
200 serverVersion, clientVersion);
201 return CHANNEL_RC_OK;
202 }
203 ecam->version = serverVersion;
204
205 if (ecam->ihal)
206 ecam_enumerate_devices(ecam, hchannel);
207 else
208 WLog_ERR(TAG, "No HAL registered");
209
210 return CHANNEL_RC_OK;
211}
212
218static UINT ecam_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
219{
220 UINT error = CHANNEL_RC_OK;
221 BYTE version = 0;
222 BYTE messageId = 0;
223 GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
224
225 if (!hchannel || !data)
226 return ERROR_INVALID_PARAMETER;
227
228 CameraPlugin* ecam = (CameraPlugin*)hchannel->plugin;
229
230 if (!ecam)
231 return ERROR_INTERNAL_ERROR;
232
233 if (!Stream_CheckAndLogRequiredLength(TAG, data, CAM_HEADER_SIZE))
234 return ERROR_NO_DATA;
235
236 Stream_Read_UINT8(data, version);
237 Stream_Read_UINT8(data, messageId);
238 WLog_DBG(TAG, "ChannelId=%" PRIu32 ", MessageId=0x%02" PRIx8 ", Version=%d",
239 hchannel->channel_mgr->GetChannelId(hchannel->channel), messageId, version);
240
241 switch (messageId)
242 {
243 case CAM_MSG_ID_SelectVersionResponse:
244 error = ecam_process_select_version_response(ecam, hchannel, data, version);
245 break;
246
247 default:
248 WLog_WARN(TAG, "unknown MessageId=0x%02" PRIx8 "", messageId);
249 error = ERROR_INVALID_DATA;
250 ecam_channel_send_error_response(ecam, hchannel, CAM_ERROR_CODE_OperationNotSupported);
251 break;
252 }
253
254 return error;
255}
256
262static UINT ecam_on_open(IWTSVirtualChannelCallback* pChannelCallback)
263{
264 GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
265 WINPR_ASSERT(hchannel);
266
267 CameraPlugin* ecam = (CameraPlugin*)hchannel->plugin;
268 WINPR_ASSERT(ecam);
269
270 WLog_DBG(TAG, "entered");
271 return ecam_channel_send_generic_msg(ecam, hchannel, CAM_MSG_ID_SelectVersionRequest);
272}
273
279static UINT ecam_on_close(IWTSVirtualChannelCallback* pChannelCallback)
280{
281 GENERIC_CHANNEL_CALLBACK* hchannel = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
282 WINPR_ASSERT(hchannel);
283
284 WLog_DBG(TAG, "entered");
285 CameraPlugin* ecam = (CameraPlugin*)hchannel->plugin;
286 WINPR_ASSERT(ecam);
287
288 ecam->connected = FALSE;
289
290 free(hchannel);
291 return CHANNEL_RC_OK;
292}
293
299static UINT ecam_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
300 IWTSVirtualChannel* pChannel,
301 WINPR_ATTR_UNUSED BYTE* Data,
302 WINPR_ATTR_UNUSED BOOL* pbAccept,
303 IWTSVirtualChannelCallback** ppCallback)
304{
305 GENERIC_LISTENER_CALLBACK* hlistener = (GENERIC_LISTENER_CALLBACK*)pListenerCallback;
306
307 if (!hlistener || !hlistener->plugin)
308 return ERROR_INTERNAL_ERROR;
309
310 WLog_DBG(TAG, "entered");
311
312 CameraPlugin* ecam = (CameraPlugin*)hlistener->plugin;
313 WINPR_ASSERT(ecam);
314
315 if (ecam->connected)
316 {
317 WLog_ERR(TAG, "Channel already connected, terminating.");
318 return ERROR_DEVICE_ALREADY_ATTACHED;
319 }
320
321 GENERIC_CHANNEL_CALLBACK* hchannel =
323
324 if (!hchannel)
325 {
326 WLog_ERR(TAG, "calloc failed!");
327 return CHANNEL_RC_NO_MEMORY;
328 }
329
330 hchannel->iface.OnDataReceived = ecam_on_data_received;
331 hchannel->iface.OnOpen = ecam_on_open;
332 hchannel->iface.OnClose = ecam_on_close;
333 hchannel->plugin = hlistener->plugin;
334 hchannel->channel_mgr = hlistener->channel_mgr;
335 hchannel->channel = pChannel;
336 *ppCallback = &hchannel->iface;
337 ecam->connected = TRUE;
338 return CHANNEL_RC_OK;
339}
340
341static void ecam_dev_destroy_pv(void* obj)
342{
343 CameraDevice* dev = obj;
344 ecam_dev_destroy(dev);
345}
346
352static UINT ecam_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr)
353{
354 CameraPlugin* ecam = (CameraPlugin*)pPlugin;
355
356 WLog_DBG(TAG, "entered");
357
358 if (!ecam || !pChannelMgr)
359 return ERROR_INVALID_PARAMETER;
360
361 if (ecam->initialized)
362 {
363 WLog_ERR(TAG, "[%s] plugin initialized twice, aborting", RDPECAM_CONTROL_DVC_CHANNEL_NAME);
364 return ERROR_INVALID_DATA;
365 }
366
367 ecam->version = ECAM_PROTO_VERSION;
368
369 ecam->devices = HashTable_New(FALSE);
370 if (!ecam->devices)
371 {
372 WLog_ERR(TAG, "HashTable_New failed!");
373 return CHANNEL_RC_NO_MEMORY;
374 }
375
376 if (!HashTable_SetupForStringData(ecam->devices, FALSE))
377 {
378 HashTable_Free(ecam->devices);
379 ecam->devices = nullptr;
380 return ERROR_INTERNAL_ERROR;
381 }
382
383 wObject* obj = HashTable_ValueObject(ecam->devices);
384 WINPR_ASSERT(obj);
385 obj->fnObjectFree = ecam_dev_destroy_pv;
386
387 ecam->hlistener = (GENERIC_LISTENER_CALLBACK*)calloc(1, sizeof(GENERIC_LISTENER_CALLBACK));
388
389 if (!ecam->hlistener)
390 {
391 WLog_ERR(TAG, "calloc failed!");
392 return CHANNEL_RC_NO_MEMORY;
393 }
394
395 ecam->hlistener->iface.OnNewChannelConnection = ecam_on_new_channel_connection;
396 ecam->hlistener->plugin = pPlugin;
397 ecam->hlistener->channel_mgr = pChannelMgr;
398 const UINT rc = pChannelMgr->CreateListener(pChannelMgr, RDPECAM_CONTROL_DVC_CHANNEL_NAME, 0,
399 &ecam->hlistener->iface, &ecam->listener);
400
401 ecam->initialized = (rc == CHANNEL_RC_OK);
402 return rc;
403}
404
410static UINT ecam_plugin_terminated(IWTSPlugin* pPlugin)
411{
412 CameraPlugin* ecam = (CameraPlugin*)pPlugin;
413
414 if (!ecam)
415 return ERROR_INVALID_DATA;
416
417 WLog_DBG(TAG, "entered");
418
419 if (ecam->hlistener)
420 {
421 IWTSVirtualChannelManager* mgr = ecam->hlistener->channel_mgr;
422 if (mgr)
423 IFCALL(mgr->DestroyListener, mgr, ecam->listener);
424 }
425
426 free(ecam->hlistener);
427
428 HashTable_Free(ecam->devices);
429
430 UINT rc = CHANNEL_RC_OK;
431 if (ecam->ihal)
432 rc = ecam->ihal->Free(ecam->ihal);
433
434 free(ecam);
435 return rc;
436}
437
443static UINT ecam_plugin_attached(IWTSPlugin* pPlugin)
444{
445 CameraPlugin* ecam = (CameraPlugin*)pPlugin;
446 UINT error = CHANNEL_RC_OK;
447
448 if (!ecam)
449 return ERROR_INVALID_PARAMETER;
450
451 ecam->attached = TRUE;
452 return error;
453}
454
460static UINT ecam_plugin_detached(IWTSPlugin* pPlugin)
461{
462 CameraPlugin* ecam = (CameraPlugin*)pPlugin;
463 UINT error = CHANNEL_RC_OK;
464
465 if (!ecam)
466 return ERROR_INVALID_PARAMETER;
467
468 ecam->attached = FALSE;
469 return error;
470}
471
477static UINT ecam_register_hal_plugin(IWTSPlugin* pPlugin, ICamHal* ihal)
478{
479 CameraPlugin* ecam = (CameraPlugin*)pPlugin;
480
481 WINPR_ASSERT(ecam);
482
483 if (ecam->ihal)
484 {
485 WLog_DBG(TAG, "already registered");
486 return ERROR_ALREADY_EXISTS;
487 }
488
489 WLog_DBG(TAG, "HAL registered");
490 ecam->ihal = ihal;
491 return CHANNEL_RC_OK;
492}
493
499static UINT ecam_load_hal_plugin(CameraPlugin* ecam, const char* name, const ADDIN_ARGV* args)
500{
501 WINPR_ASSERT(ecam);
502
503 FREERDP_CAMERA_HAL_ENTRY_POINTS entryPoints = WINPR_C_ARRAY_INIT;
504 UINT error = ERROR_INTERNAL_ERROR;
505 union
506 {
507 PVIRTUALCHANNELENTRY pvce;
508 const PFREERDP_CAMERA_HAL_ENTRY entry;
509 } cnv;
510 cnv.pvce = freerdp_load_channel_addin_entry(RDPECAM_CHANNEL_NAME, name, nullptr, 0);
511
512 if (cnv.entry == nullptr)
513 {
514 WLog_ERR(TAG,
515 "freerdp_load_channel_addin_entry did not return any function pointers for %s ",
516 name);
517 return ERROR_INVALID_FUNCTION;
518 }
519
520 entryPoints.plugin = &ecam->iface;
521 entryPoints.pRegisterCameraHal = ecam_register_hal_plugin;
522 entryPoints.args = args;
523 entryPoints.ecam = ecam;
524
525 error = cnv.entry(&entryPoints);
526 if (error)
527 {
528 WLog_ERR(TAG, "%s entry returned error %" PRIu32 ".", name, error);
529 return error;
530 }
531
532 WLog_INFO(TAG, "Loaded %s HAL for ecam", name);
533 return CHANNEL_RC_OK;
534}
535
543WINPR_ATTR_NODISCARD
544static BOOL ecam_process_addin_args(CameraPlugin* ecam, const ADDIN_ARGV* args)
545{
546 WINPR_ASSERT(ecam);
547
548 COMMAND_LINE_ARGUMENT_A ecam_args[] = { { "sys", COMMAND_LINE_VALUE_REQUIRED, "<subsystem>",
549 nullptr, nullptr, -1, nullptr, "subsystem" },
550 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr,
551 nullptr } };
552
553 if (!args || (args->argc <= 1))
554 return TRUE;
555
556 const DWORD flags =
557 COMMAND_LINE_SIGIL_NONE | COMMAND_LINE_SEPARATOR_COLON | COMMAND_LINE_IGN_UNKNOWN_KEYWORD;
558 const int status = CommandLineParseArgumentsA(args->argc, args->argv, ecam_args, flags, ecam,
559 nullptr, nullptr);
560 if (status != 0)
561 return FALSE;
562
563 const COMMAND_LINE_ARGUMENT_A* arg = CommandLineFindArgumentA(ecam_args, "sys");
564 if (arg && (arg->Flags & COMMAND_LINE_VALUE_PRESENT))
565 {
566 /* the value points into args, which outlive the plugin */
567 ecam->subsystem = arg->Value;
568 }
569 return TRUE;
570}
571
577FREERDP_ENTRY_POINT(UINT VCAPITYPE rdpecam_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
578{
579 UINT error = CHANNEL_RC_INITIALIZATION_ERROR;
580
581 WINPR_ASSERT(pEntryPoints);
582 WINPR_ASSERT(pEntryPoints->GetPlugin);
583 CameraPlugin* ecam = (CameraPlugin*)pEntryPoints->GetPlugin(pEntryPoints, RDPECAM_CHANNEL_NAME);
584
585 if (ecam != nullptr)
586 return CHANNEL_RC_ALREADY_INITIALIZED;
587
588 ecam = (CameraPlugin*)calloc(1, sizeof(CameraPlugin));
589
590 if (!ecam)
591 {
592 WLog_ERR(TAG, "calloc failed!");
593 return CHANNEL_RC_NO_MEMORY;
594 }
595
596 ecam->attached = TRUE;
597 ecam->iface.Initialize = ecam_plugin_initialize;
598 ecam->iface.Connected = nullptr; /* server connects to client */
599 ecam->iface.Disconnected = nullptr;
600 ecam->iface.Terminated = ecam_plugin_terminated;
601 ecam->iface.Attached = ecam_plugin_attached;
602 ecam->iface.Detached = ecam_plugin_detached;
603
604 /* TODO: camera redirect only supported for platforms with Video4Linux, Android or macOS/iOS */
605#if defined(WITH_V4L)
606 ecam->subsystem = "v4l";
607#elif defined(__ANDROID__)
608 ecam->subsystem = "android";
609#elif defined(__APPLE__)
610 ecam->subsystem = "avfoundation";
611#else
612 ecam->subsystem = nullptr;
613#endif
614
615 const ADDIN_ARGV* args = pEntryPoints->GetPluginData(pEntryPoints);
616 if (!ecam_process_addin_args(ecam, args))
617 {
618 WLog_ERR(TAG, "Invalid rdpecam arguments");
619 error = ERROR_INVALID_PARAMETER;
620 goto out;
621 }
622
623 if (ecam->subsystem)
624 {
625 if ((error = ecam_load_hal_plugin(ecam, ecam->subsystem, args)))
626 {
627 WLog_ERR(TAG,
628 "Unable to load camera redirection subsystem %s because of error %" PRIu32 "",
629 ecam->subsystem, error);
630 goto out;
631 }
632 }
633
634 error = pEntryPoints->RegisterPlugin(pEntryPoints, RDPECAM_CHANNEL_NAME, &ecam->iface);
635 if (error == CHANNEL_RC_OK)
636 return error;
637
638out:
639 ecam_plugin_terminated(&ecam->iface);
640 return error;
641}
Definition camera.h:237
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59