summaryrefslogtreecommitdiffstats
path: root/channels/ainput
diff options
context:
space:
mode:
Diffstat (limited to 'channels/ainput')
-rw-r--r--channels/ainput/CMakeLists.txt27
-rw-r--r--channels/ainput/ChannelOptions.cmake13
-rw-r--r--channels/ainput/client/CMakeLists.txt32
-rw-r--r--channels/ainput/client/ainput_main.c183
-rw-r--r--channels/ainput/client/ainput_main.h40
-rw-r--r--channels/ainput/common/ainput_common.h59
-rw-r--r--channels/ainput/server/CMakeLists.txt28
-rw-r--r--channels/ainput/server/ainput_main.c597
8 files changed, 979 insertions, 0 deletions
diff --git a/channels/ainput/CMakeLists.txt b/channels/ainput/CMakeLists.txt
new file mode 100644
index 0000000..b1d1a6a
--- /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 <anovak@thincast.com>
+# 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 0000000..eafc6c0
--- /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 0000000..00c8e4a
--- /dev/null
+++ b/channels/ainput/client/CMakeLists.txt
@@ -0,0 +1,32 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Armin Novak <anovak@thincast.com>
+# 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
+)
+
+set(${MODULE_PREFIX}_LIBS
+ winpr
+)
+
+include_directories(..)
+
+add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DVCPluginEntry")
diff --git a/channels/ainput/client/ainput_main.c b/channels/ainput/client/ainput_main.c
new file mode 100644
index 0000000..1a2128d
--- /dev/null
+++ b/channels/ainput/client/ainput_main.c
@@ -0,0 +1,183 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Advanced Input Virtual Channel Extension
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+
+#include "ainput_main.h"
+#include <freerdp/channels/log.h>
+#include <freerdp/client/channels.h>
+#include <freerdp/client/ainput.h>
+#include <freerdp/channels/ainput.h>
+
+#include "../common/ainput_common.h"
+
+#define TAG CHANNELS_TAG("ainput.client")
+
+typedef struct AINPUT_PLUGIN_ AINPUT_PLUGIN;
+struct AINPUT_PLUGIN_
+{
+ GENERIC_DYNVC_PLUGIN base;
+ AInputClientContext* context;
+ UINT32 MajorVersion;
+ UINT32 MinorVersion;
+};
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT ainput_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
+{
+ UINT16 type = 0;
+ AINPUT_PLUGIN* ainput = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+
+ WINPR_ASSERT(callback);
+ WINPR_ASSERT(data);
+
+ ainput = (AINPUT_PLUGIN*)callback->plugin;
+ WINPR_ASSERT(ainput);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, data, 2))
+ return ERROR_NO_DATA;
+ Stream_Read_UINT16(data, type);
+ switch (type)
+ {
+ case MSG_AINPUT_VERSION:
+ if (!Stream_CheckAndLogRequiredLength(TAG, 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 = NULL;
+ GENERIC_CHANNEL_CALLBACK* callback = NULL;
+ BYTE buffer[32] = { 0 };
+ UINT64 time = 0;
+ wStream sbuffer = { 0 };
+ wStream* s = Stream_StaticInit(&sbuffer, buffer, sizeof(buffer));
+
+ WINPR_ASSERT(s);
+ WINPR_ASSERT(context);
+
+ time = GetTickCount64();
+ ainput = (AINPUT_PLUGIN*)context->handle;
+ WINPR_ASSERT(ainput);
+
+ 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->base.listener_callback->channel_callback;
+ WINPR_ASSERT(callback);
+
+ {
+ char ebuffer[128] = { 0 };
+ WLog_VRB(TAG, "sending timestamp=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, time,
+ ainput_flags_to_string(flags, ebuffer, sizeof(ebuffer)), x, y);
+ }
+
+ /* Message type */
+ Stream_Write_UINT16(s, MSG_AINPUT_MOUSE);
+
+ /* Event data */
+ Stream_Write_UINT64(s, time);
+ 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)
+{
+ GENERIC_CHANNEL_CALLBACK* callback = (GENERIC_CHANNEL_CALLBACK*)pChannelCallback;
+
+ free(callback);
+
+ return CHANNEL_RC_OK;
+}
+
+static UINT init_plugin_cb(GENERIC_DYNVC_PLUGIN* base, rdpContext* rcontext, rdpSettings* settings)
+{
+ AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base;
+ AInputClientContext* context = (AInputClientContext*)calloc(1, sizeof(AInputClientContext));
+ if (!context)
+ return CHANNEL_RC_NO_MEMORY;
+
+ context->handle = (void*)base;
+ context->AInputSendInputEvent = ainput_send_input_event;
+
+ ainput->context = context;
+ ainput->base.iface.pInterface = context;
+ return CHANNEL_RC_OK;
+}
+
+static void terminate_plugin_cb(GENERIC_DYNVC_PLUGIN* base)
+{
+ AINPUT_PLUGIN* ainput = (AINPUT_PLUGIN*)base;
+ free(ainput->context);
+}
+
+static const IWTSVirtualChannelCallback ainput_functions = { ainput_on_data_received,
+ NULL, /* Open */
+ ainput_on_close, NULL };
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+FREERDP_ENTRY_POINT(UINT ainput_DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints))
+{
+ return freerdp_generic_DVCPluginEntry(pEntryPoints, TAG, AINPUT_DVC_CHANNEL_NAME,
+ sizeof(AINPUT_PLUGIN), sizeof(GENERIC_CHANNEL_CALLBACK),
+ &ainput_functions, init_plugin_cb, terminate_plugin_cb);
+}
diff --git a/channels/ainput/client/ainput_main.h b/channels/ainput/client/ainput_main.h
new file mode 100644
index 0000000..5e1d5b1
--- /dev/null
+++ b/channels/ainput/client/ainput_main.h
@@ -0,0 +1,40 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Advanced Input Virtual Channel Extension
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * 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
+
+#include <freerdp/config.h>
+#include <freerdp/dvc.h>
+#include <freerdp/types.h>
+#include <freerdp/addin.h>
+#include <freerdp/channels/log.h>
+
+#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/common/ainput_common.h b/channels/ainput/common/ainput_common.h
new file mode 100644
index 0000000..34442f7
--- /dev/null
+++ b/channels/ainput/common/ainput_common.h
@@ -0,0 +1,59 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Audio Input Redirection Virtual Channel
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * 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_INT_AINPUT_COMMON_H
+#define FREERDP_INT_AINPUT_COMMON_H
+
+#include <winpr/string.h>
+
+#include <freerdp/channels/ainput.h>
+
+static INLINE const char* ainput_flags_to_string(UINT64 flags, char* buffer, size_t size)
+{
+ char number[32] = { 0 };
+
+ if (flags & AINPUT_FLAGS_HAVE_REL)
+ winpr_str_append("AINPUT_FLAGS_HAVE_REL", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_WHEEL)
+ winpr_str_append("AINPUT_FLAGS_WHEEL", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_MOVE)
+ winpr_str_append("AINPUT_FLAGS_MOVE", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_DOWN)
+ winpr_str_append("AINPUT_FLAGS_DOWN", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_REL)
+ winpr_str_append("AINPUT_FLAGS_REL", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_BUTTON1)
+ winpr_str_append("AINPUT_FLAGS_BUTTON1", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_BUTTON2)
+ winpr_str_append("AINPUT_FLAGS_BUTTON2", buffer, size, "|");
+ if (flags & AINPUT_FLAGS_BUTTON3)
+ winpr_str_append("AINPUT_FLAGS_BUTTON3", buffer, size, "|");
+ if (flags & AINPUT_XFLAGS_BUTTON1)
+ winpr_str_append("AINPUT_XFLAGS_BUTTON1", buffer, size, "|");
+ if (flags & AINPUT_XFLAGS_BUTTON2)
+ winpr_str_append("AINPUT_XFLAGS_BUTTON2", buffer, size, "|");
+
+ _snprintf(number, sizeof(number), "[0x%08" PRIx64 "]", flags);
+ winpr_str_append(number, buffer, size, " ");
+
+ return buffer;
+}
+
+#endif /* FREERDP_INT_AINPUT_COMMON_H */
diff --git a/channels/ainput/server/CMakeLists.txt b/channels/ainput/server/CMakeLists.txt
new file mode 100644
index 0000000..13bf2e4
--- /dev/null
+++ b/channels/ainput/server/CMakeLists.txt
@@ -0,0 +1,28 @@
+# FreeRDP: A Remote Desktop Protocol Implementation
+# FreeRDP cmake build script
+#
+# Copyright 2022 Armin Novak <anovak@thincast.com>
+# 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
+)
+
+set(${MODULE_PREFIX}_LIBS
+ freerdp
+)
+add_channel_server_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} FALSE "DVCPluginEntry")
diff --git a/channels/ainput/server/ainput_main.c b/channels/ainput/server/ainput_main.c
new file mode 100644
index 0000000..6bb4650
--- /dev/null
+++ b/channels/ainput/server/ainput_main.c
@@ -0,0 +1,597 @@
+/**
+ * FreeRDP: A Remote Desktop Protocol Implementation
+ * Advanced Input Virtual Channel Extension
+ *
+ * Copyright 2022 Armin Novak <anovak@thincast.com>
+ * 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.
+ */
+
+#include <freerdp/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <winpr/crt.h>
+#include <winpr/assert.h>
+#include <winpr/synch.h>
+#include <winpr/thread.h>
+#include <winpr/stream.h>
+#include <winpr/sysinfo.h>
+
+#include <freerdp/freerdp.h>
+#include <freerdp/channels/ainput.h>
+#include <freerdp/server/ainput.h>
+#include <freerdp/channels/log.h>
+
+#include "../common/ainput_common.h"
+
+#define TAG CHANNELS_TAG("ainput.server")
+
+typedef enum
+{
+ AINPUT_INITIAL,
+ AINPUT_OPENED,
+ AINPUT_VERSION_SENT,
+} eAInputChannelState;
+
+typedef struct
+{
+ ainput_server_context context;
+
+ BOOL opened;
+
+ HANDLE stopEvent;
+
+ HANDLE thread;
+ void* ainput_channel;
+
+ DWORD SessionId;
+
+ BOOL isOpened;
+ BOOL externalThread;
+
+ /* Channel state */
+ eAInputChannelState state;
+
+ wStream* buffer;
+} ainput_server;
+
+static UINT ainput_server_context_poll(ainput_server_context* context);
+static BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle);
+static UINT ainput_server_context_poll_int(ainput_server_context* context);
+
+static BOOL ainput_server_is_open(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+ return ainput->isOpened;
+}
+
+/**
+ * Function description
+ *
+ * @return 0 on success, otherwise a Win32 error code
+ */
+static UINT ainput_server_open_channel(ainput_server* ainput)
+{
+ DWORD Error = 0;
+ HANDLE hEvent = NULL;
+ DWORD StartTick = 0;
+ 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);
+
+ Error = GetLastError();
+
+ if (Error == ERROR_NOT_FOUND)
+ {
+ WLog_DBG(TAG, "Channel %s not found", AINPUT_DVC_CHANNEL_NAME);
+ break;
+ }
+
+ if (ainput->ainput_channel)
+ {
+ UINT32 channelId = 0;
+ BOOL status = TRUE;
+
+ channelId = WTSChannelGetIdByHandle(ainput->ainput_channel);
+
+ IFCALLRET(ainput->context.ChannelIdAssigned, status, &ainput->context, channelId);
+ if (!status)
+ {
+ WLog_ERR(TAG, "context->ChannelIdAssigned failed!");
+ return ERROR_INTERNAL_ERROR;
+ }
+
+ break;
+ }
+
+ if (GetTickCount() - StartTick > 5000)
+ {
+ WLog_WARN(TAG, "Timeout opening channel %s", AINPUT_DVC_CHANNEL_NAME);
+ break;
+ }
+ }
+
+ return ainput->ainput_channel ? CHANNEL_RC_OK : ERROR_INTERNAL_ERROR;
+}
+
+static UINT ainput_server_send_version(ainput_server* ainput)
+{
+ ULONG written = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(ainput);
+
+ s = ainput->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ if (!Stream_EnsureCapacity(s, 10))
+ {
+ WLog_WARN(TAG, "[%s] out of memory", AINPUT_DVC_CHANNEL_NAME);
+ 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 = 0;
+ UINT64 time = 0;
+ INT32 x = 0;
+ INT32 y = 0;
+ char buffer[128] = { 0 };
+
+ WINPR_ASSERT(ainput);
+ WINPR_ASSERT(s);
+
+ if (!Stream_CheckAndLogRequiredLength(TAG, s, 24))
+ return ERROR_NO_DATA;
+
+ Stream_Read_UINT64(s, time);
+ Stream_Read_UINT64(s, flags);
+ Stream_Read_INT32(s, x);
+ Stream_Read_INT32(s, y);
+
+ WLog_VRB(TAG, "received: time=0x%08" PRIx64 ", flags=%s, %" PRId32 "x%" PRId32, time,
+ ainput_flags_to_string(flags, buffer, sizeof(buffer)), x, y);
+ IFCALLRET(ainput->context.MouseEvent, error, &ainput->context, time, flags, x, y);
+
+ return error;
+}
+
+static HANDLE ainput_server_get_channel_handle(ainput_server* ainput)
+{
+ void* buffer = NULL;
+ DWORD BytesReturned = 0;
+ HANDLE ChannelEvent = NULL;
+
+ WINPR_ASSERT(ainput);
+
+ if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualEventHandle, &buffer,
+ &BytesReturned) == TRUE)
+ {
+ if (BytesReturned == sizeof(HANDLE))
+ CopyMemory(&ChannelEvent, buffer, sizeof(HANDLE));
+
+ WTSFreeMemory(buffer);
+ }
+
+ return ChannelEvent;
+}
+
+static DWORD WINAPI ainput_server_thread_func(LPVOID arg)
+{
+ DWORD nCount = 0;
+ HANDLE events[2] = { 0 };
+ ainput_server* ainput = (ainput_server*)arg;
+ UINT error = CHANNEL_RC_OK;
+ DWORD status = 0;
+
+ WINPR_ASSERT(ainput);
+
+ nCount = 0;
+ events[nCount++] = ainput->stopEvent;
+
+ while ((error == CHANNEL_RC_OK) && (WaitForSingleObject(events[0], 0) != WAIT_OBJECT_0))
+ {
+ switch (ainput->state)
+ {
+ case AINPUT_OPENED:
+ events[1] = ainput_server_get_channel_handle(ainput);
+ nCount = 2;
+ status = WaitForMultipleObjects(nCount, events, FALSE, 100);
+ switch (status)
+ {
+ case WAIT_TIMEOUT:
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_OBJECT_0:
+ error = ainput_server_context_poll_int(&ainput->context);
+ break;
+ case WAIT_FAILED:
+ default:
+ WLog_WARN(TAG, "[%s] Wait for open failed", AINPUT_DVC_CHANNEL_NAME);
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ case AINPUT_VERSION_SENT:
+ status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
+ switch (status)
+ {
+ case WAIT_TIMEOUT:
+ case WAIT_OBJECT_0 + 1:
+ case WAIT_OBJECT_0:
+ error = ainput_server_context_poll_int(&ainput->context);
+ break;
+
+ case WAIT_FAILED:
+ default:
+ WLog_WARN(TAG, "[%s] Wait for version failed", AINPUT_DVC_CHANNEL_NAME);
+ error = ERROR_INTERNAL_ERROR;
+ break;
+ }
+ break;
+ default:
+ error = ainput_server_context_poll_int(&ainput->context);
+ break;
+ }
+ }
+
+ 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->externalThread && (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;
+ }
+ }
+ ainput->isOpened = TRUE;
+
+ 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->externalThread && 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;
+ }
+ if (ainput->externalThread)
+ {
+ if (ainput->state != AINPUT_INITIAL)
+ {
+ WTSVirtualChannelClose(ainput->ainput_channel);
+ ainput->ainput_channel = NULL;
+ ainput->state = AINPUT_INITIAL;
+ }
+ }
+ ainput->isOpened = FALSE;
+
+ return error;
+}
+
+static UINT ainput_server_initialize(ainput_server_context* context, BOOL externalThread)
+{
+ UINT error = CHANNEL_RC_OK;
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+
+ if (ainput->isOpened)
+ {
+ WLog_WARN(TAG, "Application error: AINPUT channel already initialized, calling in this "
+ "state is not possible!");
+ return ERROR_INVALID_STATE;
+ }
+ ainput->externalThread = externalThread;
+ 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;
+ ainput->context.Initialize = ainput_server_initialize;
+ ainput->context.Poll = ainput_server_context_poll;
+ ainput->context.ChannelHandle = ainput_server_context_handle;
+
+ ainput->buffer = Stream_New(NULL, 4096);
+ if (!ainput->buffer)
+ goto fail;
+ return &ainput->context;
+fail:
+ WINPR_PRAGMA_DIAG_PUSH
+ WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
+ ainput_server_context_free(&ainput->context);
+ WINPR_PRAGMA_DIAG_POP
+ return NULL;
+}
+
+void ainput_server_context_free(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+ if (ainput)
+ {
+ ainput_server_close(context);
+ Stream_Free(ainput->buffer, TRUE);
+ }
+ free(ainput);
+}
+
+static UINT ainput_process_message(ainput_server* ainput)
+{
+ BOOL rc = 0;
+ UINT error = ERROR_INTERNAL_ERROR;
+ ULONG BytesReturned = 0;
+ ULONG ActualBytesReturned = 0;
+ UINT16 MessageId = 0;
+ wStream* s = NULL;
+
+ WINPR_ASSERT(ainput);
+ WINPR_ASSERT(ainput->ainput_channel);
+
+ s = ainput->buffer;
+ WINPR_ASSERT(s);
+
+ Stream_SetPosition(s, 0);
+ rc = WTSVirtualChannelRead(ainput->ainput_channel, 0, NULL, 0, &BytesReturned);
+ if (!rc)
+ goto out;
+
+ if (BytesReturned < 2)
+ {
+ 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(ainput->ainput_channel, 0, (PCHAR)Stream_Buffer(s),
+ (ULONG)Stream_Capacity(s), &ActualBytesReturned) == FALSE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
+ goto out;
+ }
+
+ if (BytesReturned != ActualBytesReturned)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelRead size mismatch %" PRId32 ", expected %" PRId32,
+ ActualBytesReturned, BytesReturned);
+ goto out;
+ }
+
+ Stream_SetLength(s, ActualBytesReturned);
+ 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;
+ }
+
+out:
+ if (error)
+ WLog_ERR(TAG, "Response failed with error %" PRIu32 "!", error);
+
+ return error;
+}
+
+BOOL ainput_server_context_handle(ainput_server_context* context, HANDLE* handle)
+{
+ ainput_server* ainput = (ainput_server*)context;
+ WINPR_ASSERT(ainput);
+ WINPR_ASSERT(handle);
+
+ if (!ainput->externalThread)
+ {
+ WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME);
+ return FALSE;
+ }
+ if (ainput->state == AINPUT_INITIAL)
+ {
+ WLog_WARN(TAG, "[%s] state fail!", AINPUT_DVC_CHANNEL_NAME);
+ return FALSE;
+ }
+ *handle = ainput_server_get_channel_handle(ainput);
+ return TRUE;
+}
+
+UINT ainput_server_context_poll_int(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+ UINT error = ERROR_INTERNAL_ERROR;
+
+ WINPR_ASSERT(ainput);
+
+ switch (ainput->state)
+ {
+ case AINPUT_INITIAL:
+ error = ainput_server_open_channel(ainput);
+ if (error)
+ WLog_ERR(TAG, "ainput_server_open_channel failed with error %" PRIu32 "!", error);
+ else
+ ainput->state = AINPUT_OPENED;
+ break;
+ case AINPUT_OPENED:
+ {
+ union
+ {
+ BYTE* pb;
+ void* pv;
+ } buffer;
+ DWORD BytesReturned = 0;
+
+ buffer.pv = NULL;
+
+ if (WTSVirtualChannelQuery(ainput->ainput_channel, WTSVirtualChannelReady, &buffer.pv,
+ &BytesReturned) != TRUE)
+ {
+ WLog_ERR(TAG, "WTSVirtualChannelReady failed,");
+ }
+ else
+ {
+ if (*buffer.pb != 0)
+ {
+ error = ainput_server_send_version(ainput);
+ if (error)
+ WLog_ERR(TAG, "audin_server_send_version failed with error %" PRIu32 "!",
+ error);
+ else
+ ainput->state = AINPUT_VERSION_SENT;
+ }
+ else
+ error = CHANNEL_RC_OK;
+ }
+ WTSFreeMemory(buffer.pv);
+ }
+ break;
+ case AINPUT_VERSION_SENT:
+ error = ainput_process_message(ainput);
+ break;
+
+ default:
+ WLog_ERR(TAG, "AINPUT chanel is in invalid state %d", ainput->state);
+ break;
+ }
+
+ return error;
+}
+
+UINT ainput_server_context_poll(ainput_server_context* context)
+{
+ ainput_server* ainput = (ainput_server*)context;
+
+ WINPR_ASSERT(ainput);
+ if (!ainput->externalThread)
+ {
+ WLog_WARN(TAG, "[%s] externalThread fail!", AINPUT_DVC_CHANNEL_NAME);
+ return ERROR_INTERNAL_ERROR;
+ }
+ return ainput_server_context_poll_int(context);
+}