mirror of
https://github.com/FreeRDP/FreeRDP.git
synced 2024-11-23 09:54:26 +08:00
Added Advanced Input Channel
This commit is contained in:
parent
b5c9a410d3
commit
f45e515fcd
27
channels/ainput/CMakeLists.txt
Normal file
27
channels/ainput/CMakeLists.txt
Normal file
@ -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()
|
13
channels/ainput/ChannelOptions.cmake
Normal file
13
channels/ainput/ChannelOptions.cmake
Normal file
@ -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})
|
||||
|
34
channels/ainput/client/CMakeLists.txt
Normal file
34
channels/ainput/client/CMakeLists.txt
Normal file
@ -0,0 +1,34 @@
|
||||
# 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)
|
||||
|
||||
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")
|
301
channels/ainput/client/ainput_main.c
Normal file
301
channels/ainput/client/ainput_main.c
Normal file
@ -0,0 +1,301 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/assert.h>
|
||||
#include <winpr/stream.h>
|
||||
|
||||
#include "ainput_main.h"
|
||||
#include <freerdp/channels/log.h>
|
||||
#include <freerdp/client/ainput.h>
|
||||
#include <freerdp/channels/ainput.h>
|
||||
|
||||
#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;
|
||||
}
|
43
channels/ainput/client/ainput_main.h
Normal file
43
channels/ainput/client/ainput_main.h
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#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 */
|
27
channels/ainput/server/CMakeLists.txt
Normal file
27
channels/ainput/server/CMakeLists.txt
Normal file
@ -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_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")
|
418
channels/ainput/server/ainput_main.c
Normal file
418
channels/ainput/server/ainput_main.c
Normal file
@ -0,0 +1,418 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#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/server/ainput.h>
|
||||
#include <freerdp/channels/ainput.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -54,6 +54,10 @@
|
||||
#include <freerdp/server/disp.h>
|
||||
#include <freerdp/server/gfxredir.h>
|
||||
|
||||
#if defined(CHANNEL_AINPUT_SERVER)
|
||||
#include <freerdp/server/ainput.h>
|
||||
#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
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
60
include/freerdp/channels/ainput.h
Normal file
60
include/freerdp/channels/ainput.h
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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_CHANNEL_AINPUT_H
|
||||
#define FREERDP_CHANNEL_AINPUT_H
|
||||
|
||||
#include <freerdp/api.h>
|
||||
#include <freerdp/dvc.h>
|
||||
#include <freerdp/types.h>
|
||||
|
||||
#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 */
|
39
include/freerdp/client/ainput.h
Normal file
39
include/freerdp/client/ainput.h
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* Display Update Virtual Channel Extension
|
||||
*
|
||||
* Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||||
* Copyright 2015 Thincast Technologies GmbH
|
||||
* Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com>
|
||||
*
|
||||
* 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 <winpr/assert.h>
|
||||
#include <freerdp/channels/ainput.h>
|
||||
|
||||
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 */
|
88
include/freerdp/server/ainput.h
Normal file
88
include/freerdp/server/ainput.h
Normal file
@ -0,0 +1,88 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* AInput 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_SERVER_H
|
||||
#define FREERDP_CHANNEL_AINPUT_SERVER_H
|
||||
|
||||
#include <freerdp/channels/wtsvc.h>
|
||||
|
||||
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 */
|
Loading…
Reference in New Issue
Block a user