diff --git a/channels/ainput/CMakeLists.txt b/channels/ainput/CMakeLists.txt new file mode 100644 index 000000000..b1d1a6a4a --- /dev/null +++ b/channels/ainput/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 Thincast Technologies GmbH +# +# 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("ainput") + +if(WITH_CLIENT_CHANNELS) + add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() + +if(WITH_SERVER_CHANNELS) + add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME}) +endif() diff --git a/channels/ainput/ChannelOptions.cmake b/channels/ainput/ChannelOptions.cmake new file mode 100644 index 000000000..eafc6c0f5 --- /dev/null +++ b/channels/ainput/ChannelOptions.cmake @@ -0,0 +1,13 @@ + +set(OPTION_DEFAULT OFF) +set(OPTION_CLIENT_DEFAULT ON) +set(OPTION_SERVER_DEFAULT ON) + +define_channel_options(NAME "ainput" TYPE "dynamic" + DESCRIPTION "Advanced Input Virtual Channel Extension" + SPECIFICATIONS "[XXXXX]" + DEFAULT ${OPTION_DEFAULT}) + +define_channel_client_options(${OPTION_CLIENT_DEFAULT}) +define_channel_server_options(${OPTION_SERVER_DEFAULT}) + diff --git a/channels/ainput/client/CMakeLists.txt b/channels/ainput/client/CMakeLists.txt new file mode 100644 index 000000000..4cf2d2273 --- /dev/null +++ b/channels/ainput/client/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 Thincast Technologies GmbH +# +# 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_client("ainput") + +set(${MODULE_PREFIX}_SRCS + ainput_main.c + ainput_main.h) + +include_directories(..) + +add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry") + +if (WITH_DEBUG_SYMBOLS AND MSVC AND NOT BUILTIN_CHANNELS AND BUILD_SHARED_LIBS) + install(FILES ${PROJECT_BINARY_DIR}/${MODULE_NAME}.pdb DESTINATION ${FREERDP_ADDIN_PATH} COMPONENT symbols) +endif() + +target_link_libraries(${MODULE_NAME} winpr) +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "Channels/${CHANNEL_NAME}/Client") diff --git a/channels/ainput/client/ainput_main.c b/channels/ainput/client/ainput_main.c new file mode 100644 index 000000000..e3e2d9cb3 --- /dev/null +++ b/channels/ainput/client/ainput_main.c @@ -0,0 +1,301 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include + +#include "ainput_main.h" +#include +#include +#include + +#define TAG CHANNELS_TAG("ainput.client") + +typedef struct AINPUT_CHANNEL_CALLBACK_ AINPUT_CHANNEL_CALLBACK; +struct AINPUT_CHANNEL_CALLBACK_ +{ + IWTSVirtualChannelCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + IWTSVirtualChannel* channel; +}; + +typedef struct AINPUT_LISTENER_CALLBACK_ AINPUT_LISTENER_CALLBACK; +struct AINPUT_LISTENER_CALLBACK_ +{ + IWTSListenerCallback iface; + + IWTSPlugin* plugin; + IWTSVirtualChannelManager* channel_mgr; + AINPUT_CHANNEL_CALLBACK* channel_callback; +}; + +typedef struct AINPUT_PLUGIN_ AINPUT_PLUGIN; +struct AINPUT_PLUGIN_ +{ + IWTSPlugin iface; + + AINPUT_LISTENER_CALLBACK* listener_callback; + IWTSListener* listener; + UINT32 MajorVersion; + UINT32 MinorVersion; + BOOL initialized; +}; + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data) +{ + UINT16 type; + AINPUT_PLUGIN* ainput; + AINPUT_CHANNEL_CALLBACK* callback = (AINPUT_CHANNEL_CALLBACK*)pChannelCallback; + + WINPR_ASSERT(callback); + WINPR_ASSERT(data); + + ainput = (AINPUT_PLUGIN*)callback->plugin; + WINPR_ASSERT(ainput); + + if (Stream_GetRemainingLength(data) < 2) + return ERROR_NO_DATA; + Stream_Read_UINT16(data, type); + switch (type) + { + case MSG_AINPUT_VERSION: + if (Stream_GetRemainingLength(data) < 8) + return ERROR_NO_DATA; + Stream_Read_UINT32(data, ainput->MajorVersion); + Stream_Read_UINT32(data, ainput->MinorVersion); + break; + default: + WLog_WARN(TAG, "Received unsupported message type 0x%04" PRIx16, type); + break; + } + + return CHANNEL_RC_OK; +} + +static UINT ainput_send_input_event(AInputClientContext* context, UINT64 flags, INT32 x, INT32 y) +{ + AINPUT_PLUGIN* ainput; + AINPUT_CHANNEL_CALLBACK* callback; + BYTE buffer[32] = { 0 }; + wStream sbuffer = { 0 }; + wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer)); + + WINPR_ASSERT(s); + WINPR_ASSERT(context); + + ainput = (AINPUT_PLUGIN*)context->handle; + WINPR_ASSERT(ainput); + WINPR_ASSERT(ainput->listener_callback); + + if (ainput->MajorVersion != AINPUT_VERSION_MAJOR) + { + WLog_WARN(TAG, "Unsupported channel version %" PRIu32 ".%" PRIu32 ", aborting.", + ainput->MajorVersion, ainput->MinorVersion); + return CHANNEL_RC_UNSUPPORTED_VERSION; + } + callback = ainput->listener_callback->channel_callback; + WINPR_ASSERT(callback); + + /* Message type */ + Stream_Write_UINT16(s, MSG_AINPUT_MOUSE); + + /* Event data */ + Stream_Write_UINT64(s, flags); + Stream_Write_INT32(s, x); + Stream_Write_INT32(s, y); + Stream_SealLength(s); + + /* ainput back what we have received. AINPUT does not have any message IDs. */ + WINPR_ASSERT(callback->channel); + WINPR_ASSERT(callback->channel->Write); + return callback->channel->Write(callback->channel, (ULONG)Stream_Length(s), Stream_Buffer(s), + NULL); +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_close(IWTSVirtualChannelCallback* pChannelCallback) +{ + AINPUT_CHANNEL_CALLBACK* callback = (AINPUT_CHANNEL_CALLBACK*)pChannelCallback; + + free(callback); + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_on_new_channel_connection(IWTSListenerCallback* pListenerCallback, + IWTSVirtualChannel* pChannel, BYTE* Data, + BOOL* pbAccept, + IWTSVirtualChannelCallback** ppCallback) +{ + AINPUT_CHANNEL_CALLBACK* callback; + AINPUT_LISTENER_CALLBACK* listener_callback = (AINPUT_LISTENER_CALLBACK*)pListenerCallback; + + WINPR_ASSERT(listener_callback); + WINPR_UNUSED(Data); + WINPR_UNUSED(pbAccept); + + callback = (AINPUT_CHANNEL_CALLBACK*)calloc(1, sizeof(AINPUT_CHANNEL_CALLBACK)); + + if (!callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + callback->iface.OnDataReceived = ainput_on_data_received; + callback->iface.OnClose = ainput_on_close; + callback->plugin = listener_callback->plugin; + callback->channel_mgr = listener_callback->channel_mgr; + callback->channel = pChannel; + listener_callback->channel_callback = callback; + + *ppCallback = &callback->iface; + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManager* pChannelMgr) +{ + UINT status; + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)pPlugin; + + WINPR_ASSERT(ainput); + + if (ainput->initialized) + { + WLog_ERR(TAG, "[%s] channel initialized twice, aborting", AINPUT_DVC_CHANNEL_NAME); + return ERROR_INVALID_DATA; + } + ainput->listener_callback = + (AINPUT_LISTENER_CALLBACK*)calloc(1, sizeof(AINPUT_LISTENER_CALLBACK)); + + if (!ainput->listener_callback) + { + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + ainput->listener_callback->iface.OnNewChannelConnection = ainput_on_new_channel_connection; + ainput->listener_callback->plugin = pPlugin; + ainput->listener_callback->channel_mgr = pChannelMgr; + + status = pChannelMgr->CreateListener(pChannelMgr, AINPUT_DVC_CHANNEL_NAME, 0, + &ainput->listener_callback->iface, &ainput->listener); + + ainput->listener->pInterface = ainput->iface.pInterface; + ainput->initialized = status == CHANNEL_RC_OK; + return status; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_plugin_terminated(IWTSPlugin* pPlugin) +{ + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)pPlugin; + if (ainput && ainput->listener_callback) + { + IWTSVirtualChannelManager* mgr = ainput->listener_callback->channel_mgr; + if (mgr) + IFCALL(mgr->DestroyListener, mgr, ainput->listener); + } + if (ainput) + { + free(ainput->listener_callback); + free(ainput->iface.pInterface); + } + free(ainput); + + return CHANNEL_RC_OK; +} + +#ifdef BUILTIN_CHANNELS +#define DVCPluginEntry ainput_DVCPluginEntry +#else +#define DVCPluginEntry FREERDP_API DVCPluginEntry +#endif + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints) +{ + UINT status = CHANNEL_RC_OK; + AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)pEntryPoints->GetPlugin(pEntryPoints, "ainput"); + + if (!ainput) + { + AInputClientContext* context = (AInputClientContext*)calloc(1, sizeof(AInputClientContext)); + ainput = (AINPUT_PLUGIN*)calloc(1, sizeof(AINPUT_PLUGIN)); + + if (!ainput || !context) + { + free(context); + free(ainput); + + WLog_ERR(TAG, "calloc failed!"); + return CHANNEL_RC_NO_MEMORY; + } + + ainput->iface.Initialize = ainput_plugin_initialize; + ainput->iface.Terminated = ainput_plugin_terminated; + + context->handle = (void*)ainput; + context->AInputSendInputEvent = ainput_send_input_event; + ainput->iface.pInterface = (void*)context; + + status = pEntryPoints->RegisterPlugin(pEntryPoints, AINPUT_CHANNEL_NAME, &ainput->iface); + } + + return status; +} diff --git a/channels/ainput/client/ainput_main.h b/channels/ainput/client/ainput_main.h new file mode 100644 index 000000000..8a19ad91b --- /dev/null +++ b/channels/ainput/client/ainput_main.h @@ -0,0 +1,43 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * 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_AINPUT_CLIENT_MAIN_H +#define FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +#define DVC_TAG CHANNELS_TAG("ainput.client") +#ifdef WITH_DEBUG_DVC +#define DEBUG_DVC(...) WLog_DBG(DVC_TAG, __VA_ARGS__) +#else +#define DEBUG_DVC(...) \ + do \ + { \ + } while (0) +#endif + +#endif /* FREERDP_CHANNEL_AINPUT_CLIENT_MAIN_H */ diff --git a/channels/ainput/server/CMakeLists.txt b/channels/ainput/server/CMakeLists.txt new file mode 100644 index 000000000..59be263a7 --- /dev/null +++ b/channels/ainput/server/CMakeLists.txt @@ -0,0 +1,27 @@ +# FreeRDP: A Remote Desktop Protocol Implementation +# FreeRDP cmake build script +# +# Copyright 2022 Armin Novak +# Copyright 2022 Thincast Technologies GmbH +# +# 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("ainput") + +set(${MODULE_PREFIX}_SRCS + ainput_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/ainput/server/ainput_main.c b/channels/ainput/server/ainput_main.c new file mode 100644 index 000000000..122379cfb --- /dev/null +++ b/channels/ainput/server/ainput_main.c @@ -0,0 +1,418 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Advanced Input Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define TAG CHANNELS_TAG("ainput.server") + +typedef struct ainput_server_ +{ + ainput_server_context context; + + BOOL opened; + + HANDLE stopEvent; + + HANDLE thread; + void* ainput_channel; + + DWORD SessionId; + +} ainput_server; + +static BOOL ainput_server_is_open(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + return ainput->thread != NULL; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_open_channel(ainput_server* ainput) +{ + DWORD Error; + HANDLE hEvent; + DWORD StartTick; + DWORD BytesReturned = 0; + PULONG pSessionId = NULL; + + WINPR_ASSERT(ainput); + + if (WTSQuerySessionInformationA(ainput->context.vcm, WTS_CURRENT_SESSION, WTSSessionId, + (LPSTR*)&pSessionId, &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSQuerySessionInformationA failed!"); + return ERROR_INTERNAL_ERROR; + } + + ainput->SessionId = (DWORD)*pSessionId; + WTSFreeMemory(pSessionId); + hEvent = WTSVirtualChannelManagerGetEventHandle(ainput->context.vcm); + StartTick = GetTickCount(); + + while (ainput->ainput_channel == NULL) + { + if (WaitForSingleObject(hEvent, 1000) == WAIT_FAILED) + { + Error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "!", Error); + return Error; + } + + ainput->ainput_channel = WTSVirtualChannelOpenEx(ainput->SessionId, AINPUT_DVC_CHANNEL_NAME, + WTS_CHANNEL_OPTION_DYNAMIC); + + if (ainput->ainput_channel) + break; + + Error = GetLastError(); + + if (Error == ERROR_NOT_FOUND) + break; + + if (GetTickCount() - StartTick > 5000) + break; + } + + return ainput->ainput_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR; +} + +static UINT ainput_server_send_version(ainput_server* ainput, wStream* s) +{ + ULONG written; + WINPR_ASSERT(ainput); + WINPR_ASSERT(s); + + Stream_SetPosition(s, 0); + if (!Stream_EnsureCapacity(s, 10)) + return ERROR_OUTOFMEMORY; + + Stream_Write_UINT16(s, MSG_AINPUT_VERSION); + Stream_Write_UINT32(s, AINPUT_VERSION_MAJOR); /* Version (4 bytes) */ + Stream_Write_UINT32(s, AINPUT_VERSION_MINOR); /* Version (4 bytes) */ + + WINPR_ASSERT(Stream_GetPosition(s) <= ULONG_MAX); + if (!WTSVirtualChannelWrite(ainput->ainput_channel, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_GetPosition(s), &written)) + { + WLog_ERR(TAG, "WTSVirtualChannelWrite failed!"); + return ERROR_INTERNAL_ERROR; + } + + return CHANNEL_RC_OK; +} + +static UINT ainput_server_recv_mouse_event(ainput_server* ainput, wStream* s) +{ + UINT error = CHANNEL_RC_OK; + UINT64 flags; + INT32 x, y; + + WINPR_ASSERT(ainput); + WINPR_ASSERT(s); + + if (Stream_GetRemainingLength(s) < 16) + return ERROR_NO_DATA; + + Stream_Read_UINT64(s, flags); + Stream_Read_INT32(s, x); + Stream_Read_INT32(s, y); + IFCALLRET(ainput->context.MouseEvent, error, &ainput->context, flags, x, y); + + return error; +} +static DWORD WINAPI ainput_server_thread_func(LPVOID arg) +{ + wStream* s; + void* buffer; + DWORD nCount; + HANDLE events[8] = { 0 }; + BOOL ready = FALSE; + HANDLE ChannelEvent; + DWORD BytesReturned = 0; + ainput_server* ainput = (ainput_server*)arg; + UINT error; + DWORD status; + + WINPR_ASSERT(ainput); + + if ((error = ainput_server_open_channel(ainput))) + { + WLog_ERR(TAG, "ainput_server_open_channel failed with error %" PRIu32 "!", error); + goto out; + } + + buffer = NULL; + BytesReturned = 0; + ChannelEvent = NULL; + + if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualEventHandle, &buffer, + &BytesReturned) == TRUE) + { + if (BytesReturned == sizeof(HANDLE)) + CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE)); + + WTSFreeMemory(buffer); + } + + nCount = 0; + events[nCount++] = ainput->stopEvent; + events[nCount++] = ChannelEvent; + + while (1) + { + status = WaitForMultipleObjects(nCount, events, FALSE, 100); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + if (status == WAIT_OBJECT_0) + { + if (error) + WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); + + break; + } + + if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualChannelReady, &buffer, + &BytesReturned) == FALSE) + { + if (error) + WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); + + break; + } + + ready = *((BOOL*)buffer); + WTSFreeMemory(buffer); + + if (ready) + { + if (error) + WLog_ERR(TAG, "OpenResult failed with error %" PRIu32 "!", error); + + break; + } + } + + s = Stream_New(NULL, 4096); + + if (!s) + { + WLog_ERR(TAG, "Stream_New failed!"); + WTSVirtualChannelClose(ainput->ainput_channel); + ExitThread(ERROR_NOT_ENOUGH_MEMORY); + return ERROR_NOT_ENOUGH_MEMORY; + } + + if (ready) + { + if ((error = ainput_server_send_version(ainput, s))) + { + WLog_ERR(TAG, "audin_server_send_version failed with error %" PRIu32 "!", error); + goto out_capacity; + } + } + + while (ready) + { + UINT16 MessageId; + status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE); + + if (status == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForMultipleObjects failed with error %" PRIu32 "", error); + break; + } + + if (status == WAIT_OBJECT_0) + break; + + Stream_SetPosition(s, 0); + WTSVirtualChannelRead(ainput->ainput_channel, 0, NULL, 0, &BytesReturned); + + if (BytesReturned < 2) + continue; + + if (!Stream_EnsureRemainingCapacity(s, BytesReturned)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + error = CHANNEL_RC_NO_MEMORY; + break; + } + + if (WTSVirtualChannelRead(ainput->ainput_channel, 0, (PCHAR)Stream_Buffer(s), + (ULONG)Stream_Capacity(s), &BytesReturned) == FALSE) + { + WLog_ERR(TAG, "WTSVirtualChannelRead failed!"); + error = ERROR_INTERNAL_ERROR; + break; + } + + Stream_SetLength(s, BytesReturned); + Stream_Read_UINT16(s, MessageId); + + switch (MessageId) + { + case MSG_AINPUT_MOUSE: + error = ainput_server_recv_mouse_event(ainput, s); + break; + + default: + WLog_ERR(TAG, "audin_server_thread_func: unknown MessageId %" PRIu8 "", MessageId); + break; + } + + if (error) + { + WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error); + break; + } + } + +out_capacity: + Stream_Free(s, TRUE); + +out: + WTSVirtualChannelClose(ainput->ainput_channel); + ainput->ainput_channel = NULL; + + if (error && ainput->context.rdpcontext) + setChannelError(ainput->context.rdpcontext, error, + "ainput_server_thread_func reported an error"); + + ExitThread(error); + return error; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_open(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (ainput->thread == NULL) + { + ainput->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!ainput->stopEvent) + { + WLog_ERR(TAG, "CreateEvent failed!"); + return ERROR_INTERNAL_ERROR; + } + + ainput->thread = CreateThread(NULL, 0, ainput_server_thread_func, ainput, 0, NULL); + if (!ainput->thread) + { + WLog_ERR(TAG, "CreateEvent failed!"); + CloseHandle(ainput->stopEvent); + ainput->stopEvent = NULL; + return ERROR_INTERNAL_ERROR; + } + } + + return CHANNEL_RC_OK; +} + +/** + * Function description + * + * @return 0 on success, otherwise a Win32 error code + */ +static UINT ainput_server_close(ainput_server_context* context) +{ + UINT error = CHANNEL_RC_OK; + ainput_server* ainput = (ainput_server*)context; + + WINPR_ASSERT(ainput); + + if (ainput->thread) + { + SetEvent(ainput->stopEvent); + + if (WaitForSingleObject(ainput->thread, INFINITE) == WAIT_FAILED) + { + error = GetLastError(); + WLog_ERR(TAG, "WaitForSingleObject failed with error %" PRIu32 "", error); + return error; + } + + CloseHandle(ainput->thread); + CloseHandle(ainput->stopEvent); + ainput->thread = NULL; + ainput->stopEvent = NULL; + } + + return error; +} + +ainput_server_context* ainput_server_context_new(HANDLE vcm) +{ + ainput_server* ainput = (ainput_server*)calloc(1, sizeof(ainput_server)); + + if (!ainput) + return NULL; + + ainput->context.vcm = vcm; + ainput->context.Open = ainput_server_open; + ainput->context.IsOpen = ainput_server_is_open; + ainput->context.Close = ainput_server_close; + + return &ainput->context; +} + +void ainput_server_context_free(ainput_server_context* context) +{ + ainput_server* ainput = (ainput_server*)context; + if (ainput) + ainput_server_close(context); + free(ainput); +} diff --git a/channels/server/channels.c b/channels/server/channels.c index dec096954..46a28970e 100644 --- a/channels/server/channels.c +++ b/channels/server/channels.c @@ -54,6 +54,10 @@ #include #include +#if defined(CHANNEL_AINPUT_SERVER) +#include +#endif + extern void freerdp_channels_dummy(void); void freerdp_channels_dummy(void) @@ -101,6 +105,12 @@ void freerdp_channels_dummy(void) gfxredir = gfxredir_server_context_new(NULL); gfxredir_server_context_free(gfxredir); #endif // WITH_CHANNEL_GFXREDIR +#if defined(CHANNEL_AINPUT_SERVER) + { + ainput_server_context* ainput = ainput_server_context_new(NULL); + ainput_server_context_free(ainput); + } +#endif } /** diff --git a/config.h.in b/config.h.in index d64ae8f7e..5c146d381 100644 --- a/config.h.in +++ b/config.h.in @@ -67,6 +67,9 @@ #cmakedefine WITH_RDPDR /* Channels */ +#cmakedefine CHANNEL_AINPUT +#cmakedefine CHANNEL_AINPUT_CLIENT +#cmakedefine CHANNEL_AINPUT_SERVER #cmakedefine CHANNEL_AUDIN #cmakedefine CHANNEL_AUDIN_CLIENT #cmakedefine CHANNEL_AUDIN_SERVER diff --git a/include/freerdp/channels/ainput.h b/include/freerdp/channels/ainput.h new file mode 100644 index 000000000..269d9ec3b --- /dev/null +++ b/include/freerdp/channels/ainput.h @@ -0,0 +1,60 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Input Redirection Virtual Channel + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * 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_AINPUT_H +#define FREERDP_CHANNEL_AINPUT_H + +#include +#include +#include + +#define AINPUT_CHANNEL_NAME "ainput" +#define AINPUT_DVC_CHANNEL_NAME "FreeRDP::Advanced::Input" + +typedef enum +{ + MSG_AINPUT_VERSION = 0x01, + MSG_AINPUT_MOUSE = 0x02 +} eAInputMsgType; + +typedef enum +{ + AINPUT_FLAGS_WHEEL = 0x0001, + AINPUT_FLAGS_MOVE = 0x0004, + AINPUT_FLAGS_DOWN = 0x0008, + + AINPUT_FLAGS_REL = 0x0010, + + /* Pointer Flags */ + AINPUT_FLAGS_BUTTON1 = 0x1000, /* left */ + AINPUT_FLAGS_BUTTON2 = 0x2000, /* right */ + AINPUT_FLAGS_BUTTON3 = 0x4000, /* middle */ + + /* Extended Pointer Flags */ + AINPUT_XFLAGS_BUTTON1 = 0x0100, + AINPUT_XFLAGS_BUTTON2 = 0x0200 +} AInputEventFlags; + +typedef struct ainput_client_context AInputClientContext; + +#define AINPUT_VERSION_MAJOR 1 +#define AINPUT_VERSION_MINOR 0 + +#endif /* FREERDP_CHANNEL_AINPUT_H */ diff --git a/include/freerdp/client/ainput.h b/include/freerdp/client/ainput.h new file mode 100644 index 000000000..d41397b09 --- /dev/null +++ b/include/freerdp/client/ainput.h @@ -0,0 +1,39 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Display Update Virtual Channel Extension + * + * Copyright 2013 Marc-Andre Moreau + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger + * + * 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_AINPUT_CLIENT_AINPUT_H +#define FREERDP_CHANNEL_AINPUT_CLIENT_AINPUT_H + +#include +#include + +typedef UINT (*pcAInputSendInputEvent)(AInputClientContext* context, UINT64 flags, INT32 x, + INT32 y); + +struct ainput_client_context +{ + void* handle; + void* custom; + + pcAInputSendInputEvent AInputSendInputEvent; +}; + +#endif /* FREERDP_CHANNEL_AINPUT_CLIENT_AINPUT_H */ diff --git a/include/freerdp/server/ainput.h b/include/freerdp/server/ainput.h new file mode 100644 index 000000000..aca091bb4 --- /dev/null +++ b/include/freerdp/server/ainput.h @@ -0,0 +1,88 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * AInput Virtual Channel Extension + * + * Copyright 2022 Armin Novak + * Copyright 2022 Thincast Technologies GmbH + * + * 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_AINPUT_SERVER_H +#define FREERDP_CHANNEL_AINPUT_SERVER_H + +#include + +typedef enum AINPUT_SERVER_OPEN_RESULT +{ + AINPUT_SERVER_OPEN_RESULT_OK = 0, + AINPUT_SERVER_OPEN_RESULT_CLOSED = 1, + AINPUT_SERVER_OPEN_RESULT_NOTSUPPORTED = 2, + AINPUT_SERVER_OPEN_RESULT_ERROR = 3 +} AINPUT_SERVER_OPEN_RESULT; + +typedef struct _ainput_server_context ainput_server_context; + +typedef UINT (*psAInputServerOpen)(ainput_server_context* context); +typedef UINT (*psAInputServerClose)(ainput_server_context* context); +typedef BOOL (*psAInputServerIsOpen)(ainput_server_context* context); + +typedef UINT (*psAInputServerOpenResult)(ainput_server_context* context, + AINPUT_SERVER_OPEN_RESULT result); +typedef UINT (*psAInputServerMouseEvent)(ainput_server_context* context, UINT64 flags, INT32 x, + INT32 y); + +struct _ainput_server_context +{ + HANDLE vcm; + + /* Server self-defined pointer. */ + void* data; + + /*** APIs called by the server. ***/ + /** + * Open the ainput channel. + */ + psAInputServerOpen Open; + /** + * Close the ainput channel. + */ + psAInputServerClose Close; + /** + * Status of the ainput channel. + */ + psAInputServerIsOpen IsOpen; + + /*** Callbacks registered by the server. ***/ + + /** + * Receive ainput mouse event PDU. + */ + psAInputServerMouseEvent MouseEvent; + + rdpContext* rdpcontext; +}; + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_API ainput_server_context* ainput_server_context_new(HANDLE vcm); + FREERDP_API void ainput_server_context_free(ainput_server_context* context); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CHANNEL_AINPUT_SERVER_H */