diff --git a/channels/rdpecam/CMakeLists.txt b/channels/rdpecam/CMakeLists.txt new file mode 100644 index 000000000..2ea7828d0 --- /dev/null +++ b/channels/rdpecam/CMakeLists.txt @@ -0,0 +1,23 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel("camera-device") +define_channel("camera-device-enumerator") + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/rdpecam/ChannelOptions.cmake b/channels/rdpecam/ChannelOptions.cmake new file mode 100644 index 000000000..6377499d7 --- /dev/null +++ b/channels/rdpecam/ChannelOptions.cmake @@ -0,0 +1,17 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT OFF) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "camera-device" TYPE "dynamic" + DESCRIPTION "Video Capture Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPECAM]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_options(NAME "camera-device-enumerator" TYPE "dynamic" + DESCRIPTION "Video Capture Virtual Channel Extension" + SPECIFICATIONS "[MS-RDPECAM]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/rdpecam/server/CMakeLists.txt b/channels/rdpecam/server/CMakeLists.txt new file mode 100644 index 000000000..e272f9d40 --- /dev/null +++ b/channels/rdpecam/server/CMakeLists.txt @@ -0,0 +1,28 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Pascal Nowack +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +define_channel_server("camera-device") +define_channel_server("camera-device-enumerator") + +set(${MODULE_PREFIX}_SRCS + camera_device_enumerator_main.c + camera_device_main.c) + +add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry") + +target_link_libraries(${MODULE_NAME} freerdp) +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Server") diff --git a/channels/rdpecam/server/camera_device_enumerator_main.c b/channels/rdpecam/server/camera_device_enumerator_main.c new file mode 100644 index 000000000..0b9eb5d20 --- /dev/null +++ b/channels/rdpecam/server/camera_device_enumerator_main.c @@ -0,0 +1,610 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#define TAG CHANNELS_TAG("cam-dev-enum.server") + +typedef enum +{ + ENUMERATOR_INITIAL, + ENUMERATOR_OPENED, +} eEnumeratorChannelState; + +typedef struct +{ + CamDevEnumServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* enumerator_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eEnumeratorChannelState state; + + wStream* buffer; +} enumerator_server; + +static UINT enumerator_server_initialize(CamDevEnumServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (enumerator->isOpened) + { + WLog_WARN(TAG, "Application error: Camera Device Enumerator channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + enumerator->externalThread = externalThread; + + return error; +} + +static UINT enumerator_server_open_channel(enumerator_server* enumerator) +{ + CamDevEnumServerContext* context = &enumerator->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + UINT32 channelId; + BOOL status = TRUE; + + WINPR_ASSERT(enumerator); + + if (WTSQuerySessionInformationA(enumerator->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + enumerator->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(enumerator->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + enumerator->enumerator_channel = WTSVirtualChannelOpenEx( + enumerator->SessionId, CAM_DEVICE_ENUMERATOR_DVC_CHANNEL_NAME, WTS_CHANNEL_OPTION_DYNAMIC); + if (!enumerator->enumerator_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(enumerator->enumerator_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT enumerator_server_handle_select_version_request(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SELECT_VERSION_REQUEST pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + IFCALLRET(context->SelectVersionRequest, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SelectVersionRequest failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_server_recv_device_added_notification(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_DEVICE_ADDED_NOTIFICATION pdu; + UINT error = CHANNEL_RC_OK; + size_t remaining_length; + WCHAR* channel_name_start; + char* tmp; + size_t i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + /* + * RequiredLength 4: + * + * Nullterminator DeviceName (2), + * VirtualChannelName (>= 1), + * Nullterminator VirtualChannelName (1) + */ + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_NO_DATA; + + pdu.DeviceName = (WCHAR*)Stream_Pointer(s); + + remaining_length = Stream_GetRemainingLength(s); + channel_name_start = (WCHAR*)Stream_Pointer(s); + + /* Search for null terminator of DeviceName */ + for (i = 0; i < remaining_length; i += sizeof(WCHAR), ++channel_name_start) + { + if (*channel_name_start == L'\0') + break; + } + + if (*channel_name_start != L'\0') + { + WLog_ERR(TAG, "enumerator_server_recv_device_added_notification: Invalid DeviceName!"); + return ERROR_INVALID_DATA; + } + + pdu.VirtualChannelName = (char*)++channel_name_start; + ++i; + + if (i >= remaining_length || *pdu.VirtualChannelName == '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + tmp = pdu.VirtualChannelName; + for (; i < remaining_length; ++i, ++tmp) + { + if (*tmp == '\0') + break; + } + + if (*tmp != '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_added_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + IFCALLRET(context->DeviceAddedNotification, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->DeviceAddedNotification failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_server_recv_device_removed_notification(CamDevEnumServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_DEVICE_REMOVED_NOTIFICATION pdu; + UINT error = CHANNEL_RC_OK; + size_t remaining_length; + char* tmp; + size_t i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return ERROR_NO_DATA; + + pdu.VirtualChannelName = (char*)Stream_Pointer(s); + + remaining_length = Stream_GetRemainingLength(s); + tmp = (char*)(Stream_Pointer(s) + 1); + + for (i = 1; i < remaining_length; ++i, ++tmp) + { + if (*tmp == '\0') + break; + } + + if (*tmp != '\0') + { + WLog_ERR(TAG, + "enumerator_server_recv_device_removed_notification: Invalid VirtualChannelName!"); + return ERROR_INVALID_DATA; + } + + IFCALLRET(context->DeviceRemovedNotification, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->DeviceRemovedNotification failed with error %" PRIu32 "", error); + + return error; +} + +static UINT enumerator_process_message(enumerator_server* enumerator) +{ + BOOL rc; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned; + CAM_SHARED_MSG_HEADER header = {}; + wStream* s; + + WINPR_ASSERT(enumerator); + WINPR_ASSERT(enumerator->enumerator_channel); + + s = enumerator->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + rc = WTSVirtualChannelRead(enumerator->enumerator_channel, 0, NULL, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(enumerator->enumerator_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_SetLength(s, BytesReturned); + Stream_Read_UINT8(s, header.Version); + Stream_Read_UINT8(s, header.MessageId); + + switch (header.MessageId) + { + case CAM_MSG_ID_SelectVersionRequest: + error = + enumerator_server_handle_select_version_request(&enumerator->context, s, &header); + break; + case CAM_MSG_ID_DeviceAddedNotification: + error = + enumerator_server_recv_device_added_notification(&enumerator->context, s, &header); + break; + case CAM_MSG_ID_DeviceRemovedNotification: + error = enumerator_server_recv_device_removed_notification(&enumerator->context, s, + &header); + break; + default: + WLog_ERR(TAG, "enumerator_process_message: unknown or invalid MessageId %" PRIu8 "", + header.MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT enumerator_server_context_poll_int(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(enumerator); + + switch (enumerator->state) + { + case ENUMERATOR_INITIAL: + error = enumerator_server_open_channel(enumerator); + if (error) + WLog_ERR(TAG, "enumerator_server_open_channel failed with error %" PRIu32 "!", + error); + else + enumerator->state = ENUMERATOR_OPENED; + break; + case ENUMERATOR_OPENED: + error = enumerator_process_message(enumerator); + break; + } + + return error; +} + +static HANDLE enumerator_server_get_channel_handle(enumerator_server* enumerator) +{ + void* buffer = NULL; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = NULL; + + WINPR_ASSERT(enumerator); + + if (WTSVirtualChannelQuery(enumerator->enumerator_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI enumerator_server_thread_func(LPVOID arg) +{ + DWORD nCount; + HANDLE events[2] = { 0 }; + enumerator_server* enumerator = (enumerator_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + + WINPR_ASSERT(enumerator); + + nCount = 0; + events[nCount++] = enumerator->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (enumerator->state) + { + case ENUMERATOR_INITIAL: + error = enumerator_server_context_poll_int(&enumerator->context); + if (error == CHANNEL_RC_OK) + { + events[1] = enumerator_server_get_channel_handle(enumerator); + nCount = 2; + } + break; + case ENUMERATOR_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = enumerator_server_context_poll_int(&enumerator->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + } + } + + WTSVirtualChannelClose(enumerator->enumerator_channel); + enumerator->enumerator_channel = NULL; + + if (error && enumerator->context.rdpcontext) + setChannelError(enumerator->context.rdpcontext, error, + "enumerator_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT enumerator_server_open(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread && (enumerator->thread == NULL)) + { + enumerator->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!enumerator->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + enumerator->thread = + CreateThread(NULL, 0, enumerator_server_thread_func, enumerator, 0, NULL); + if (!enumerator->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(enumerator->stopEvent); + enumerator->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + enumerator->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT enumerator_server_close(CamDevEnumServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread && enumerator->thread) + { + SetEvent(enumerator->stopEvent); + + if (WaitForSingleObject(enumerator->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(enumerator->thread); + CloseHandle(enumerator->stopEvent); + enumerator->thread = NULL; + enumerator->stopEvent = NULL; + } + if (enumerator->externalThread) + { + if (enumerator->state != ENUMERATOR_INITIAL) + { + WTSVirtualChannelClose(enumerator->enumerator_channel); + enumerator->enumerator_channel = NULL; + enumerator->state = ENUMERATOR_INITIAL; + } + } + enumerator->isOpened = FALSE; + + return error; +} + +static UINT enumerator_server_context_poll(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + + if (!enumerator->externalThread) + return ERROR_INTERNAL_ERROR; + + return enumerator_server_context_poll_int(context); +} + +static BOOL enumerator_server_context_handle(CamDevEnumServerContext* context, HANDLE* handle) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + WINPR_ASSERT(enumerator); + WINPR_ASSERT(handle); + + if (!enumerator->externalThread) + return FALSE; + if (enumerator->state == ENUMERATOR_INITIAL) + return FALSE; + + *handle = enumerator_server_get_channel_handle(enumerator); + + return TRUE; +} + +static UINT enumerator_server_packet_send(CamDevEnumServerContext* context, wStream* s) +{ + enumerator_server* enumerator = (enumerator_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written; + + if (!WTSVirtualChannelWrite(enumerator->enumerator_channel, (PCHAR)Stream_Buffer(s), + Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT enumerator_send_select_version_response_pdu( + CamDevEnumServerContext* context, const CAM_SELECT_VERSION_RESPONSE* selectVersionResponse) +{ + wStream* s; + + s = Stream_New(NULL, CAM_HEADER_SIZE); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return ERROR_NOT_ENOUGH_MEMORY; + } + + Stream_Write_UINT8(s, selectVersionResponse->Header.Version); + Stream_Write_UINT8(s, selectVersionResponse->Header.MessageId); + + return enumerator_server_packet_send(context, s); +} + +CamDevEnumServerContext* cam_dev_enum_server_context_new(HANDLE vcm) +{ + enumerator_server* enumerator = (enumerator_server*)calloc(1, sizeof(enumerator_server)); + + if (!enumerator) + return NULL; + + enumerator->context.vcm = vcm; + enumerator->context.Initialize = enumerator_server_initialize; + enumerator->context.Open = enumerator_server_open; + enumerator->context.Close = enumerator_server_close; + enumerator->context.Poll = enumerator_server_context_poll; + enumerator->context.ChannelHandle = enumerator_server_context_handle; + + enumerator->context.SelectVersionResponse = enumerator_send_select_version_response_pdu; + + enumerator->buffer = Stream_New(NULL, 4096); + if (!enumerator->buffer) + goto fail; + + return &enumerator->context; +fail: + cam_dev_enum_server_context_free(&enumerator->context); + return NULL; +} + +void cam_dev_enum_server_context_free(CamDevEnumServerContext* context) +{ + enumerator_server* enumerator = (enumerator_server*)context; + + if (enumerator) + { + enumerator_server_close(context); + Stream_Free(enumerator->buffer, TRUE); + } + + free(enumerator); +} diff --git a/channels/rdpecam/server/camera_device_main.c b/channels/rdpecam/server/camera_device_main.c new file mode 100644 index 000000000..fb65b1fd9 --- /dev/null +++ b/channels/rdpecam/server/camera_device_main.c @@ -0,0 +1,972 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#define TAG CHANNELS_TAG("camera-device.server") + +typedef enum +{ + CAMERA_DEVICE_INITIAL, + CAMERA_DEVICE_OPENED, +} eCameraDeviceChannelState; + +typedef struct +{ + CameraDeviceServerContext context; + + HANDLE stopEvent; + + HANDLE thread; + void* device_channel; + + DWORD SessionId; + + BOOL isOpened; + BOOL externalThread; + + /* Channel state */ + eCameraDeviceChannelState state; + + wStream* buffer; +} device_server; + +static UINT device_server_initialize(CameraDeviceServerContext* context, BOOL externalThread) +{ + UINT error = CHANNEL_RC_OK; + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (device->isOpened) + { + WLog_WARN(TAG, "Application error: Camera channel already initialized, " + "calling in this state is not possible!"); + return ERROR_INVALID_STATE; + } + + device->externalThread = externalThread; + + return error; +} + +static UINT device_server_open_channel(device_server* device) +{ + CameraDeviceServerContext* context = &device->context; + DWORD Error = ERROR_SUCCESS; + HANDLE hEvent; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + UINT32 channelId; + BOOL status = TRUE; + + WINPR_ASSERT(device); + + if (WTSQuerySessionInformationA(device->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + device->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(device->context.vcm); + + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + device->device_channel = WTSVirtualChannelOpenEx(device->SessionId, context->virtualChannelName, + WTS_CHANNEL_OPTION_DYNAMIC); + if (!device->device_channel) + { + Error = GetLastError(); + WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed with error %" PRIu32 "!", Error); + return Error; + } + + channelId = WTSChannelGetIdByHandle(device->device_channel); + + IFCALLRET(context->ChannelIdAssigned, status, context, channelId); + if (!status) + { + WLog_ERR(TAG, "context->ChannelIdAssigned failed!"); + return ERROR_INTERNAL_ERROR; + } + + return Error; +} + +static UINT device_server_handle_success_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SUCCESS_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + IFCALLRET(context->SuccessResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SuccessResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_error_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_ERROR_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return ERROR_NO_DATA; + + Stream_Read_UINT32(s, pdu.ErrorCode); + + IFCALLRET(context->ErrorResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->ErrorResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_stream_list_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_STREAM_LIST_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + BYTE i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + pdu.N_Descriptions = MIN(Stream_GetRemainingLength(s) / 5, 255); + + for (i = 0; i < pdu.N_Descriptions; ++i) + { + CAM_STREAM_DESCRIPTION* StreamDescription = &pdu.StreamDescriptions[i]; + + Stream_Read_UINT16(s, StreamDescription->FrameSourceTypes); + Stream_Read_UINT8(s, StreamDescription->StreamCategory); + Stream_Read_UINT8(s, StreamDescription->Selected); + Stream_Read_UINT8(s, StreamDescription->CanBeShared); + } + + IFCALLRET(context->StreamListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->StreamListResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_media_type_list_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_MEDIA_TYPE_LIST_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + BYTE i; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 26)) + return ERROR_NO_DATA; + + pdu.N_Descriptions = Stream_GetRemainingLength(s) / 26; + + pdu.MediaTypeDescriptions = calloc(pdu.N_Descriptions, sizeof(CAM_MEDIA_TYPE_DESCRIPTION)); + if (!pdu.MediaTypeDescriptions) + { + WLog_ERR(TAG, "Failed to allocate %zu CAM_MEDIA_TYPE_DESCRIPTION structs", + pdu.N_Descriptions); + return ERROR_NOT_ENOUGH_MEMORY; + } + + for (i = 0; i < pdu.N_Descriptions; ++i) + { + CAM_MEDIA_TYPE_DESCRIPTION* MediaTypeDescriptions = &pdu.MediaTypeDescriptions[i]; + + Stream_Read_UINT8(s, MediaTypeDescriptions->Format); + Stream_Read_UINT32(s, MediaTypeDescriptions->Width); + Stream_Read_UINT32(s, MediaTypeDescriptions->Height); + Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateNumerator); + Stream_Read_UINT32(s, MediaTypeDescriptions->FrameRateDenominator); + Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioNumerator); + Stream_Read_UINT32(s, MediaTypeDescriptions->PixelAspectRatioDenominator); + Stream_Read_UINT8(s, MediaTypeDescriptions->Flags); + } + + IFCALLRET(context->MediaTypeListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->MediaTypeListResponse failed with error %" PRIu32 "", error); + + free(pdu.MediaTypeDescriptions); + + return error; +} + +static UINT device_server_recv_current_media_type_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_CURRENT_MEDIA_TYPE_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 26)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.MediaTypeDescription.Format); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.Width); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.Height); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateNumerator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.FrameRateDenominator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioNumerator); + Stream_Read_UINT32(s, pdu.MediaTypeDescription.PixelAspectRatioDenominator); + Stream_Read_UINT8(s, pdu.MediaTypeDescription.Flags); + + IFCALLRET(context->CurrentMediaTypeResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->CurrentMediaTypeResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_sample_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SAMPLE_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.StreamIndex); + + pdu.SampleSize = Stream_GetRemainingLength(s); + pdu.Sample = Stream_Pointer(s); + + IFCALLRET(context->SampleResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SampleResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_sample_error_response(CameraDeviceServerContext* context, wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_SAMPLE_ERROR_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.StreamIndex); + Stream_Read_UINT32(s, pdu.ErrorCode); + + IFCALLRET(context->SampleErrorResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->SampleErrorResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_server_recv_property_list_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_PROPERTY_LIST_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + pdu.N_Properties = Stream_GetRemainingLength(s) / 19; + + if (pdu.N_Properties > 0) + { + size_t i; + + pdu.Properties = calloc(pdu.N_Properties, sizeof(CAM_PROPERTY_DESCRIPTION)); + if (!pdu.Properties) + { + WLog_ERR(TAG, "Failed to allocate %zu CAM_PROPERTY_DESCRIPTION structs", + pdu.N_Properties); + return ERROR_NOT_ENOUGH_MEMORY; + } + + for (i = 0; i < pdu.N_Properties; ++i) + { + Stream_Read_UINT8(s, pdu.Properties[i].PropertySet); + Stream_Read_UINT8(s, pdu.Properties[i].PropertyId); + Stream_Read_UINT8(s, pdu.Properties[i].Capabilities); + Stream_Read_INT32(s, pdu.Properties[i].MinValue); + Stream_Read_INT32(s, pdu.Properties[i].MaxValue); + Stream_Read_INT32(s, pdu.Properties[i].Step); + Stream_Read_INT32(s, pdu.Properties[i].DefaultValue); + } + } + + IFCALLRET(context->PropertyListResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->PropertyListResponse failed with error %" PRIu32 "", error); + + free(pdu.Properties); + + return error; +} + +static UINT device_server_recv_property_value_response(CameraDeviceServerContext* context, + wStream* s, + const CAM_SHARED_MSG_HEADER* header) +{ + CAM_PROPERTY_VALUE_RESPONSE pdu = {}; + UINT error = CHANNEL_RC_OK; + + WINPR_ASSERT(context); + WINPR_ASSERT(header); + + pdu.Header = *header; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 5)) + return ERROR_NO_DATA; + + Stream_Read_UINT8(s, pdu.PropertyValue.Mode); + Stream_Read_INT32(s, pdu.PropertyValue.Value); + + IFCALLRET(context->PropertyValueResponse, error, context, &pdu); + if (error) + WLog_ERR(TAG, "context->PropertyValueResponse failed with error %" PRIu32 "", error); + + return error; +} + +static UINT device_process_message(device_server* device) +{ + BOOL rc; + UINT error = ERROR_INTERNAL_ERROR; + ULONG BytesReturned; + CAM_SHARED_MSG_HEADER header = {}; + wStream* s; + + WINPR_ASSERT(device); + WINPR_ASSERT(device->device_channel); + + s = device->buffer; + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + rc = WTSVirtualChannelRead(device->device_channel, 0, NULL, 0, &BytesReturned); + if (!rc) + goto out; + + if (BytesReturned < 1) + { + error = CHANNEL_RC_OK; + goto out; + } + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + goto out; + } + + if (WTSVirtualChannelRead(device->device_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + goto out; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, CAM_HEADER_SIZE)) + return ERROR_NO_DATA; + + Stream_SetLength(s, BytesReturned); + Stream_Read_UINT8(s, header.Version); + Stream_Read_UINT8(s, header.MessageId); + + switch (header.MessageId) + { + case CAM_MSG_ID_SuccessResponse: + error = device_server_handle_success_response(&device->context, s, &header); + break; + case CAM_MSG_ID_ErrorResponse: + error = device_server_recv_error_response(&device->context, s, &header); + break; + case CAM_MSG_ID_StreamListResponse: + error = device_server_recv_stream_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_MediaTypeListResponse: + error = device_server_recv_media_type_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_CurrentMediaTypeResponse: + error = device_server_recv_current_media_type_response(&device->context, s, &header); + break; + case CAM_MSG_ID_SampleResponse: + error = device_server_recv_sample_response(&device->context, s, &header); + break; + case CAM_MSG_ID_SampleErrorResponse: + error = device_server_recv_sample_error_response(&device->context, s, &header); + break; + case CAM_MSG_ID_PropertyListResponse: + error = device_server_recv_property_list_response(&device->context, s, &header); + break; + case CAM_MSG_ID_PropertyValueResponse: + error = device_server_recv_property_value_response(&device->context, s, &header); + break; + default: + WLog_ERR(TAG, "device_process_message: unknown or invalid MessageId %" PRIu8 "", + header.MessageId); + break; + } + +out: + if (error) + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + + return error; +} + +static UINT device_server_context_poll_int(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + UINT error = ERROR_INTERNAL_ERROR; + + WINPR_ASSERT(device); + + switch (device->state) + { + case CAMERA_DEVICE_INITIAL: + error = device_server_open_channel(device); + if (error) + WLog_ERR(TAG, "device_server_open_channel failed with error %" PRIu32 "!", error); + else + device->state = CAMERA_DEVICE_OPENED; + break; + case CAMERA_DEVICE_OPENED: + error = device_process_message(device); + break; + } + + return error; +} + +static HANDLE device_server_get_channel_handle(device_server* device) +{ + void* buffer = NULL; + DWORD BytesReturned = 0; + HANDLE ChannelEvent = NULL; + + WINPR_ASSERT(device); + + if (WTSVirtualChannelQuery(device->device_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + return ChannelEvent; +} + +static DWORD WINAPI device_server_thread_func(LPVOID arg) +{ + DWORD nCount; + HANDLE events[2] = { 0 }; + device_server* device = (device_server*)arg; + UINT error = CHANNEL_RC_OK; + DWORD status; + + WINPR_ASSERT(device); + + nCount = 0; + events[nCount++] = device->stopEvent; + + while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0)) + { + switch (device->state) + { + case CAMERA_DEVICE_INITIAL: + error = device_server_context_poll_int(&device->context); + if (error == CHANNEL_RC_OK) + { + events[1] = device_server_get_channel_handle(device); + nCount = 2; + } + break; + case CAMERA_DEVICE_OPENED: + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + switch (status) + { + case WAIT_OBJECT_0: + break; + case WAIT_OBJECT_0 + 1: + case WAIT_TIMEOUT: + error = device_server_context_poll_int(&device->context); + break; + + case WAIT_FAILED: + default: + error = ERROR_INTERNAL_ERROR; + break; + } + break; + } + } + + WTSVirtualChannelClose(device->device_channel); + device->device_channel = NULL; + + if (error && device->context.rdpcontext) + setChannelError(device->context.rdpcontext, error, + "device_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +static UINT device_server_open(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread && (device->thread == NULL)) + { + device->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!device->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + device->thread = CreateThread(NULL, 0, device_server_thread_func, device, 0, NULL); + if (!device->thread) + { + WLog_ERR(TAG, "CreateThread failed!"); + CloseHandle(device->stopEvent); + device->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + device->isOpened = TRUE; + + return CHANNEL_RC_OK; +} + +static UINT device_server_close(CameraDeviceServerContext* context) +{ + UINT error = CHANNEL_RC_OK; + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread && device->thread) + { + SetEvent(device->stopEvent); + + if (WaitForSingleObject(device->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(device->thread); + CloseHandle(device->stopEvent); + device->thread = NULL; + device->stopEvent = NULL; + } + if (device->externalThread) + { + if (device->state != CAMERA_DEVICE_INITIAL) + { + WTSVirtualChannelClose(device->device_channel); + device->device_channel = NULL; + device->state = CAMERA_DEVICE_INITIAL; + } + } + device->isOpened = FALSE; + + return error; +} + +static UINT device_server_context_poll(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + + if (!device->externalThread) + return ERROR_INTERNAL_ERROR; + + return device_server_context_poll_int(context); +} + +static BOOL device_server_context_handle(CameraDeviceServerContext* context, HANDLE* handle) +{ + device_server* device = (device_server*)context; + + WINPR_ASSERT(device); + WINPR_ASSERT(handle); + + if (!device->externalThread) + return FALSE; + if (device->state == CAMERA_DEVICE_INITIAL) + return FALSE; + + *handle = device_server_get_channel_handle(device); + + return TRUE; +} + +static wStream* device_server_packet_new(size_t size, BYTE version, BYTE messageId) +{ + wStream* s; + + WINPR_ASSERT(size > 0); + + s = Stream_New(NULL, size); + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + return NULL; + } + + Stream_Write_UINT8(s, version); + Stream_Write_UINT8(s, messageId); + + return s; +} + +static UINT device_server_packet_send(CameraDeviceServerContext* context, wStream* s) +{ + device_server* device = (device_server*)context; + UINT error = CHANNEL_RC_OK; + ULONG written; + + WINPR_ASSERT(context); + WINPR_ASSERT(s); + + if (!WTSVirtualChannelWrite(device->device_channel, (PCHAR)Stream_Buffer(s), + Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + error = ERROR_INTERNAL_ERROR; + goto out; + } + + if (written < Stream_GetPosition(s)) + { + WLog_WARN(TAG, "Unexpected bytes written: %" PRIu32 "/%" PRIuz "", written, + Stream_GetPosition(s)); + } + +out: + Stream_Free(s, TRUE); + return error; +} + +static UINT device_server_write_and_send_header(CameraDeviceServerContext* context, BYTE messageId) +{ + wStream* s; + + WINPR_ASSERT(context); + + s = device_server_packet_new(CAM_HEADER_SIZE, context->protocolVersion, messageId); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + return device_server_packet_send(context, s); +} + +static UINT +device_send_activate_device_request_pdu(CameraDeviceServerContext* context, + const CAM_ACTIVATE_DEVICE_REQUEST* activateDeviceRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_ActivateDeviceRequest); +} + +static UINT device_send_deactivate_device_request_pdu( + CameraDeviceServerContext* context, + const CAM_DEACTIVATE_DEVICE_REQUEST* deactivateDeviceRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_DeactivateDeviceRequest); +} + +static UINT device_send_stream_list_request_pdu(CameraDeviceServerContext* context, + const CAM_STREAM_LIST_REQUEST* streamListRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_StreamListRequest); +} + +static UINT +device_send_media_type_list_request_pdu(CameraDeviceServerContext* context, + const CAM_MEDIA_TYPE_LIST_REQUEST* mediaTypeListRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(mediaTypeListRequest); + + s = device_server_packet_new(CAM_HEADER_SIZE + 1, context->protocolVersion, + CAM_MSG_ID_MediaTypeListRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, mediaTypeListRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT device_send_current_media_type_request_pdu( + CameraDeviceServerContext* context, + const CAM_CURRENT_MEDIA_TYPE_REQUEST* currentMediaTypeRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(currentMediaTypeRequest); + + s = device_server_packet_new(CAM_HEADER_SIZE + 1, context->protocolVersion, + CAM_MSG_ID_CurrentMediaTypeRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, currentMediaTypeRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT +device_send_start_streams_request_pdu(CameraDeviceServerContext* context, + const CAM_START_STREAMS_REQUEST* startStreamsRequest) +{ + wStream* s; + size_t i; + + WINPR_ASSERT(context); + WINPR_ASSERT(startStreamsRequest); + + s = device_server_packet_new(CAM_HEADER_SIZE + startStreamsRequest->N_Infos * 27, + context->protocolVersion, CAM_MSG_ID_StartStreamsRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + for (i = 0; i < startStreamsRequest->N_Infos; ++i) + { + const CAM_START_STREAM_INFO* info = &startStreamsRequest->StartStreamsInfo[i]; + const CAM_MEDIA_TYPE_DESCRIPTION* description = &info->MediaTypeDescription; + + Stream_Write_UINT8(s, info->StreamIndex); + + Stream_Write_UINT8(s, description->Format); + Stream_Write_UINT32(s, description->Width); + Stream_Write_UINT32(s, description->Height); + Stream_Write_UINT32(s, description->FrameRateNumerator); + Stream_Write_UINT32(s, description->FrameRateDenominator); + Stream_Write_UINT32(s, description->PixelAspectRatioNumerator); + Stream_Write_UINT32(s, description->PixelAspectRatioDenominator); + Stream_Write_UINT8(s, description->Flags); + } + + return device_server_packet_send(context, s); +} + +static UINT device_send_stop_streams_request_pdu(CameraDeviceServerContext* context, + const CAM_STOP_STREAMS_REQUEST* stopStreamsRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_StopStreamsRequest); +} + +static UINT device_send_sample_request_pdu(CameraDeviceServerContext* context, + const CAM_SAMPLE_REQUEST* sampleRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(sampleRequest); + + s = device_server_packet_new(CAM_HEADER_SIZE + 1, context->protocolVersion, + CAM_MSG_ID_SampleRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, sampleRequest->StreamIndex); + + return device_server_packet_send(context, s); +} + +static UINT +device_send_property_list_request_pdu(CameraDeviceServerContext* context, + const CAM_PROPERTY_LIST_REQUEST* propertyListRequest) +{ + WINPR_ASSERT(context); + + return device_server_write_and_send_header(context, CAM_MSG_ID_PropertyListRequest); +} + +static UINT +device_send_property_value_request_pdu(CameraDeviceServerContext* context, + const CAM_PROPERTY_VALUE_REQUEST* propertyValueRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(propertyValueRequest); + + s = device_server_packet_new(CAM_HEADER_SIZE + 2, context->protocolVersion, + CAM_MSG_ID_PropertyValueRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, propertyValueRequest->PropertySet); + Stream_Write_UINT8(s, propertyValueRequest->PropertyId); + + return device_server_packet_send(context, s); +} + +static UINT device_send_set_property_value_request_pdu( + CameraDeviceServerContext* context, + const CAM_SET_PROPERTY_VALUE_REQUEST* setPropertyValueRequest) +{ + wStream* s; + + WINPR_ASSERT(context); + WINPR_ASSERT(setPropertyValueRequest); + + s = device_server_packet_new(CAM_HEADER_SIZE + 2 + 5, context->protocolVersion, + CAM_MSG_ID_SetPropertyValueRequest); + if (!s) + return ERROR_NOT_ENOUGH_MEMORY; + + Stream_Write_UINT8(s, setPropertyValueRequest->PropertySet); + Stream_Write_UINT8(s, setPropertyValueRequest->PropertyId); + + Stream_Write_UINT8(s, setPropertyValueRequest->PropertyValue.Mode); + Stream_Write_INT32(s, setPropertyValueRequest->PropertyValue.Value); + + return device_server_packet_send(context, s); +} + +CameraDeviceServerContext* camera_device_server_context_new(HANDLE vcm) +{ + device_server* device = (device_server*)calloc(1, sizeof(device_server)); + + if (!device) + return NULL; + + device->context.vcm = vcm; + device->context.Initialize = device_server_initialize; + device->context.Open = device_server_open; + device->context.Close = device_server_close; + device->context.Poll = device_server_context_poll; + device->context.ChannelHandle = device_server_context_handle; + + device->context.ActivateDeviceRequest = device_send_activate_device_request_pdu; + device->context.DeactivateDeviceRequest = device_send_deactivate_device_request_pdu; + + device->context.StreamListRequest = device_send_stream_list_request_pdu; + device->context.MediaTypeListRequest = device_send_media_type_list_request_pdu; + device->context.CurrentMediaTypeRequest = device_send_current_media_type_request_pdu; + + device->context.StartStreamsRequest = device_send_start_streams_request_pdu; + device->context.StopStreamsRequest = device_send_stop_streams_request_pdu; + device->context.SampleRequest = device_send_sample_request_pdu; + + device->context.PropertyListRequest = device_send_property_list_request_pdu; + device->context.PropertyValueRequest = device_send_property_value_request_pdu; + device->context.SetPropertyValueRequest = device_send_set_property_value_request_pdu; + + device->buffer = Stream_New(NULL, 4096); + if (!device->buffer) + goto fail; + + return &device->context; +fail: + camera_device_server_context_free(&device->context); + return NULL; +} + +void camera_device_server_context_free(CameraDeviceServerContext* context) +{ + device_server* device = (device_server*)context; + + if (device) + { + device_server_close(context); + Stream_Free(device->buffer, TRUE); + } + + free(context->virtualChannelName); + + free(device); +} diff --git a/channels/server/channels.c b/channels/server/channels.c index 1101ae68c..cbd9ba2d2 100644 --- a/channels/server/channels.c +++ b/channels/server/channels.c @@ -51,7 +51,12 @@ #include #include #include +#include +#include + +#ifdef WITH_CHANNEL_GFXREDIR #include +#endif /* WITH_CHANNEL_GFXREDIR */ #if defined(CHANNEL_AINPUT_SERVER) #include @@ -74,6 +79,8 @@ void freerdp_channels_dummy(void) TelemetryServerContext* telemetry; RdpgfxServerContext* rdpgfx; DispServerContext* disp; + CamDevEnumServerContext* camera_enumerator; + CameraDeviceServerContext* camera_device; #ifdef WITH_CHANNEL_GFXREDIR GfxRedirServerContext* gfxredir; #endif // WITH_CHANNEL_GFXREDIR @@ -103,6 +110,10 @@ void freerdp_channels_dummy(void) rdpgfx_server_context_free(rdpgfx); disp = disp_server_context_new(NULL); disp_server_context_free(disp); + camera_enumerator = cam_dev_enum_server_context_new(NULL); + cam_dev_enum_server_context_free(camera_enumerator); + camera_device = camera_device_server_context_new(NULL); + camera_device_server_context_free(camera_device); #ifdef WITH_CHANNEL_GFXREDIR gfxredir = gfxredir_server_context_new(NULL); gfxredir_server_context_free(gfxredir); diff --git a/include/config/config.h.in b/include/config/config.h.in index 89be7fdb1..ccf3bf261 100644 --- a/include/config/config.h.in +++ b/include/config/config.h.in @@ -62,6 +62,9 @@ #cmakedefine CHANNEL_AUDIN #cmakedefine CHANNEL_AUDIN_CLIENT #cmakedefine CHANNEL_AUDIN_SERVER +#cmakedefine CHANNEL_CAMERA +#cmakedefine CHANNEL_CAMERA_CLIENT +#cmakedefine CHANNEL_CAMERA_SERVER #cmakedefine CHANNEL_CLIPRDR #cmakedefine CHANNEL_CLIPRDR_CLIENT #cmakedefine CHANNEL_CLIPRDR_SERVER diff --git a/include/freerdp/channels/camera-device.h b/include/freerdp/channels/camera-device.h new file mode 100644 index 000000000..adb97be20 --- /dev/null +++ b/include/freerdp/channels/camera-device.h @@ -0,0 +1,335 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CAMERA_DEVICE_H +#define FREERDP_CHANNEL_CAMERA_DEVICE_H + +#include +#include +#include + +#define CAM_DEVICE_ENUMERATOR_DVC_CHANNEL_NAME "RDCamera_Device_Enumerator" + +typedef enum +{ + CAM_MSG_ID_SuccessResponse = 0x01, + CAM_MSG_ID_ErrorResponse = 0x02, + CAM_MSG_ID_SelectVersionRequest = 0x03, + CAM_MSG_ID_SelectVersionResponse = 0x04, + CAM_MSG_ID_DeviceAddedNotification = 0x05, + CAM_MSG_ID_DeviceRemovedNotification = 0x06, + CAM_MSG_ID_ActivateDeviceRequest = 0x07, + CAM_MSG_ID_DeactivateDeviceRequest = 0x08, + CAM_MSG_ID_StreamListRequest = 0x09, + CAM_MSG_ID_StreamListResponse = 0x0A, + CAM_MSG_ID_MediaTypeListRequest = 0x0B, + CAM_MSG_ID_MediaTypeListResponse = 0x0C, + CAM_MSG_ID_CurrentMediaTypeRequest = 0x0D, + CAM_MSG_ID_CurrentMediaTypeResponse = 0x0E, + CAM_MSG_ID_StartStreamsRequest = 0x0F, + CAM_MSG_ID_StopStreamsRequest = 0x10, + CAM_MSG_ID_SampleRequest = 0x11, + CAM_MSG_ID_SampleResponse = 0x12, + CAM_MSG_ID_SampleErrorResponse = 0x13, + CAM_MSG_ID_PropertyListRequest = 0x14, + CAM_MSG_ID_PropertyListResponse = 0x15, + CAM_MSG_ID_PropertyValueRequest = 0x16, + CAM_MSG_ID_PropertyValueResponse = 0x17, + CAM_MSG_ID_SetPropertyValueRequest = 0x18, +} CAM_MSG_ID; + +#define CAM_HEADER_SIZE 2 + +typedef struct +{ + BYTE Version; + CAM_MSG_ID MessageId; +} CAM_SHARED_MSG_HEADER; + +/* Messages Exchanged on the Device Enumeration Channel (2.2.2) */ + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_SELECT_VERSION_REQUEST; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_SELECT_VERSION_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + WCHAR* DeviceName; + char* VirtualChannelName; +} CAM_DEVICE_ADDED_NOTIFICATION; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + char* VirtualChannelName; +} CAM_DEVICE_REMOVED_NOTIFICATION; + +/* Messages Exchanged on Device Channels (2.2.3) */ + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_SUCCESS_RESPONSE; + +typedef enum +{ + CAM_ERROR_CODE_UnexpectedError = 0x00000001, + CAM_ERROR_CODE_InvalidMessage = 0x00000002, + CAM_ERROR_CODE_NotInitialized = 0x00000003, + CAM_ERROR_CODE_InvalidRequest = 0x00000004, + CAM_ERROR_CODE_InvalidStreamNumber = 0x00000005, + CAM_ERROR_CODE_InvalidMediaType = 0x00000006, + CAM_ERROR_CODE_OutOfMemory = 0x00000007, + CAM_ERROR_CODE_ItemNotFound = 0x00000008, + CAM_ERROR_CODE_SetNotFound = 0x00000009, + CAM_ERROR_CODE_OperationNotSupported = 0x0000000A, +} CAM_ERROR_CODE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + CAM_ERROR_CODE ErrorCode; +} CAM_ERROR_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_ACTIVATE_DEVICE_REQUEST; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_DEACTIVATE_DEVICE_REQUEST; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_STREAM_LIST_REQUEST; + +typedef enum +{ + CAM_STREAM_FRAME_SOURCE_TYPE_Color = 0x0001, + CAM_STREAM_FRAME_SOURCE_TYPE_Infrared = 0x0002, + CAM_STREAM_FRAME_SOURCE_TYPE_Custom = 0x0008, +} CAM_STREAM_FRAME_SOURCE_TYPES; + +typedef enum +{ + CAM_STREAM_CATEGORY_Capture = 0x01, +} CAM_STREAM_CATEGORY; + +typedef struct +{ + CAM_STREAM_FRAME_SOURCE_TYPES FrameSourceTypes; + CAM_STREAM_CATEGORY StreamCategory; + BYTE Selected; + BYTE CanBeShared; +} CAM_STREAM_DESCRIPTION; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + BYTE N_Descriptions; + CAM_STREAM_DESCRIPTION StreamDescriptions[255]; +} CAM_STREAM_LIST_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + BYTE StreamIndex; +} CAM_MEDIA_TYPE_LIST_REQUEST; + +typedef enum +{ + CAM_MEDIA_FORMAT_H264 = 0x01, + CAM_MEDIA_FORMAT_MJPG = 0x02, + CAM_MEDIA_FORMAT_YUY2 = 0x03, + CAM_MEDIA_FORMAT_NV12 = 0x04, + CAM_MEDIA_FORMAT_I420 = 0x05, + CAM_MEDIA_FORMAT_RGB24 = 0x06, + CAM_MEDIA_FORMAT_RGB32 = 0x07, +} CAM_MEDIA_FORMAT; + +typedef enum +{ + CAM_MEDIA_TYPE_DESCRIPTION_FLAG_DecodingRequired = 0x01, + CAM_MEDIA_TYPE_DESCRIPTION_FLAG_BottomUpImage = 0x02, +} CAM_MEDIA_TYPE_DESCRIPTION_FLAGS; + +typedef struct +{ + CAM_MEDIA_FORMAT Format; + UINT32 Width; + UINT32 Height; + UINT32 FrameRateNumerator; + UINT32 FrameRateDenominator; + UINT32 PixelAspectRatioNumerator; + UINT32 PixelAspectRatioDenominator; + CAM_MEDIA_TYPE_DESCRIPTION_FLAGS Flags; +} CAM_MEDIA_TYPE_DESCRIPTION; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + size_t N_Descriptions; + CAM_MEDIA_TYPE_DESCRIPTION* MediaTypeDescriptions; +} CAM_MEDIA_TYPE_LIST_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + BYTE StreamIndex; +} CAM_CURRENT_MEDIA_TYPE_REQUEST; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + CAM_MEDIA_TYPE_DESCRIPTION MediaTypeDescription; +} CAM_CURRENT_MEDIA_TYPE_RESPONSE; + +typedef struct +{ + BYTE StreamIndex; + CAM_MEDIA_TYPE_DESCRIPTION MediaTypeDescription; +} CAM_START_STREAM_INFO; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + BYTE N_Infos; + CAM_START_STREAM_INFO StartStreamsInfo[255]; +} CAM_START_STREAMS_REQUEST; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_STOP_STREAMS_REQUEST; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + BYTE StreamIndex; +} CAM_SAMPLE_REQUEST; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + BYTE StreamIndex; + size_t SampleSize; + BYTE* Sample; +} CAM_SAMPLE_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + BYTE StreamIndex; + CAM_ERROR_CODE ErrorCode; +} CAM_SAMPLE_ERROR_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; +} CAM_PROPERTY_LIST_REQUEST; + +typedef enum +{ + CAM_PROPERTY_SET_CameraControl = 0x01, + CAM_PROPERTY_SET_VideoProcAmp = 0x02, +} CAM_PROPERTY_SET; + +/* CameraControl properties */ +#define CAM_PROPERTY_ID_CAMERA_CONTROL_Exposure 0x01 +#define CAM_PROPERTY_ID_CAMERA_CONTROL_Focus 0x02 +#define CAM_PROPERTY_ID_CAMERA_CONTROL_Pan 0x03 +#define CAM_PROPERTY_ID_CAMERA_CONTROL_Roll 0x04 +#define CAM_PROPERTY_ID_CAMERA_CONTROL_Tilt 0x05 +#define CAM_PROPERTY_ID_CAMERA_CONTROL_Zoom 0x06 + +/* VideoProcAmp properties */ +#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_BacklightCompensation 0x01 +#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_Brightness 0x02 +#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_Contrast 0x03 +#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_Hue 0x04 +#define CAM_PROPERTY_ID_VIDEO_PROC_AMP_WhiteBalance 0x05 + +typedef enum +{ + CAM_PROPERTY_CAPABILITY_Manual = 0x01, + CAM_PROPERTY_CAPABILITY_Auto = 0x02, +} CAM_PROPERTY_CAPABILITIES; + +typedef struct +{ + CAM_PROPERTY_SET PropertySet; + BYTE PropertyId; + CAM_PROPERTY_CAPABILITIES Capabilities; + INT32 MinValue; + INT32 MaxValue; + INT32 Step; + INT32 DefaultValue; +} CAM_PROPERTY_DESCRIPTION; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + size_t N_Properties; + CAM_PROPERTY_DESCRIPTION* Properties; +} CAM_PROPERTY_LIST_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + CAM_PROPERTY_SET PropertySet; + BYTE PropertyId; +} CAM_PROPERTY_VALUE_REQUEST; + +typedef enum +{ + CAM_PROPERTY_MODE_Manual = 0x01, + CAM_PROPERTY_MODE_Auto = 0x02, +} CAM_PROPERTY_MODE; + +typedef struct +{ + CAM_PROPERTY_MODE Mode; + INT32 Value; +} CAM_PROPERTY_VALUE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + CAM_PROPERTY_VALUE PropertyValue; +} CAM_PROPERTY_VALUE_RESPONSE; + +typedef struct +{ + CAM_SHARED_MSG_HEADER Header; + CAM_PROPERTY_SET PropertySet; + BYTE PropertyId; + CAM_PROPERTY_VALUE PropertyValue; +} CAM_SET_PROPERTY_VALUE_REQUEST; + +#endif /* FREERDP_CHANNEL_CAMERA_DEVICE_H */ diff --git a/include/freerdp/server/camera-device-enumerator.h b/include/freerdp/server/camera-device-enumerator.h new file mode 100644 index 000000000..230b4e49c --- /dev/null +++ b/include/freerdp/server/camera-device-enumerator.h @@ -0,0 +1,134 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CAM_DEV_ENUM_SERVER_CAM_DEV_ENUM_H +#define FREERDP_CHANNEL_CAM_DEV_ENUM_SERVER_CAM_DEV_ENUM_H + +#include +#include + +typedef struct _cam_dev_enum_server_context CamDevEnumServerContext; + +typedef UINT (*psCamDevEnumServerServerOpen)(CamDevEnumServerContext* context); +typedef UINT (*psCamDevEnumServerServerClose)(CamDevEnumServerContext* context); + +typedef BOOL (*psCamDevEnumServerServerChannelIdAssigned)(CamDevEnumServerContext* context, + UINT32 channelId); + +typedef UINT (*psCamDevEnumServerServerInitialize)(CamDevEnumServerContext* context, + BOOL externalThread); +typedef UINT (*psCamDevEnumServerServerPoll)(CamDevEnumServerContext* context); +typedef BOOL (*psCamDevEnumServerServerChannelHandle)(CamDevEnumServerContext* context, + HANDLE* handle); + +typedef UINT (*psCamDevEnumServerServerSelectVersionRequest)( + CamDevEnumServerContext* context, const CAM_SELECT_VERSION_REQUEST* selectVersionRequest); +typedef UINT (*psCamDevEnumServerServerSelectVersionResponse)( + CamDevEnumServerContext* context, const CAM_SELECT_VERSION_RESPONSE* selectVersionResponse); + +typedef UINT (*psCamDevEnumServerServerDeviceAddedNotification)( + CamDevEnumServerContext* context, const CAM_DEVICE_ADDED_NOTIFICATION* deviceAddedNotification); +typedef UINT (*psCamDevEnumServerServerDeviceRemovedNotification)( + CamDevEnumServerContext* context, + const CAM_DEVICE_REMOVED_NOTIFICATION* deviceRemovedNotification); + +struct _cam_dev_enum_server_context +{ + HANDLE vcm; + + /* Server self-defined pointer. */ + void* userdata; + + /*** APIs called by the server. ***/ + + /** + * Optional: Set thread handling. + * When externalThread=TRUE, the application is responsible to call + * Poll() periodically to process channel events. + * + * Defaults to externalThread=FALSE + */ + psCamDevEnumServerServerInitialize Initialize; + + /** + * Open the camera device enumerator channel. + */ + psCamDevEnumServerServerOpen Open; + + /** + * Close the camera device enumerator channel. + */ + psCamDevEnumServerServerClose Close; + + /** + * Poll + * When externalThread=TRUE, call Poll() periodically from your main loop. + * If externalThread=FALSE do not call. + */ + psCamDevEnumServerServerPoll Poll; + + /** + * Retrieve the channel handle for use in conjunction with Poll(). + * If externalThread=FALSE do not call. + */ + psCamDevEnumServerServerChannelHandle ChannelHandle; + + /* + * Send a Select Version Response PDU. + */ + psCamDevEnumServerServerSelectVersionResponse SelectVersionResponse; + + /*** Callbacks registered by the server. ***/ + + /** + * Callback, when the channel got its id assigned. + */ + psCamDevEnumServerServerChannelIdAssigned ChannelIdAssigned; + + /** + * Callback for the Select Version Request PDU. + */ + psCamDevEnumServerServerSelectVersionRequest SelectVersionRequest; + + /** + * Callback for the Device Added Notification PDU. + */ + psCamDevEnumServerServerDeviceAddedNotification DeviceAddedNotification; + + /** + * Callback for the Device Removed Notification PDU. + */ + psCamDevEnumServerServerDeviceRemovedNotification DeviceRemovedNotification; + + rdpContext* rdpcontext; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_API CamDevEnumServerContext* cam_dev_enum_server_context_new(HANDLE vcm); + FREERDP_API void cam_dev_enum_server_context_free(CamDevEnumServerContext* context); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_CAM_DEV_ENUM_SERVER_CAM_DEV_ENUM_H */ diff --git a/include/freerdp/server/camera-device.h b/include/freerdp/server/camera-device.h new file mode 100644 index 000000000..966a9dbbd --- /dev/null +++ b/include/freerdp/server/camera-device.h @@ -0,0 +1,278 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Video Capture Virtual Channel Extension + * + * Copyright 2022 Pascal Nowack + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CHANNEL_CAMERA_DEVICE_SERVER_CAMERA_DEVICE_H +#define FREERDP_CHANNEL_CAMERA_DEVICE_SERVER_CAMERA_DEVICE_H + +#include +#include + +typedef struct _camera_device_server_context CameraDeviceServerContext; + +typedef UINT (*psCameraDeviceServerOpen)(CameraDeviceServerContext* context); +typedef UINT (*psCameraDeviceServerClose)(CameraDeviceServerContext* context); + +typedef BOOL (*psCameraDeviceServerChannelIdAssigned)(CameraDeviceServerContext* context, + UINT32 channelId); + +typedef UINT (*psCameraDeviceServerInitialize)(CameraDeviceServerContext* context, + BOOL externalThread); +typedef UINT (*psCameraDeviceServerPoll)(CameraDeviceServerContext* context); +typedef BOOL (*psCameraDeviceServerChannelHandle)(CameraDeviceServerContext* context, + HANDLE* handle); + +typedef UINT (*psCameraDeviceServerSuccessResponse)(CameraDeviceServerContext* context, + const CAM_SUCCESS_RESPONSE* successResponse); +typedef UINT (*psCameraDeviceServerErrorResponse)(CameraDeviceServerContext* context, + const CAM_ERROR_RESPONSE* errorResponse); + +typedef UINT (*psCameraDeviceServerActivateDeviceRequest)( + CameraDeviceServerContext* context, const CAM_ACTIVATE_DEVICE_REQUEST* activateDeviceRequest); +typedef UINT (*psCameraDeviceServerDeactivateDeviceRequest)( + CameraDeviceServerContext* context, + const CAM_DEACTIVATE_DEVICE_REQUEST* deactivateDeviceRequest); + +typedef UINT (*psCameraDeviceServerStreamListRequest)( + CameraDeviceServerContext* context, const CAM_STREAM_LIST_REQUEST* streamListRequest); +typedef UINT (*psCameraDeviceServerStreamListResponse)( + CameraDeviceServerContext* context, const CAM_STREAM_LIST_RESPONSE* streamListResponse); + +typedef UINT (*psCameraDeviceServerMediaTypeListRequest)( + CameraDeviceServerContext* context, const CAM_MEDIA_TYPE_LIST_REQUEST* mediaTypeListRequest); +typedef UINT (*psCameraDeviceServerMediaTypeListResponse)( + CameraDeviceServerContext* context, const CAM_MEDIA_TYPE_LIST_RESPONSE* mediaTypeListResponse); + +typedef UINT (*psCameraDeviceServerCurrentMediaTypeRequest)( + CameraDeviceServerContext* context, + const CAM_CURRENT_MEDIA_TYPE_REQUEST* currentMediaTypeRequest); +typedef UINT (*psCameraDeviceServerCurrentMediaTypeResponse)( + CameraDeviceServerContext* context, + const CAM_CURRENT_MEDIA_TYPE_RESPONSE* currentMediaTypeResponse); + +typedef UINT (*psCameraDeviceServerStartStreamsRequest)( + CameraDeviceServerContext* context, const CAM_START_STREAMS_REQUEST* startStreamsRequest); +typedef UINT (*psCameraDeviceServerStopStreamsRequest)( + CameraDeviceServerContext* context, const CAM_STOP_STREAMS_REQUEST* stopStreamsRequest); + +typedef UINT (*psCameraDeviceServerSampleRequest)(CameraDeviceServerContext* context, + const CAM_SAMPLE_REQUEST* sampleRequest); +typedef UINT (*psCameraDeviceServerSampleResponse)(CameraDeviceServerContext* context, + const CAM_SAMPLE_RESPONSE* sampleResponse); +typedef UINT (*psCameraDeviceServerSampleErrorResponse)( + CameraDeviceServerContext* context, const CAM_SAMPLE_ERROR_RESPONSE* sampleErrorResponse); + +typedef UINT (*psCameraDeviceServerPropertyListRequest)( + CameraDeviceServerContext* context, const CAM_PROPERTY_LIST_REQUEST* propertyListRequest); +typedef UINT (*psCameraDeviceServerPropertyListResponse)( + CameraDeviceServerContext* context, const CAM_PROPERTY_LIST_RESPONSE* propertyListResponse); + +typedef UINT (*psCameraDeviceServerPropertyValueRequest)( + CameraDeviceServerContext* context, const CAM_PROPERTY_VALUE_REQUEST* propertyValueRequest); +typedef UINT (*psCameraDeviceServerPropertyValueResponse)( + CameraDeviceServerContext* context, const CAM_PROPERTY_VALUE_RESPONSE* propertyValueResponse); + +typedef UINT (*psCameraDeviceServerSetPropertyValueRequest)( + CameraDeviceServerContext* context, + const CAM_SET_PROPERTY_VALUE_REQUEST* setPropertyValueRequest); + +struct _camera_device_server_context +{ + HANDLE vcm; + + /* Server self-defined pointer. */ + void* userdata; + + /** + * Name of the virtual channel. Pointer owned by the CameraDeviceServerContext, + * meaning camera_device_server_context_free() takes care of freeing the pointer. + * + * Server implementations should sanitize the virtual channel name for invalid + * names, like names for other known channels + * ("ECHO", "AUDIO_PLAYBACK_DVC", etc.) + */ + char* virtualChannelName; + + /** + * Protocol version to be used. Every sent server to client PDU has the + * version value in the Header set to the following value. + */ + BYTE protocolVersion; + + /*** APIs called by the server. ***/ + + /** + * Optional: Set thread handling. + * When externalThread=TRUE, the application is responsible to call + * Poll() periodically to process channel events. + * + * Defaults to externalThread=FALSE + */ + psCameraDeviceServerInitialize Initialize; + + /** + * Open the camera device channel. + */ + psCameraDeviceServerOpen Open; + + /** + * Close the camera device channel. + */ + psCameraDeviceServerClose Close; + + /** + * Poll + * When externalThread=TRUE, call Poll() periodically from your main loop. + * If externalThread=FALSE do not call. + */ + psCameraDeviceServerPoll Poll; + + /** + * Retrieve the channel handle for use in conjunction with Poll(). + * If externalThread=FALSE do not call. + */ + psCameraDeviceServerChannelHandle ChannelHandle; + + /** + * For the following server to client PDUs, + * the message header does not have to be set. + */ + + /** + * Send a Activate Device Request PDU. + */ + psCameraDeviceServerActivateDeviceRequest ActivateDeviceRequest; + + /** + * Send a Deactivate Device Request PDU. + */ + psCameraDeviceServerDeactivateDeviceRequest DeactivateDeviceRequest; + + /** + * Send a Stream List Request PDU. + */ + psCameraDeviceServerStreamListRequest StreamListRequest; + + /** + * Send a Media Type List Request PDU. + */ + psCameraDeviceServerMediaTypeListRequest MediaTypeListRequest; + + /** + * Send a Current Media Type Request PDU. + */ + psCameraDeviceServerCurrentMediaTypeRequest CurrentMediaTypeRequest; + + /** + * Send a Start Streams Request PDU. + */ + psCameraDeviceServerStartStreamsRequest StartStreamsRequest; + + /** + * Send a Stop Streams Request PDU. + */ + psCameraDeviceServerStopStreamsRequest StopStreamsRequest; + + /** + * Send a Sample Request PDU. + */ + psCameraDeviceServerSampleRequest SampleRequest; + + /** + * Send a Property List Request PDU. + */ + psCameraDeviceServerPropertyListRequest PropertyListRequest; + + /** + * Send a Property Value Request PDU. + */ + psCameraDeviceServerPropertyValueRequest PropertyValueRequest; + + /** + * Send a Set Property Value Request PDU. + */ + psCameraDeviceServerSetPropertyValueRequest SetPropertyValueRequest; + + /*** Callbacks registered by the server. ***/ + + /** + * Callback, when the channel got its id assigned. + */ + psCameraDeviceServerChannelIdAssigned ChannelIdAssigned; + + /** + * Callback for the Success Response PDU. + */ + psCameraDeviceServerSuccessResponse SuccessResponse; + + /** + * Callback for the Error Response PDU. + */ + psCameraDeviceServerErrorResponse ErrorResponse; + + /** + * Callback for the Stream List Response PDU. + */ + psCameraDeviceServerStreamListResponse StreamListResponse; + + /** + * Callback for the Media Type List Response PDU. + */ + psCameraDeviceServerMediaTypeListResponse MediaTypeListResponse; + + /** + * Callback for the Current Media Type Response PDU. + */ + psCameraDeviceServerCurrentMediaTypeResponse CurrentMediaTypeResponse; + + /** + * Callback for the Sample Response PDU. + */ + psCameraDeviceServerSampleResponse SampleResponse; + + /** + * Callback for the Sample Error Response PDU. + */ + psCameraDeviceServerSampleErrorResponse SampleErrorResponse; + + /** + * Callback for the Property List Response PDU. + */ + psCameraDeviceServerPropertyListResponse PropertyListResponse; + + /** + * Callback for the Property Value Response PDU. + */ + psCameraDeviceServerPropertyValueResponse PropertyValueResponse; + + rdpContext* rdpcontext; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_API CameraDeviceServerContext* camera_device_server_context_new(HANDLE vcm); + FREERDP_API void camera_device_server_context_free(CameraDeviceServerContext* context); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_CAMERA_DEVICE_SERVER_CAMERA_DEVICE_H */