RDPEDISP server side implementation (#5414)
* libfreerdp: Add RDPEDISP server implementation * server/proxy: Add RDPEDISP support
This commit is contained in:
parent
4a6b843f16
commit
d9366df448
@ -20,3 +20,7 @@ define_channel("disp")
|
||||
if(WITH_CLIENT_CHANNELS)
|
||||
add_channel_client(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
|
||||
if(WITH_SERVER_CHANNELS)
|
||||
add_channel_server(${MODULE_PREFIX} ${CHANNEL_NAME})
|
||||
endif()
|
||||
|
@ -19,7 +19,9 @@ define_channel_client("disp")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
disp_main.c
|
||||
disp_main.h)
|
||||
disp_main.h
|
||||
../disp_common.c
|
||||
../disp_common.h)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
|
101
channels/disp/client/disp_main.c
Executable file → Normal file
101
channels/disp/client/disp_main.c
Executable file → Normal file
@ -40,6 +40,7 @@
|
||||
#include <freerdp/addin.h>
|
||||
|
||||
#include "disp_main.h"
|
||||
#include "../disp_common.h"
|
||||
|
||||
struct _DISP_CHANNEL_CALLBACK
|
||||
{
|
||||
@ -79,40 +80,39 @@ typedef struct _DISP_PLUGIN DISP_PLUGIN;
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT disp_send_display_control_monitor_layout_pdu(DISP_CHANNEL_CALLBACK* callback, UINT32 NumMonitors, DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors)
|
||||
UINT disp_send_display_control_monitor_layout_pdu(DISP_CHANNEL_CALLBACK* callback,
|
||||
UINT32 NumMonitors, DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors)
|
||||
{
|
||||
UINT status;
|
||||
wStream* s;
|
||||
UINT32 type;
|
||||
UINT32 index;
|
||||
UINT32 length;
|
||||
DISP_PLUGIN* disp;
|
||||
UINT32 MonitorLayoutSize;
|
||||
|
||||
DISPLAY_CONTROL_HEADER header;
|
||||
disp = (DISP_PLUGIN*) callback->plugin;
|
||||
MonitorLayoutSize = DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE;
|
||||
header.length = 8 + 8 + (NumMonitors * MonitorLayoutSize);
|
||||
header.type = DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT;
|
||||
|
||||
MonitorLayoutSize = 40;
|
||||
s = Stream_New(NULL, header.length);
|
||||
|
||||
length = 8 + 8 + (NumMonitors * MonitorLayoutSize);
|
||||
|
||||
type = DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT;
|
||||
|
||||
s = Stream_New(NULL, length);
|
||||
if(!s)
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT32(s, type); /* Type (4 bytes) */
|
||||
Stream_Write_UINT32(s, length); /* Length (4 bytes) */
|
||||
if ((status = disp_write_header(s, &header)))
|
||||
{
|
||||
WLog_ERR(TAG, "Failed to write header with error %"PRIu32"!", status);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (NumMonitors > disp->MaxNumMonitors)
|
||||
NumMonitors = disp->MaxNumMonitors;
|
||||
|
||||
Stream_Write_UINT32(s, MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */
|
||||
Stream_Write_UINT32(s, NumMonitors); /* NumMonitors (4 bytes) */
|
||||
|
||||
WLog_DBG(TAG, "disp_send_display_control_monitor_layout_pdu: NumMonitors=%"PRIu32"", NumMonitors);
|
||||
|
||||
for (index = 0; index < NumMonitors; index++)
|
||||
@ -144,20 +144,19 @@ UINT disp_send_display_control_monitor_layout_pdu(DISP_CHANNEL_CALLBACK* callbac
|
||||
Stream_Write_UINT32(s, Monitors[index].Orientation); /* Orientation (4 bytes) */
|
||||
Stream_Write_UINT32(s, Monitors[index].DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */
|
||||
Stream_Write_UINT32(s, Monitors[index].DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */
|
||||
|
||||
WLog_DBG(TAG, "\t%d : Flags: 0x%08"PRIX32" Left/Top: (%"PRId32",%"PRId32") W/H=%"PRIu32"x%"PRIu32")", index,
|
||||
Monitors[index].Flags, Monitors[index].Left, Monitors[index].Top, Monitors[index].Width,
|
||||
Monitors[index].Height);
|
||||
WLog_DBG(TAG,
|
||||
"\t%d : Flags: 0x%08"PRIX32" Left/Top: (%"PRId32",%"PRId32") W/H=%"PRIu32"x%"PRIu32")", index,
|
||||
Monitors[index].Flags, Monitors[index].Left, Monitors[index].Top, Monitors[index].Width,
|
||||
Monitors[index].Height);
|
||||
WLog_DBG(TAG, "\t PhysicalWidth: %"PRIu32" PhysicalHeight: %"PRIu32" Orientation: %"PRIu32"",
|
||||
Monitors[index].PhysicalWidth, Monitors[index].PhysicalHeight, Monitors[index].Orientation);
|
||||
Monitors[index].PhysicalWidth, Monitors[index].PhysicalHeight, Monitors[index].Orientation);
|
||||
}
|
||||
|
||||
out:
|
||||
Stream_SealLength(s);
|
||||
|
||||
status = callback->channel->Write(callback->channel, (UINT32) Stream_Length(s), Stream_Buffer(s), NULL);
|
||||
|
||||
status = callback->channel->Write(callback->channel, (UINT32) Stream_Length(s), Stream_Buffer(s),
|
||||
NULL);
|
||||
Stream_Free(s, TRUE);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -169,11 +168,10 @@ UINT disp_send_display_control_monitor_layout_pdu(DISP_CHANNEL_CALLBACK* callbac
|
||||
UINT disp_recv_display_control_caps_pdu(DISP_CHANNEL_CALLBACK* callback, wStream* s)
|
||||
{
|
||||
DISP_PLUGIN* disp;
|
||||
DispClientContext *context;
|
||||
DispClientContext* context;
|
||||
UINT ret = CHANNEL_RC_OK;
|
||||
|
||||
disp = (DISP_PLUGIN*) callback->plugin;
|
||||
context = (DispClientContext *)disp->iface.pInterface;
|
||||
context = (DispClientContext*)disp->iface.pInterface;
|
||||
|
||||
if (Stream_GetRemainingLength(s) < 12)
|
||||
{
|
||||
@ -186,7 +184,8 @@ UINT disp_recv_display_control_caps_pdu(DISP_CHANNEL_CALLBACK* callback, wStream
|
||||
Stream_Read_UINT32(s, disp->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */
|
||||
|
||||
if (context->DisplayControlCaps)
|
||||
ret = context->DisplayControlCaps(context, disp->MaxNumMonitors, disp->MaxMonitorAreaFactorA, disp->MaxMonitorAreaFactorB);
|
||||
ret = context->DisplayControlCaps(context, disp->MaxNumMonitors, disp->MaxMonitorAreaFactorA,
|
||||
disp->MaxMonitorAreaFactorB);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -198,8 +197,8 @@ UINT disp_recv_display_control_caps_pdu(DISP_CHANNEL_CALLBACK* callback, wStream
|
||||
*/
|
||||
UINT disp_recv_pdu(DISP_CHANNEL_CALLBACK* callback, wStream* s)
|
||||
{
|
||||
UINT32 type;
|
||||
UINT32 length;
|
||||
UINT32 error;
|
||||
DISPLAY_CONTROL_HEADER header;
|
||||
|
||||
if (Stream_GetRemainingLength(s) < 8)
|
||||
{
|
||||
@ -207,18 +206,25 @@ UINT disp_recv_pdu(DISP_CHANNEL_CALLBACK* callback, wStream* s)
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
Stream_Read_UINT32(s, type); /* Type (4 bytes) */
|
||||
Stream_Read_UINT32(s, length); /* Length (4 bytes) */
|
||||
if ((error = disp_read_header(s, &header)))
|
||||
{
|
||||
WLog_ERR(TAG, "disp_read_header failed with error %"PRIu32"!", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
//WLog_ERR(TAG, "Type: %"PRIu32" Length: %"PRIu32"", type, length);
|
||||
if (!Stream_EnsureRemainingCapacity(s, header.length))
|
||||
{
|
||||
WLog_ERR(TAG, "not enough remaining data");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
switch (header.type)
|
||||
{
|
||||
case DISPLAY_CONTROL_PDU_TYPE_CAPS:
|
||||
return disp_recv_display_control_caps_pdu(callback, s);
|
||||
|
||||
default:
|
||||
WLog_ERR(TAG, "Type %"PRIu32" not recognized!", type);
|
||||
WLog_ERR(TAG, "Type %"PRIu32" not recognized!", header.type);
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
@ -228,10 +234,9 @@ UINT disp_recv_pdu(DISP_CHANNEL_CALLBACK* callback, wStream* s)
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT disp_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream *data)
|
||||
static UINT disp_on_data_received(IWTSVirtualChannelCallback* pChannelCallback, wStream* data)
|
||||
{
|
||||
DISP_CHANNEL_CALLBACK* callback = (DISP_CHANNEL_CALLBACK*) pChannelCallback;
|
||||
|
||||
return disp_recv_pdu(callback, data);
|
||||
}
|
||||
|
||||
@ -252,12 +257,11 @@ static UINT disp_on_close(IWTSVirtualChannelCallback* pChannelCallback)
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT disp_on_new_channel_connection(IWTSListenerCallback* pListenerCallback,
|
||||
IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept,
|
||||
IWTSVirtualChannelCallback** ppCallback)
|
||||
IWTSVirtualChannel* pChannel, BYTE* Data, BOOL* pbAccept,
|
||||
IWTSVirtualChannelCallback** ppCallback)
|
||||
{
|
||||
DISP_CHANNEL_CALLBACK* callback;
|
||||
DISP_LISTENER_CALLBACK* listener_callback = (DISP_LISTENER_CALLBACK*) pListenerCallback;
|
||||
|
||||
callback = (DISP_CHANNEL_CALLBACK*) calloc(1, sizeof(DISP_CHANNEL_CALLBACK));
|
||||
|
||||
if (!callback)
|
||||
@ -272,9 +276,7 @@ static UINT disp_on_new_channel_connection(IWTSListenerCallback* pListenerCallba
|
||||
callback->channel_mgr = listener_callback->channel_mgr;
|
||||
callback->channel = pChannel;
|
||||
listener_callback->channel_callback = callback;
|
||||
|
||||
*ppCallback = (IWTSVirtualChannelCallback*) callback;
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
@ -287,7 +289,6 @@ static UINT disp_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManage
|
||||
{
|
||||
UINT status;
|
||||
DISP_PLUGIN* disp = (DISP_PLUGIN*) pPlugin;
|
||||
|
||||
disp->listener_callback = (DISP_LISTENER_CALLBACK*) calloc(1, sizeof(DISP_LISTENER_CALLBACK));
|
||||
|
||||
if (!disp->listener_callback)
|
||||
@ -299,12 +300,9 @@ static UINT disp_plugin_initialize(IWTSPlugin* pPlugin, IWTSVirtualChannelManage
|
||||
disp->listener_callback->iface.OnNewChannelConnection = disp_on_new_channel_connection;
|
||||
disp->listener_callback->plugin = pPlugin;
|
||||
disp->listener_callback->channel_mgr = pChannelMgr;
|
||||
|
||||
status = pChannelMgr->CreateListener(pChannelMgr, DISP_DVC_CHANNEL_NAME, 0,
|
||||
(IWTSListenerCallback*) disp->listener_callback, &(disp->listener));
|
||||
|
||||
(IWTSListenerCallback*) disp->listener_callback, &(disp->listener));
|
||||
disp->listener->pInterface = disp->iface.pInterface;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@ -331,11 +329,11 @@ static UINT disp_plugin_terminated(IWTSPlugin* pPlugin)
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT disp_send_monitor_layout(DispClientContext* context, UINT32 NumMonitors, DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors)
|
||||
UINT disp_send_monitor_layout(DispClientContext* context, UINT32 NumMonitors,
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors)
|
||||
{
|
||||
DISP_PLUGIN* disp = (DISP_PLUGIN*) context->handle;
|
||||
DISP_CHANNEL_CALLBACK* callback = disp->listener_callback->channel_callback;
|
||||
|
||||
return disp_send_display_control_monitor_layout_pdu(callback, NumMonitors, Monitors);
|
||||
}
|
||||
|
||||
@ -355,11 +353,12 @@ UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
DISP_PLUGIN* disp;
|
||||
DispClientContext* context;
|
||||
|
||||
disp = (DISP_PLUGIN*) pEntryPoints->GetPlugin(pEntryPoints, "disp");
|
||||
|
||||
if (!disp)
|
||||
{
|
||||
disp = (DISP_PLUGIN*) calloc(1, sizeof(DISP_PLUGIN));
|
||||
|
||||
if (!disp)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
@ -373,8 +372,8 @@ UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
|
||||
disp->MaxNumMonitors = 16;
|
||||
disp->MaxMonitorAreaFactorA = 8192;
|
||||
disp->MaxMonitorAreaFactorB = 8192;
|
||||
|
||||
context = (DispClientContext*) calloc(1, sizeof(DispClientContext));
|
||||
|
||||
if (!context)
|
||||
{
|
||||
WLog_ERR(TAG, "calloc failed!");
|
||||
@ -384,9 +383,7 @@ UINT DVCPluginEntry(IDRDYNVC_ENTRY_POINTS* pEntryPoints)
|
||||
|
||||
context->handle = (void*) disp;
|
||||
context->SendMonitorLayout = disp_send_monitor_layout;
|
||||
|
||||
disp->iface.pInterface = (void*) context;
|
||||
|
||||
error = pEntryPoints->RegisterPlugin(pEntryPoints, "disp", (IWTSPlugin*) disp);
|
||||
}
|
||||
else
|
||||
|
@ -33,8 +33,6 @@
|
||||
|
||||
#include <freerdp/client/disp.h>
|
||||
|
||||
#define DISPLAY_CONTROL_PDU_TYPE_CAPS 0x00000005
|
||||
#define DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT 0x00000002
|
||||
|
||||
#define TAG CHANNELS_TAG("disp.client")
|
||||
|
||||
|
60
channels/disp/disp_common.c
Normal file
60
channels/disp/disp_common.c
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* RDPEDISP Virtual Channel Extension
|
||||
*
|
||||
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/stream.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
#define TAG CHANNELS_TAG("disp.common")
|
||||
|
||||
#include "disp_common.h"
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header)
|
||||
{
|
||||
if (Stream_GetRemainingLength(s) < 8)
|
||||
{
|
||||
WLog_ERR(TAG, "header parsing failed: not enough data!");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
Stream_Read_UINT32(s, header->type);
|
||||
Stream_Read_UINT32(s, header->length);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header)
|
||||
{
|
||||
Stream_Write_UINT32(s, header->type);
|
||||
Stream_Write_UINT32(s, header->length);
|
||||
return CHANNEL_RC_OK;
|
||||
}
|
32
channels/disp/disp_common.h
Normal file
32
channels/disp/disp_common.h
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* RDPEDISP Virtual Channel Extension
|
||||
*
|
||||
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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_DISP_COMMON_H
|
||||
#define FREERDP_CHANNEL_DISP_COMMON_H
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/stream.h>
|
||||
|
||||
#include <freerdp/channels/disp.h>
|
||||
#include <freerdp/api.h>
|
||||
|
||||
FREERDP_LOCAL UINT disp_read_header(wStream* s, DISPLAY_CONTROL_HEADER* header);
|
||||
FREERDP_LOCAL UINT disp_write_header(wStream* s, const DISPLAY_CONTROL_HEADER* header);
|
||||
|
||||
#endif /* FREERDP_CHANNEL_DISP_COMMON_H */
|
32
channels/disp/server/CMakeLists.txt
Normal file
32
channels/disp/server/CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
||||
# FreeRDP: A Remote Desktop Protocol Implementation
|
||||
# FreeRDP cmake build script
|
||||
#
|
||||
# Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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.
|
||||
|
||||
define_channel_server("disp")
|
||||
|
||||
set(${MODULE_PREFIX}_SRCS
|
||||
disp_main.c
|
||||
disp_main.h
|
||||
../disp_common.c
|
||||
../disp_common.h
|
||||
)
|
||||
|
||||
include_directories(..)
|
||||
|
||||
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")
|
576
channels/disp/server/disp_main.c
Normal file
576
channels/disp/server/disp_main.c
Normal file
@ -0,0 +1,576 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* RDPEDISP Virtual Channel Extension
|
||||
*
|
||||
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "disp_main.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <winpr/crt.h>
|
||||
#include <winpr/synch.h>
|
||||
#include <winpr/thread.h>
|
||||
#include <winpr/stream.h>
|
||||
#include <winpr/sysinfo.h>
|
||||
#include <freerdp/channels/wtsvc.h>
|
||||
#include <freerdp/channels/log.h>
|
||||
|
||||
|
||||
#include <freerdp/server/disp.h>
|
||||
#include "../disp_common.h"
|
||||
|
||||
#define TAG CHANNELS_TAG("rdpedisp.server")
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
|
||||
static wStream* disp_server_single_packet_new(UINT32 type, UINT32 length)
|
||||
{
|
||||
UINT error;
|
||||
DISPLAY_CONTROL_HEADER header;
|
||||
wStream* s = Stream_New(NULL, DISPLAY_CONTROL_HEADER_LENGTH + length);
|
||||
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
goto error;
|
||||
}
|
||||
|
||||
header.type = type;
|
||||
header.length = length;
|
||||
|
||||
if ((error = disp_write_header(s, &header)))
|
||||
{
|
||||
WLog_ERR(TAG, "Failed to write header with error %"PRIu32"!", error);
|
||||
goto error;
|
||||
}
|
||||
|
||||
return s;
|
||||
error:
|
||||
Stream_Free(s, TRUE);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static BOOL disp_server_is_monitor_layout_valid(DISPLAY_CONTROL_MONITOR_LAYOUT* monitor)
|
||||
{
|
||||
if (monitor->Width < DISPLAY_CONTROL_MIN_MONITOR_WIDTH ||
|
||||
monitor->Width > DISPLAY_CONTROL_MAX_MONITOR_WIDTH)
|
||||
{
|
||||
WLog_WARN(TAG, "Received invalid value for monitor->Width: %"PRIu32"", monitor->Width);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (monitor->Height < DISPLAY_CONTROL_MIN_MONITOR_HEIGHT ||
|
||||
monitor->Height > DISPLAY_CONTROL_MAX_MONITOR_HEIGHT)
|
||||
{
|
||||
WLog_WARN(TAG, "Received invalid value for monitor->Height: %"PRIu32"", monitor->Width);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (monitor->PhysicalWidth < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_WIDTH ||
|
||||
monitor->PhysicalWidth > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_WIDTH)
|
||||
{
|
||||
WLog_WARN(TAG, "Received invalid value for monitor->PhysicalWidth: %"PRIu32"",
|
||||
monitor->PhysicalWidth);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (monitor->PhysicalHeight < DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_HEIGHT ||
|
||||
monitor->PhysicalHeight > DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_HEIGHT)
|
||||
{
|
||||
WLog_WARN(TAG, "Received invalid value for monitor->Height: %"PRIu32"", monitor->PhysicalHeight);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
switch (monitor->Orientation)
|
||||
{
|
||||
case ORIENTATION_LANDSCAPE:
|
||||
case ORIENTATION_PORTRAIT:
|
||||
case ORIENTATION_LANDSCAPE_FLIPPED:
|
||||
case ORIENTATION_PORTRAIT_FLIPPED:
|
||||
break;
|
||||
|
||||
default:
|
||||
WLog_WARN(TAG, "Received incorrect value for monitor->Orientation: %"PRIu32"",
|
||||
monitor->Orientation);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT disp_recv_display_control_monitor_layout_pdu(wStream* s, DispServerContext* context)
|
||||
{
|
||||
UINT32 error = CHANNEL_RC_OK;
|
||||
UINT32 index;
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT_PDU pdu;
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT* monitor;
|
||||
|
||||
if (Stream_GetRemainingLength(s) < 8)
|
||||
{
|
||||
WLog_ERR(TAG, "not enough data!");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
Stream_Read_UINT32(s, pdu.MonitorLayoutSize); /* MonitorLayoutSize (4 bytes) */
|
||||
|
||||
if (pdu.MonitorLayoutSize != DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE)
|
||||
{
|
||||
WLog_ERR(TAG, "MonitorLayoutSize is set to %"PRIu32". expected %"PRIu32"", pdu.MonitorLayoutSize,
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
Stream_Read_UINT32(s, pdu.NumMonitors); /* NumMonitors (4 bytes) */
|
||||
|
||||
if (pdu.NumMonitors > context->MaxNumMonitors)
|
||||
{
|
||||
WLog_ERR(TAG, "NumMonitors (%"PRIu32")> server MaxNumMonitors (%"PRIu32")", pdu.NumMonitors,
|
||||
context->MaxNumMonitors);
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
if (Stream_GetRemainingLength(s) < DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE * pdu.NumMonitors)
|
||||
{
|
||||
WLog_ERR(TAG, "not enough data!");
|
||||
return ERROR_INVALID_DATA;
|
||||
}
|
||||
|
||||
pdu.Monitors = (DISPLAY_CONTROL_MONITOR_LAYOUT*) calloc(pdu.NumMonitors,
|
||||
sizeof(DISPLAY_CONTROL_MONITOR_LAYOUT));
|
||||
|
||||
if (!pdu.Monitors)
|
||||
{
|
||||
WLog_ERR(TAG, "disp_recv_display_control_monitor_layout_pdu(): calloc failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
WLog_DBG(TAG, "disp_recv_display_control_monitor_layout_pdu: NumMonitors=%"PRIu32"",
|
||||
pdu.NumMonitors);
|
||||
|
||||
for (index = 0; index < pdu.NumMonitors; index++)
|
||||
{
|
||||
monitor = &(pdu.Monitors[index]);
|
||||
Stream_Read_UINT32(s, monitor->Flags); /* Flags (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->Left); /* Left (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->Top); /* Top (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->Width); /* Width (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->Height); /* Height (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->PhysicalWidth); /* PhysicalWidth (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->PhysicalHeight); /* PhysicalHeight (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->Orientation); /* Orientation (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->DesktopScaleFactor); /* DesktopScaleFactor (4 bytes) */
|
||||
Stream_Read_UINT32(s, monitor->DeviceScaleFactor); /* DeviceScaleFactor (4 bytes) */
|
||||
WLog_DBG(TAG,
|
||||
"\t%d : Flags: 0x%08"PRIX32" Left/Top: (%"PRId32",%"PRId32") W/H=%"PRIu32"x%"PRIu32")", index,
|
||||
monitor->Flags, monitor->Left, monitor->Top,
|
||||
monitor->Width,
|
||||
monitor->Height);
|
||||
WLog_DBG(TAG, "\t PhysicalWidth: %"PRIu32" PhysicalHeight: %"PRIu32" Orientation: %"PRIu32"",
|
||||
monitor->PhysicalWidth, monitor->PhysicalHeight,
|
||||
monitor->Orientation);
|
||||
|
||||
if (!disp_server_is_monitor_layout_valid(monitor))
|
||||
{
|
||||
error = ERROR_INVALID_DATA;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (context)
|
||||
IFCALLRET(context->DispMonitorLayout, error, context, &pdu);
|
||||
|
||||
out:
|
||||
free(pdu.Monitors);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
static UINT disp_server_receive_pdu(DispServerContext* context, wStream* s)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
size_t beg, end;
|
||||
DISPLAY_CONTROL_HEADER header;
|
||||
beg = Stream_GetPosition(s);
|
||||
|
||||
if ((error = disp_read_header(s, &header)))
|
||||
{
|
||||
WLog_ERR(TAG, "disp_read_header failed with error %"PRIu32"!", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
switch (header.type)
|
||||
{
|
||||
case DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT:
|
||||
if ((error = disp_recv_display_control_monitor_layout_pdu(s, context)))
|
||||
WLog_ERR(TAG, "disp_recv_display_control_monitor_layout_pdu "
|
||||
"failed with error %"PRIu32"!", error);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
error = CHANNEL_RC_BAD_PROC;
|
||||
WLog_WARN(TAG, "Received unknown PDU type: %"PRIu32"", header.type);
|
||||
break;
|
||||
}
|
||||
|
||||
end = Stream_GetPosition(s);
|
||||
|
||||
if (end != (beg + header.length))
|
||||
{
|
||||
WLog_ERR(TAG, "Unexpected DISP pdu end: Actual: %d, Expected: %"PRIu32"",
|
||||
end, (beg + header.length));
|
||||
Stream_SetPosition(s, (beg + header.length));
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static UINT disp_server_handle_messages(DispServerContext* context)
|
||||
{
|
||||
DWORD BytesReturned;
|
||||
void* buffer;
|
||||
UINT ret = CHANNEL_RC_OK;
|
||||
DispServerPrivate* priv = context->priv;
|
||||
wStream* s = priv->input_stream;
|
||||
|
||||
/* Check whether the dynamic channel is ready */
|
||||
if (!priv->isReady)
|
||||
{
|
||||
if (WTSVirtualChannelQuery(priv->disp_channel,
|
||||
WTSVirtualChannelReady,
|
||||
&buffer, &BytesReturned) == FALSE)
|
||||
{
|
||||
if (GetLastError() == ERROR_NO_DATA)
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
WLog_ERR(TAG, "WTSVirtualChannelQuery failed");
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
priv->isReady = *((BOOL*) buffer);
|
||||
WTSFreeMemory(buffer);
|
||||
}
|
||||
|
||||
/* Consume channel event only after the gfx dynamic channel is ready */
|
||||
Stream_SetPosition(s, 0);
|
||||
|
||||
if (!WTSVirtualChannelRead(priv->disp_channel,
|
||||
0, NULL, 0, &BytesReturned))
|
||||
{
|
||||
if (GetLastError() == ERROR_NO_DATA)
|
||||
return ERROR_NO_DATA;
|
||||
|
||||
WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
if (BytesReturned < 1)
|
||||
return CHANNEL_RC_OK;
|
||||
|
||||
if (!Stream_EnsureRemainingCapacity(s, BytesReturned))
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
if (WTSVirtualChannelRead(priv->disp_channel, 0,
|
||||
(PCHAR) Stream_Buffer(s),
|
||||
Stream_Capacity(s), &BytesReturned) == FALSE)
|
||||
{
|
||||
WLog_ERR(TAG, "WTSVirtualChannelRead failed!");
|
||||
return ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
Stream_SetLength(s, BytesReturned);
|
||||
Stream_SetPosition(s, 0);
|
||||
|
||||
while (Stream_GetPosition(s) < Stream_Length(s))
|
||||
{
|
||||
if ((ret = disp_server_receive_pdu(context, s)))
|
||||
{
|
||||
WLog_ERR(TAG, "disp_server_receive_pdu "
|
||||
"failed with error %"PRIu32"!", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DWORD WINAPI disp_server_thread_func(LPVOID arg)
|
||||
{
|
||||
DispServerContext* context = (DispServerContext*) arg;
|
||||
DispServerPrivate* priv = context->priv;
|
||||
DWORD status;
|
||||
DWORD nCount;
|
||||
HANDLE events[8];
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
nCount = 0;
|
||||
events[nCount++] = priv->stopEvent;
|
||||
events[nCount++] = priv->channelEvent;
|
||||
|
||||
/* Main virtual channel loop. RDPEDISP do not need version negotiation */
|
||||
while (TRUE)
|
||||
{
|
||||
status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
|
||||
|
||||
if (status == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForMultipleObjects failed with error %"PRIu32"", error);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Stop Event */
|
||||
if (status == WAIT_OBJECT_0)
|
||||
break;
|
||||
|
||||
if ((error = disp_server_handle_messages(context)))
|
||||
{
|
||||
WLog_ERR(TAG, "disp_server_handle_messages failed with error %"PRIu32"",
|
||||
error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ExitThread(error);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT disp_server_open(DispServerContext* context)
|
||||
{
|
||||
UINT rc = ERROR_INTERNAL_ERROR;
|
||||
DispServerPrivate* priv = context->priv;
|
||||
DWORD BytesReturned = 0;
|
||||
PULONG pSessionId = NULL;
|
||||
void* buffer;
|
||||
buffer = NULL;
|
||||
priv->SessionId = WTS_CURRENT_SESSION;
|
||||
|
||||
if (WTSQuerySessionInformationA(context->vcm, WTS_CURRENT_SESSION,
|
||||
WTSSessionId, (LPSTR*) &pSessionId,
|
||||
&BytesReturned) == FALSE)
|
||||
{
|
||||
WLog_ERR(TAG, "WTSQuerySessionInformationA failed!");
|
||||
rc = ERROR_INTERNAL_ERROR;
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
priv->SessionId = (DWORD) * pSessionId;
|
||||
priv->disp_channel = (HANDLE) WTSVirtualChannelOpenEx(priv->SessionId,
|
||||
DISP_DVC_CHANNEL_NAME,
|
||||
WTS_CHANNEL_OPTION_DYNAMIC);
|
||||
|
||||
if (!priv->disp_channel)
|
||||
{
|
||||
WLog_ERR(TAG, "WTSVirtualChannelOpenEx failed!");
|
||||
rc = GetLastError();
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
/* Query for channel event handle */
|
||||
if (!WTSVirtualChannelQuery(priv->disp_channel, WTSVirtualEventHandle,
|
||||
&buffer, &BytesReturned)
|
||||
|| (BytesReturned != sizeof(HANDLE)))
|
||||
{
|
||||
WLog_ERR(TAG, "WTSVirtualChannelQuery failed "
|
||||
"or invalid returned size(%"PRIu32")",
|
||||
BytesReturned);
|
||||
|
||||
if (buffer)
|
||||
WTSFreeMemory(buffer);
|
||||
|
||||
rc = ERROR_INTERNAL_ERROR;
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
CopyMemory(&priv->channelEvent, buffer, sizeof(HANDLE));
|
||||
WTSFreeMemory(buffer);
|
||||
|
||||
if (priv->thread == NULL)
|
||||
{
|
||||
if (!(priv->stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
|
||||
{
|
||||
WLog_ERR(TAG, "CreateEvent failed!");
|
||||
rc = ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
|
||||
if (!(priv->thread = CreateThread(NULL, 0, disp_server_thread_func, (void*) context, 0, NULL)))
|
||||
{
|
||||
WLog_ERR(TAG, "CreateEvent failed!");
|
||||
CloseHandle(priv->stopEvent);
|
||||
priv->stopEvent = NULL;
|
||||
rc = ERROR_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return CHANNEL_RC_OK;
|
||||
out_close:
|
||||
WTSVirtualChannelClose(priv->disp_channel);
|
||||
priv->disp_channel = NULL;
|
||||
priv->channelEvent = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static UINT disp_server_packet_send(DispServerContext* context, wStream* s)
|
||||
{
|
||||
UINT ret;
|
||||
ULONG written;
|
||||
|
||||
if (!WTSVirtualChannelWrite(context->priv->disp_channel,
|
||||
(PCHAR) Stream_Buffer(s),
|
||||
Stream_GetPosition(s), &written))
|
||||
{
|
||||
WLog_ERR(TAG, "WTSVirtualChannelWrite failed!");
|
||||
ret = ERROR_INTERNAL_ERROR;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (written < Stream_GetPosition(s))
|
||||
{
|
||||
WLog_WARN(TAG, "Unexpected bytes written: %"PRIu32"/%"PRIuz"",
|
||||
written, Stream_GetPosition(s));
|
||||
}
|
||||
|
||||
ret = CHANNEL_RC_OK;
|
||||
out:
|
||||
Stream_Free(s, TRUE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT disp_server_send_caps_pdu(DispServerContext* context)
|
||||
{
|
||||
wStream* s = disp_server_single_packet_new(DISPLAY_CONTROL_PDU_TYPE_CAPS, 12);
|
||||
|
||||
if (!s)
|
||||
{
|
||||
WLog_ERR(TAG, "disp_server_single_packet_new failed!");
|
||||
return CHANNEL_RC_NO_MEMORY;
|
||||
}
|
||||
|
||||
Stream_Write_UINT32(s, context->MaxNumMonitors); /* MaxNumMonitors (4 bytes) */
|
||||
Stream_Write_UINT32(s, context->MaxMonitorAreaFactorA); /* MaxMonitorAreaFactorA (4 bytes) */
|
||||
Stream_Write_UINT32(s, context->MaxMonitorAreaFactorB); /* MaxMonitorAreaFactorB (4 bytes) */
|
||||
return disp_server_packet_send(context, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function description
|
||||
*
|
||||
* @return 0 on success, otherwise a Win32 error code
|
||||
*/
|
||||
static UINT disp_server_close(DispServerContext* context)
|
||||
{
|
||||
UINT error = CHANNEL_RC_OK;
|
||||
DispServerPrivate* priv = context->priv;
|
||||
|
||||
if (priv->thread)
|
||||
{
|
||||
SetEvent(priv->stopEvent);
|
||||
|
||||
if (WaitForSingleObject(priv->thread, INFINITE) == WAIT_FAILED)
|
||||
{
|
||||
error = GetLastError();
|
||||
WLog_ERR(TAG, "WaitForSingleObject failed with error %"PRIu32"", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
CloseHandle(priv->thread);
|
||||
CloseHandle(priv->stopEvent);
|
||||
priv->thread = NULL;
|
||||
priv->stopEvent = NULL;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
DispServerContext* disp_server_context_new(HANDLE vcm)
|
||||
{
|
||||
DispServerContext* context;
|
||||
DispServerPrivate* priv;
|
||||
context = (DispServerContext*) calloc(1, sizeof(DispServerContext));
|
||||
|
||||
if (!context)
|
||||
{
|
||||
WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerContext failed!");
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
priv = context->priv = (DispServerPrivate*) calloc(1, sizeof(DispServerPrivate));
|
||||
|
||||
if (!context->priv)
|
||||
{
|
||||
WLog_ERR(TAG, "disp_server_context_new(): calloc DispServerPrivate failed!");
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
priv->input_stream = Stream_New(NULL, 4);
|
||||
|
||||
if (!priv->input_stream)
|
||||
{
|
||||
WLog_ERR(TAG, "Stream_New failed!");
|
||||
goto out_free_priv;
|
||||
}
|
||||
|
||||
context->vcm = vcm;
|
||||
context->Open = disp_server_open;
|
||||
context->Close = disp_server_close;
|
||||
context->DisplayControlCaps = disp_server_send_caps_pdu;
|
||||
priv->isReady = FALSE;
|
||||
return context;
|
||||
out_free_priv:
|
||||
free(context->priv);
|
||||
out_free:
|
||||
free(context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void disp_server_context_free(DispServerContext* context)
|
||||
{
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
disp_server_close(context);
|
||||
|
||||
if (context->priv)
|
||||
{
|
||||
Stream_Free(context->priv->input_stream, TRUE);
|
||||
free(context->priv);
|
||||
}
|
||||
|
||||
free(context);
|
||||
}
|
37
channels/disp/server/disp_main.h
Normal file
37
channels/disp/server/disp_main.h
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* RDPEDISP Virtual Channel Extension
|
||||
*
|
||||
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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_DISP_SERVER_MAIN_H
|
||||
#define FREERDP_CHANNEL_DISP_SERVER_MAIN_H
|
||||
|
||||
#include <freerdp/server/disp.h>
|
||||
|
||||
struct _disp_server_private
|
||||
{
|
||||
BOOL isReady;
|
||||
wStream* input_stream;
|
||||
HANDLE channelEvent;
|
||||
HANDLE thread;
|
||||
HANDLE stopEvent;
|
||||
DWORD SessionId;
|
||||
|
||||
void* disp_channel;
|
||||
};
|
||||
|
||||
#endif /* FREERDP_CHANNEL_DISP_SERVER_MAIN_H */
|
@ -50,6 +50,7 @@
|
||||
#include <freerdp/server/remdesk.h>
|
||||
#include <freerdp/server/encomsp.h>
|
||||
#include <freerdp/server/rdpgfx.h>
|
||||
#include <freerdp/server/disp.h>
|
||||
|
||||
void freerdp_channels_dummy()
|
||||
{
|
||||
@ -63,6 +64,7 @@ void freerdp_channels_dummy()
|
||||
RemdeskServerContext* remdesk;
|
||||
EncomspServerContext* encomsp;
|
||||
RdpgfxServerContext* rdpgfx;
|
||||
DispServerContext* disp;
|
||||
audin = audin_server_context_new(NULL);
|
||||
audin_server_context_free(audin);
|
||||
rdpsnd = rdpsnd_server_context_new(NULL);
|
||||
@ -83,6 +85,8 @@ void freerdp_channels_dummy()
|
||||
encomsp_server_context_free(encomsp);
|
||||
rdpgfx = rdpgfx_server_context_new(NULL);
|
||||
rdpgfx_server_context_free(rdpgfx);
|
||||
disp = disp_server_context_new(NULL);
|
||||
disp_server_context_free(disp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
82
include/freerdp/channels/disp.h
Normal file
82
include/freerdp/channels/disp.h
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* RDPEDISP Virtual Channel Extension
|
||||
*
|
||||
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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_DISP_H
|
||||
#define FREERDP_CHANNEL_DISP_H
|
||||
|
||||
#include <freerdp/api.h>
|
||||
#include <freerdp/types.h>
|
||||
|
||||
#define DISPLAY_CONTROL_PDU_TYPE_CAPS 0x00000005
|
||||
#define DISPLAY_CONTROL_PDU_TYPE_MONITOR_LAYOUT 0x00000002
|
||||
#define DISPLAY_CONTROL_MONITOR_LAYOUT_SIZE 40
|
||||
|
||||
#define DISP_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::DisplayControl"
|
||||
#define ORIENTATION_LANDSCAPE 0
|
||||
#define ORIENTATION_PORTRAIT 90
|
||||
#define ORIENTATION_LANDSCAPE_FLIPPED 180
|
||||
#define ORIENTATION_PORTRAIT_FLIPPED 270
|
||||
|
||||
#define DISPLAY_CONTROL_MONITOR_PRIMARY 0x00000001
|
||||
#define DISPLAY_CONTROL_HEADER_LENGTH 0x00000008
|
||||
|
||||
#define DISPLAY_CONTROL_MIN_MONITOR_WIDTH 200
|
||||
#define DISPLAY_CONTROL_MAX_MONITOR_WIDTH 8192
|
||||
|
||||
#define DISPLAY_CONTROL_MIN_MONITOR_HEIGHT 200
|
||||
#define DISPLAY_CONTROL_MAX_MONITOR_HEIGHT 8192
|
||||
|
||||
#define DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_WIDTH 10
|
||||
#define DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_WIDTH 10000
|
||||
|
||||
#define DISPLAY_CONTROL_MIN_PHYSICAL_MONITOR_HEIGHT 10
|
||||
#define DISPLAY_CONTROL_MAX_PHYSICAL_MONITOR_HEIGHT 10000
|
||||
|
||||
struct _DISPLAY_CONTROL_HEADER
|
||||
{
|
||||
UINT32 type;
|
||||
UINT32 length;
|
||||
};
|
||||
typedef struct _DISPLAY_CONTROL_HEADER DISPLAY_CONTROL_HEADER;
|
||||
|
||||
struct _DISPLAY_CONTROL_MONITOR_LAYOUT
|
||||
{
|
||||
UINT32 Flags;
|
||||
INT32 Left;
|
||||
INT32 Top;
|
||||
UINT32 Width;
|
||||
UINT32 Height;
|
||||
UINT32 PhysicalWidth;
|
||||
UINT32 PhysicalHeight;
|
||||
UINT32 Orientation;
|
||||
UINT32 DesktopScaleFactor;
|
||||
UINT32 DeviceScaleFactor;
|
||||
};
|
||||
typedef struct _DISPLAY_CONTROL_MONITOR_LAYOUT DISPLAY_CONTROL_MONITOR_LAYOUT;
|
||||
|
||||
struct _DISPLAY_CONTROL_MONITOR_LAYOUT_PDU
|
||||
{
|
||||
UINT32 MonitorLayoutSize;
|
||||
UINT32 NumMonitors;
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors;
|
||||
};
|
||||
|
||||
typedef struct _DISPLAY_CONTROL_MONITOR_LAYOUT_PDU DISPLAY_CONTROL_MONITOR_LAYOUT_PDU;
|
||||
|
||||
#endif /* FREERDP_CHANNEL_DISP_H */
|
@ -22,39 +22,15 @@
|
||||
#ifndef FREERDP_CHANNEL_DISP_CLIENT_DISP_H
|
||||
#define FREERDP_CHANNEL_DISP_CLIENT_DISP_H
|
||||
|
||||
#define ORIENTATION_LANDSCAPE 0
|
||||
#define ORIENTATION_PORTRAIT 90
|
||||
#define ORIENTATION_LANDSCAPE_FLIPPED 180
|
||||
#define ORIENTATION_PORTRAIT_FLIPPED 270
|
||||
|
||||
#define DISPLAY_CONTROL_MONITOR_PRIMARY 0x00000001
|
||||
|
||||
struct _DISPLAY_CONTROL_MONITOR_LAYOUT
|
||||
{
|
||||
UINT32 Flags;
|
||||
INT32 Left;
|
||||
INT32 Top;
|
||||
UINT32 Width;
|
||||
UINT32 Height;
|
||||
UINT32 PhysicalWidth;
|
||||
UINT32 PhysicalHeight;
|
||||
UINT32 Orientation;
|
||||
UINT32 DesktopScaleFactor;
|
||||
UINT32 DeviceScaleFactor;
|
||||
};
|
||||
typedef struct _DISPLAY_CONTROL_MONITOR_LAYOUT DISPLAY_CONTROL_MONITOR_LAYOUT;
|
||||
|
||||
/**
|
||||
* Client Interface
|
||||
*/
|
||||
|
||||
#define DISP_DVC_CHANNEL_NAME "Microsoft::Windows::RDS::DisplayControl"
|
||||
#include <freerdp/channels/disp.h>
|
||||
|
||||
typedef struct _disp_client_context DispClientContext;
|
||||
|
||||
typedef UINT (*pcDispCaps)(DispClientContext* context, UINT32 MaxNumMonitors, UINT32 MaxMonitorAreaFactorA,
|
||||
UINT32 MaxMonitorAreaFactorB);
|
||||
typedef UINT (*pcDispSendMonitorLayout)(DispClientContext* context, UINT32 NumMonitors, DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors);
|
||||
typedef UINT(*pcDispCaps)(DispClientContext* context, UINT32 MaxNumMonitors,
|
||||
UINT32 MaxMonitorAreaFactorA,
|
||||
UINT32 MaxMonitorAreaFactorB);
|
||||
typedef UINT(*pcDispSendMonitorLayout)(DispClientContext* context, UINT32 NumMonitors,
|
||||
DISPLAY_CONTROL_MONITOR_LAYOUT* Monitors);
|
||||
|
||||
struct _disp_client_context
|
||||
{
|
||||
@ -66,4 +42,3 @@ struct _disp_client_context
|
||||
};
|
||||
|
||||
#endif /* FREERDP_CHANNEL_DISP_CLIENT_DISP_H */
|
||||
|
||||
|
69
include/freerdp/server/disp.h
Normal file
69
include/freerdp/server/disp.h
Normal file
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* RDPEDISP Virtual Channel Extension
|
||||
*
|
||||
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.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_DISP_SERVER_DISP_H
|
||||
#define FREERDP_CHANNEL_DISP_SERVER_DISP_H
|
||||
|
||||
#include <freerdp/channels/disp.h>
|
||||
|
||||
#include <freerdp/freerdp.h>
|
||||
#include <freerdp/api.h>
|
||||
#include <freerdp/types.h>
|
||||
|
||||
typedef struct _disp_server_private DispServerPrivate;
|
||||
typedef struct _disp_server_context DispServerContext;
|
||||
|
||||
typedef UINT(*psDispMonitorLayout)(DispServerContext* context,
|
||||
const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* pdu);
|
||||
typedef UINT(*psDispCaps)(DispServerContext* context);
|
||||
typedef UINT(*psDispOpen)(DispServerContext* context);
|
||||
typedef UINT(*psDispClose)(DispServerContext* context);
|
||||
|
||||
struct _disp_server_context
|
||||
{
|
||||
void* custom;
|
||||
HANDLE vcm;
|
||||
|
||||
/* Server capabilities */
|
||||
UINT32 MaxNumMonitors;
|
||||
UINT32 MaxMonitorAreaFactorA;
|
||||
UINT32 MaxMonitorAreaFactorB;
|
||||
|
||||
psDispOpen Open;
|
||||
psDispClose Close;
|
||||
|
||||
psDispMonitorLayout DispMonitorLayout;
|
||||
psDispCaps DisplayControlCaps;
|
||||
|
||||
DispServerPrivate* priv;
|
||||
rdpContext* rdpcontext;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
FREERDP_API DispServerContext* disp_server_context_new(HANDLE vcm);
|
||||
FREERDP_API void disp_server_context_free(DispServerContext* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FREERDP_CHANNEL_DISP_SERVER_DISP_H */
|
@ -33,6 +33,8 @@ set(${MODULE_PREFIX}_SRCS
|
||||
pf_update.h
|
||||
pf_rdpgfx.c
|
||||
pf_rdpgfx.h
|
||||
pf_disp.c
|
||||
pf_disp.h
|
||||
pf_server.c
|
||||
pf_server.h
|
||||
pf_common.c
|
||||
|
@ -30,12 +30,14 @@
|
||||
#include <freerdp/client/rail.h>
|
||||
#include <freerdp/client/cliprdr.h>
|
||||
#include <freerdp/client/rdpgfx.h>
|
||||
#include <freerdp/client/disp.h>
|
||||
|
||||
#include "pf_channels.h"
|
||||
#include "pf_client.h"
|
||||
#include "pf_context.h"
|
||||
#include "pf_rdpgfx.h"
|
||||
#include "pf_log.h"
|
||||
#include "pf_disp.h"
|
||||
|
||||
#define TAG PROXY_TAG("channels")
|
||||
|
||||
@ -59,6 +61,27 @@ void pf_OnChannelConnectedEventHandler(void* context,
|
||||
server = ps->gfx;
|
||||
pf_rdpgfx_pipeline_init(gfx, server, pc->pdata);
|
||||
}
|
||||
else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
|
||||
{
|
||||
UINT error;
|
||||
pc->disp = (DispClientContext*) e->pInterface;
|
||||
ps->dispOpened = FALSE;
|
||||
|
||||
if ((error = ps->disp->Open(ps->disp)) != CHANNEL_RC_OK)
|
||||
{
|
||||
if (error == ERROR_NOT_FOUND)
|
||||
{
|
||||
/* disp is not opened by client, ignore */
|
||||
return;
|
||||
}
|
||||
|
||||
WLog_WARN(TAG, "Failed to open disp channel");
|
||||
return;
|
||||
}
|
||||
|
||||
ps->dispOpened = TRUE;
|
||||
pf_disp_register_callbacks(pc->disp, ps->disp, pc->pdata);
|
||||
}
|
||||
}
|
||||
|
||||
void pf_OnChannelDisconnectedEventHandler(void* context,
|
||||
@ -74,7 +97,10 @@ void pf_OnChannelDisconnectedEventHandler(void* context,
|
||||
}
|
||||
else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
|
||||
{
|
||||
gdi_graphics_pipeline_uninit(((rdpContext*)pc)->gdi,
|
||||
(RdpgfxClientContext*) e->pInterface);
|
||||
gdi_graphics_pipeline_uninit(((rdpContext*)context)->gdi, (RdpgfxClientContext*) e->pInterface);
|
||||
}
|
||||
else if (strcmp(e->name, DISP_DVC_CHANNEL_NAME) == 0)
|
||||
{
|
||||
pc->disp = NULL;
|
||||
}
|
||||
}
|
||||
|
@ -105,4 +105,8 @@ void pf_common_copy_settings(rdpSettings* dst, rdpSettings* src)
|
||||
dst->GfxAVC444 = src->GfxAVC444;
|
||||
dst->GfxSendQoeAck = src->GfxSendQoeAck;
|
||||
dst->GfxAVC444v2 = src->GfxAVC444v2;
|
||||
}
|
||||
dst->SupportDisplayControl = src->SupportDisplayControl;
|
||||
dst->SupportMonitorLayoutPdu = src->SupportMonitorLayoutPdu;
|
||||
dst->DynamicResolutionUpdate = src->DynamicResolutionUpdate;
|
||||
dst->DesktopResize = src->DesktopResize;
|
||||
}
|
||||
|
@ -27,6 +27,9 @@
|
||||
#include <freerdp/client/rdpei.h>
|
||||
#include <freerdp/client/rdpgfx.h>
|
||||
#include <freerdp/server/rdpgfx.h>
|
||||
#include <freerdp/client/disp.h>
|
||||
#include <freerdp/server/disp.h>
|
||||
|
||||
#include "pf_config.h"
|
||||
#include "pf_server.h"
|
||||
#include "pf_filters.h"
|
||||
@ -47,6 +50,9 @@ struct p_server_context
|
||||
HANDLE dynvcReady;
|
||||
|
||||
RdpgfxServerContext* gfx;
|
||||
DispServerContext* disp;
|
||||
|
||||
BOOL dispOpened;
|
||||
};
|
||||
typedef struct p_server_context pServerContext;
|
||||
|
||||
@ -61,6 +67,7 @@ struct p_client_context
|
||||
|
||||
RdpeiClientContext* rdpei;
|
||||
RdpgfxClientContext* gfx;
|
||||
DispClientContext* disp;
|
||||
};
|
||||
typedef struct p_client_context pClientContext;
|
||||
|
||||
|
55
server/proxy/pf_disp.c
Normal file
55
server/proxy/pf_disp.c
Normal file
@ -0,0 +1,55 @@
|
||||
#include <freerdp/server/disp.h>
|
||||
|
||||
#include "pf_disp.h"
|
||||
#include "pf_log.h"
|
||||
|
||||
#define TAG PROXY_TAG("disp")
|
||||
|
||||
BOOL pf_server_disp_init(pServerContext* ps)
|
||||
{
|
||||
DispServerContext* disp;
|
||||
disp = ps->disp = disp_server_context_new(ps->vcm);
|
||||
|
||||
if (!disp)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
disp->rdpcontext = (rdpContext*)ps;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static UINT pf_disp_monitor_layout(DispServerContext* context,
|
||||
const DISPLAY_CONTROL_MONITOR_LAYOUT_PDU* pdu)
|
||||
{
|
||||
proxyData* pdata = (proxyData*) context->custom;
|
||||
DispClientContext* client = (DispClientContext*) pdata->pc->disp;
|
||||
WLog_INFO(TAG, __FUNCTION__);
|
||||
return client->SendMonitorLayout(client, pdu->NumMonitors, pdu->Monitors);
|
||||
}
|
||||
|
||||
static UINT pf_disp_on_caps_control(DispClientContext* context, UINT32 MaxNumMonitors,
|
||||
UINT32 MaxMonitorAreaFactorA,
|
||||
UINT32 MaxMonitorAreaFactorB)
|
||||
{
|
||||
proxyData* pdata = (proxyData*) context->custom;
|
||||
DispServerContext* server = (DispServerContext*) pdata->ps->disp;
|
||||
WLog_INFO(TAG, __FUNCTION__);
|
||||
/* Update caps of proxy's disp server */
|
||||
server->MaxMonitorAreaFactorA = MaxMonitorAreaFactorA;
|
||||
server->MaxMonitorAreaFactorB = MaxMonitorAreaFactorB;
|
||||
server->MaxNumMonitors = MaxNumMonitors;
|
||||
/* Send CapsControl to client */
|
||||
return server->DisplayControlCaps(server);
|
||||
}
|
||||
|
||||
void pf_disp_register_callbacks(DispClientContext* client, DispServerContext* server,
|
||||
proxyData* pdata)
|
||||
{
|
||||
client->custom = (void*) pdata;
|
||||
server->custom = (void*) pdata;
|
||||
/* client receives from server, forward using disp server to original client */
|
||||
client->DisplayControlCaps = pf_disp_on_caps_control;
|
||||
/* server receives from client, forward to target server using disp client */
|
||||
server->DispMonitorLayout = pf_disp_monitor_layout;
|
||||
}
|
34
server/proxy/pf_disp.h
Normal file
34
server/proxy/pf_disp.h
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||||
* FreeRDP Proxy Server
|
||||
*
|
||||
* Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
|
||||
* Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
|
||||
* Copyright 2019 Idan Freiberg <speidy@gmail.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_SERVER_PROXY_RDPEDISP_H
|
||||
#define FREERDP_SERVER_PROXY_RDPEDISP_H
|
||||
|
||||
#include <freerdp/client/disp.h>
|
||||
#include <freerdp/server/disp.h>
|
||||
|
||||
#include "pf_context.h"
|
||||
|
||||
BOOL pf_server_disp_init(pServerContext* ps);
|
||||
void pf_disp_register_callbacks(DispClientContext* client, DispServerContext* server,
|
||||
proxyData* pdata);
|
||||
|
||||
#endif /*FREERDP_SERVER_PROXY_RDPEDISP_H*/
|
@ -49,6 +49,7 @@
|
||||
#include "pf_input.h"
|
||||
#include "pf_update.h"
|
||||
#include "pf_rdpgfx.h"
|
||||
#include "pf_disp.h"
|
||||
|
||||
#define TAG PROXY_TAG("server")
|
||||
|
||||
@ -182,6 +183,7 @@ static BOOL pf_server_post_connect(freerdp_peer* client)
|
||||
}
|
||||
|
||||
pf_server_rdpgfx_init(ps);
|
||||
pf_server_disp_init(ps);
|
||||
|
||||
/* Start a proxy's client in it's own thread */
|
||||
if (!(ps->thread = CreateThread(NULL, 0, pf_client_start, pc, 0, NULL)))
|
||||
@ -256,6 +258,9 @@ static DWORD WINAPI pf_server_handle_client(LPVOID arg)
|
||||
goto out_free_peer;
|
||||
}
|
||||
|
||||
client->settings->SupportDisplayControl = TRUE;
|
||||
client->settings->SupportMonitorLayoutPdu = TRUE;
|
||||
client->settings->DynamicResolutionUpdate = TRUE;
|
||||
client->settings->RdpSecurity = config->RdpSecurity;
|
||||
client->settings->TlsSecurity = config->TlsSecurity;
|
||||
client->settings->NlaSecurity = config->NlaSecurity;
|
||||
@ -263,8 +268,7 @@ static DWORD WINAPI pf_server_handle_client(LPVOID arg)
|
||||
client->settings->ColorDepth = 32;
|
||||
client->settings->SuppressOutput = TRUE;
|
||||
client->settings->RefreshRect = TRUE;
|
||||
client->settings->UseMultimon = TRUE;
|
||||
client->settings->SupportMonitorLayoutPdu = TRUE;
|
||||
client->settings->DesktopResize = TRUE;
|
||||
client->PostConnect = pf_server_post_connect;
|
||||
client->Activate = pf_server_activate;
|
||||
client->AdjustMonitorsLayout = pf_server_adjust_monitor_layout;
|
||||
@ -345,6 +349,17 @@ static DWORD WINAPI pf_server_handle_client(LPVOID arg)
|
||||
|
||||
fail:
|
||||
|
||||
if (ps->disp)
|
||||
{
|
||||
if (ps->dispOpened)
|
||||
{
|
||||
WLog_INFO(TAG, "Closing disp server");
|
||||
(void)ps->disp->Close(ps->disp);
|
||||
}
|
||||
|
||||
disp_server_context_free(ps->disp);
|
||||
}
|
||||
|
||||
if (client->connected && !pf_common_connection_aborted_by_peer(pdata))
|
||||
{
|
||||
pf_server_handle_client_disconnection(client);
|
||||
|
@ -19,7 +19,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <freerdp/update.h>
|
||||
#include <freerdp/display.h>
|
||||
|
||||
#include "pf_update.h"
|
||||
#include "pf_context.h"
|
||||
|
Loading…
Reference in New Issue
Block a user